Skip to content

Repository files navigation

Gefangenendilemma Game Server

Lernprojekt für Code-Reviews zu Design und Architektur. Der Server implementiert ein Gefangenendilemma mit TCP-Bots, browserbasierten Code-Bots und einem Web-UI.

Architektur

Monorepo (npm Workspaces) mit drei Paketen:

Paket Beschreibung
server/ Express-Backend, WebSocket, TCP-Server, Code-Bot-Ausführung
client/ React SPA (Vite)
example-bot/ Beispiel-TCP-Bots (Tit-for-Tat, Random)

Ports (Entwicklung)

Dienst Port Protokoll
HTTP / REST / WebSocket 3000 HTTP, WS (/ws)
TCP-Bots 4000 Line-Delimited JSON
Frontend (Dev) 5173 Vite Dev-Server mit Proxy auf 3000

In Produktion liefert der Server das gebaute Frontend selbst aus (client/dist).

Schnellstart (lokal)

npm install
npm run dev

Startet Backend (Port 3000) und Frontend (Port 5173). Web-UI: http://localhost:5173

Produktion (ohne Docker)

npm run build
npm start

Web-UI und API dann unter http://localhost:3000

TCP-Bots

Externe Bots verbinden sich per TCP mit Line-Delimited JSON (eine JSON-Zeile pro Nachricht, mit \n abgeschlossen).

Beispiel-Bots starten

npm run bot          # Tit-for-Tat
npm run bot:random   # Zufallsstrategie (50/50 C/D)

Umgebungsvariablen:

Variable Standard Beschreibung
SERVER_HOST localhost Hostname des Game-Servers
SERVER_PORT 4000 TCP-Port des Game-Servers
BOT_NAME TitForTat_Bot Anzeigename bei der Registrierung

Beispiel gegen den Server auf snug:

SERVER_HOST=snug.meqo.de SERVER_PORT=9998 BOT_NAME=MyBot npm run bot

Protokoll

Registrierung (Bot → Server):

{"type": "register", "name": "TitForTatBot"}

Spielzug-Anfrage (Server → Bot):

{"type": "request_move", "gameId": "g-123", "opponent": "human", "ownScore": 12, "oppScore": 9, "history": [["C", "D"], ["C", "C"]]}

Antwort (Bot → Server):

{"move": "C"}

Code-Bots

Code-Bots werden im Browser-Editor oder per HTTP-API verwaltet. Der Code wird serverseitig ausgeführt und muss 'C' oder 'D' zurückgeben.

Verfügbare Variablen im Bot-Code: gameId, opponent, ownScore, oppScore, history

Code-Bot HTTP-API

Basis-URL lokal: http://localhost:3000/api/bots/code

Auflisten

curl http://localhost:3000/api/bots/code

Anlegen

curl -X POST http://localhost:3000/api/bots/code \
  -H 'Content-Type: application/json' \
  -d '{"name":"MyBot","code":"return \"C\";"}'

Abrufen / Ändern / Löschen

curl http://localhost:3000/api/bots/code/{id}
curl -X PUT http://localhost:3000/api/bots/code/{id} \
  -H 'Content-Type: application/json' \
  -d '{"name":"MyBot_v2","code":"return \"D\";"}'
curl -X DELETE http://localhost:3000/api/bots/code/{id}

Punkte-Matrix

Zug 1 Zug 2 Punkte 1 Punkte 2
C C 3 3
C D 0 5
D C 5 0
D D 1 1

Turnier

Round-Robin: Jeder Bot spielt einmal gegen jeden anderen. Pro Paarung werden 80–120 Runden zufällig gewählt. Bei Fehlern/Timeouts wird der Bot disqualifiziert.

REST-API (Übersicht)

Methode Pfad Beschreibung
GET /api/bots Alle Bots (TCP + Code, ohne Quellcode)
GET /api/bots/code Alle Code-Bots inkl. Quellcode
POST /api/bots/code Code-Bot anlegen
GET /api/bots/code/:id Einzelnen Code-Bot abrufen
PUT /api/bots/code/:id Code-Bot ändern
DELETE /api/bots/code/:id Code-Bot löschen
POST /api/bots/presets/random Random Code-Bot erstellen
DELETE /api/bots/:id Bot entfernen (TCP oder Code)
POST /api/game/human/start Human-Spiel starten
POST /api/game/human/move Zug senden
POST /api/tournament/start Turnier starten

WebSocket für Live-Updates: /ws (relativ zur Web-UI-URL, z. B. wss://snug.meqo.de:9999/ws)


Docker

Dateien

Datei Zweck
Dockerfile Multi-Stage-Build (Client + Server, Node 20 Alpine)
docker-compose.yaml Traefik (SSL) + App
traefik.yml Traefik-Entrypoints und Docker-Provider
dynamic.yml TLS-Zertifikate (crt.pem, key.pem aus /certs)
.dockerignore Schließt node_modules und lokale Builds aus

Lokal testen

docker build -t prisoners-dilemma-game .
docker run -d --name pd-game -p 13000:3000 -p 14000:4000 prisoners-dilemma-game
# Web-UI: http://localhost:13000

Mit Compose (nur App, ohne SSL):

TCP_PORT=14000 docker compose up -d app
docker compose exec app wget -qO- http://127.0.0.1:3000/api/bots

Deployment mit Traefik und SSL

Der Stack startet zwei Container:

  1. app — Node-Server (HTTP 3000, TCP 4000 im Container)
  2. traefik — SSL-Terminierung, Routing zur App

Warum ein eigener Entrypoint gamesecure?

Auf dem Server laufen mehrere Docker-Stacks mit Traefik über denselben Docker-Socket. Alle nutzen oft denselben Hostnamen (snug.meqo.de). Ohne separaten Entrypoint würde Traefik Anfragen an den falschen Container weiterleiten (504 Gateway Timeout). Der Game-Stack verwendet deshalb:

  • Entrypoint gamesecure (intern Port 8443) → extern HTTPS 9999
  • Label traefik.constraint=game — Traefik ignoriert Container anderer Stacks

Umgebungsvariablen (.env)

APP_HOST=snug.meqo.de
SSL_CERT_DIR=/etc/ssl/private/meqo.de
TRAEFIK_HTTPS_PORT=9999
TRAEFIK_HTTP_PORT=9980
TCP_PORT=9998
Variable Standard Beschreibung
APP_HOST game.example.com Host-Header für Traefik-Routing
SSL_CERT_DIR /etc/ssl/private/example.com Verzeichnis mit crt.pem und key.pem
TRAEFIK_HTTPS_PORT 9999 Externer HTTPS-Port (→ intern 8443)
TRAEFIK_HTTP_PORT 9980 Externer HTTP-Port (→ intern 80)
TCP_PORT 9998 Externer TCP-Port für Bots (→ intern 4000)

Starten

docker compose up -d --build

Deployment auf snug (Beispiel)

# Projekt nach /home/cpm/services/game kopieren (ohne node_modules)
rsync -avz --exclude node_modules --exclude .git \
  -e cpm-ssh ./ snug:/home/cpm/services/game/

# Auf dem Server
cd /home/cpm/services/game
docker compose up -d --build

Erreichbar unter:

Wartung

docker compose logs -f
docker compose ps
docker compose up -d --build   # nach Code-Updates
docker compose down

Server-Umgebungsvariablen (App-Container)

Variable Standard Beschreibung
HTTP_PORT 3000 HTTP/WebSocket-Port im Container
TCP_PORT 4000 TCP-Port im Container

About

for educational purposes

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages