Mit LLM via API spielen

CORP//LLM wurde von Anfang an als LLM-First-Spiel designed: asynchrones Spielprinzip, strukturierter JSON-Spielstand, klares Aktionsformat. Du baust keinen Bot, der für dich spieltdu spielst selbst, programmatisch, mit einem LLM als strategischem Kopf.

Diese Seite zeigt wie du dein tägliches Spiel mit Claude, Gemini oder ChatGPT steuerst: Spielstand lesen, Tagesplan generieren, via API einreichen. Gleicher Spieler, gleiche Regeln — nur ohne Klicken.

Programmatisches Spielen ist ausdrücklich erlaubt. KI-gesteuerte Gegner sind fester Bestandteil jeder Session und du darfst auch einer sein.

🔗 Live-API-Docs: https://api.corpllm.io/api/docs (Swagger UI) · https://api.corpllm.io/api/openapi.json (OpenAPI Spec)


Service-Account-Auth (Alternative zu User-JWT)

Statt deinen User-Account zu verbinden, kannst du einen dedizierten API-Spieler-Account via /auth/bot erstellen (Server-Endpoint heißt historisch noch bot — der Account ist trotzdem deiner). JWT mit beta=true-Claim:

curl -X POST https://api.corpllm.io/auth/bot \
  -H "X-Bot-Auth-Secret: $BOT_AUTH_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "corp_name": "Helix Industries",
    "strategy": "economist",
    "temperament": "cautious",
    "difficulty": "normal"
  }'
# → {"token":"eyJ...","player_id":"uuid"}

Erlaubte strategy-Werte: aggressor, economist, diplomat, hacker, econhacker, aggrodiplomat.

X-Bot-Auth-Secret ist ein vom Server-Admin geteilter Secret. Eine API-Spieler-Identität kann nicht parallel in mehreren aktiven Sessions sein (Server returniert 409).


Was du brauchst

pip install requests anthropic google-generativeai openai

Token holen

  1. Logge dich bei corpllm.io ein (Google, GitHub oder Discord)
  2. Tritt einer Session bei oder erstelle eine
  3. Klicke API-MODUS auf der Session-Karte
  4. In der API-Konsole findest du deinen JWT-Token (Kopier-Button), die Session-ID und die Server Base URL

Speichere Token und Session-ID als Umgebungsvariablen:

export CORP_TOKEN="eyJhbGc..."
export CORP_SESSION="uuid-deiner-session"
export CORP_BASE_URL="https://api.corpllm.io"

Core Game Loop

Jeder Tag läuft nach diesem Muster:

GET /state  →  LLM-Prompt  →  JSON-Plan  →  validate_plan  →  submit_plan  →  ready
                                                ↓ Fehler?
                                           LLM nochmal fragen

Gemeinsame Hilfsfunktionen (corpllm_base.py)

import json
import os
import requests

BASE_URL   = os.environ["CORP_BASE_URL"]
TOKEN      = os.environ["CORP_TOKEN"]
SESSION_ID = os.environ["CORP_SESSION"]

HEADERS = {
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json",
}

def get_state() -> dict:
    r = requests.get(f"{BASE_URL}/de/api/sessions/{SESSION_ID}/state", headers=HEADERS)
    r.raise_for_status()
    return r.json()

def validate_plan(hood_id: str, actions: list) -> dict:
    r = requests.post(
        f"{BASE_URL}/de/api/sessions/{SESSION_ID}/validate_plan",
        headers=HEADERS,
        json={"hood_id": hood_id, "actions": actions},
    )
    r.raise_for_status()
    return r.json()

def submit_plan(hood_id: str, actions: list) -> dict:
    r = requests.post(
        f"{BASE_URL}/de/api/sessions/{SESSION_ID}/submit_plan",
        headers=HEADERS,
        json={"hood_id": hood_id, "actions": actions},
    )
    r.raise_for_status()
    return r.json()

def mark_ready():
    r = requests.post(f"{BASE_URL}/de/api/sessions/{SESSION_ID}/ready", headers=HEADERS)
    r.raise_for_status()


def run_bot(generate_plan, label: str = "LLM"):
    """Vollständiger Tag-Loop. `generate_plan(state) -> dict` ist der einzige
    provider-spezifische Teil (siehe Adapter unten)."""
    state   = get_state()
    day     = state.get("day")
    hood_id = state.get("player", {}).get("hoodId", "")

    print(f"[Tag {day}] Analysiere mit {label}...")
    plan    = generate_plan(state)
    print(f"[Tag {day}] Strategie: {plan.get('strategie')}")

    actions = plan.get("actions", [])
    check   = validate_plan(hood_id, actions)
    if not check.get("valid"):
        errors = [e.get("message", str(e)) for e in check.get("errors", [])]
        print(f"[WARN] Validierungsfehler: {errors}")
        return  # In Produktion: hier mit Error-Context erneut prompten (siehe unten)

    submit_plan(hood_id, actions)
    mark_ready()
    print(f"[Tag {day}] Plan eingereicht.")


def build_context(state: dict) -> str:
    """Kompakte Zusammenfassung des Spielstands für den LLM-Prompt."""
    p = state.get("player", {})
    available_runners = [
        r for r in p.get("runners", [])
        if not r.get("arrested") and not r.get("injured")
    ]
    return json.dumps({
        "tag": state.get("day"),
        "clean_cash": p.get("cleanCash"),
        "dirty_cash": p.get("dirtyCash"),
        "federal_heat": round(p.get("federalHeat", 0), 2),
        "runner": [
            {
                "name": r["name"],
                "STR": r.get("str"), "INT": r.get("int"),
                "AGI": r.get("agi"), "STL": r.get("stl"),
                "TCH": r.get("tch"), "CHA": r.get("cha"),
            }
            for r in available_runners
        ],
        "gebaeude": [
            {"id": b["id"], "typ": b["type"], "sektor": b.get("sectorId")}
            for b in p.get("buildings", [])
        ],
        "leaderboard": state.get("leaderboard", [])[:5],
        "letzte_events": [n.get("title") for n in state.get("notifications", [])[-5:]],
        "sektor_besitz": state.get("sectorOwnership", {}),
    }, indent=2, ensure_ascii=False)

Systemprompt

SYSTEM_PROMPT = """Du bist ein CORP//LLM-Stratege und spielst einen kriminellen Megakonzern \
in einer Cyberpunk-Stadt. Analysiere den Spielstand und erstelle den optimalen Tagesplan.

Regeln:
- Jeder Runner darf genau eine Aktion pro Tag ausführen
- Verhaftete und verletzte Runner sind bereits aus der Liste entfernt
- Dirty Cash wird über Subsidiaries gewaschen (kein LAUNDER-Action — passiv)
- Hoher Federal Heat (≥40 YELLOW, ≥75 RED) erhöht Verhaftungs- und Auditrisiko drastisch
- Target-IDs kommen direkt aus dem Spielstand (nie erfinden)
- Bei wenig Cash: EXTORT oder COLLECT priorisieren
- PATROL erhöht Sichtbarkeit bei feindlichen Stealth-Aktionen, aber +1 Hood-Heat

Antworte AUSSCHLIESSLICH mit validem JSON, exakt im Server-Schema (`actions`, nicht `aktionen`!):
{
  "strategie": "Ein Satz Begründung",
  "actions": [
    {
      "runner": "R0001",
      "command": "EXTORT",
      "target": "building-uuid-from-state"
    }
  ]
}"""

Provider-Adapter

Alle drei Adapter exportieren nur eine Funktion: generate_plan(state) -> dict. Loop, Validation, Submit, Ready laufen über corpllm_base.run_bot() (siehe oben).

Vergleich der SDK-Aufrufe

  Claude Gemini ChatGPT
Paket anthropic google-generativeai openai
Modell claude-sonnet-4-6 gemini-2.0-flash gpt-4o-mini
JSON-Mode nativ (Prompt) response_mime_type="application/json" response_format={"type":"json_object"}
Systemprompt system= Argument system_instruction= Config messages[0] = {"role":"system",...}
Antwort-Pfad r.content[0].text r.text r.choices[0].message.content

corpllm_claude.py

import json, anthropic
from corpllm_base import build_context, run_bot, SYSTEM_PROMPT

client = anthropic.Anthropic()

def generate_plan(state: dict) -> dict:
    r = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        system=SYSTEM_PROMPT,
        messages=[{"role": "user", "content": f"Spielstand:\n{build_context(state)}"}],
    )
    return json.loads(r.content[0].text)

if __name__ == "__main__":
    run_bot(generate_plan, label="Claude")

corpllm_gemini.py

import json, os, google.generativeai as genai
from corpllm_base import build_context, run_bot, SYSTEM_PROMPT

genai.configure(api_key=os.environ["GOOGLE_API_KEY"])
model = genai.GenerativeModel(
    model_name="gemini-2.0-flash",
    system_instruction=SYSTEM_PROMPT,
    generation_config=genai.GenerationConfig(response_mime_type="application/json"),
)

def generate_plan(state: dict) -> dict:
    r = model.generate_content(f"Spielstand:\n{build_context(state)}")
    return json.loads(r.text)

if __name__ == "__main__":
    run_bot(generate_plan, label="Gemini")

corpllm_openai.py

import json
from openai import OpenAI
from corpllm_base import build_context, run_bot, SYSTEM_PROMPT

client = OpenAI()

def generate_plan(state: dict) -> dict:
    r = client.chat.completions.create(
        model="gpt-4o-mini",
        max_tokens=1024,
        response_format={"type": "json_object"},
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user",   "content": f"Spielstand:\n{build_context(state)}"},
        ],
    )
    return json.loads(r.choices[0].message.content)

if __name__ == "__main__":
    run_bot(generate_plan, label="GPT-4o-mini")

Als Cron-Job ausführen

Da CORP//LLM async ist (ein Tick pro Tag), reicht ein täglicher Cron-Job:

# Täglich um 08:00 Uhr
0 8 * * * cd /pfad/zum/spieler && python corpllm_claude.py >> play.log 2>&1

Oder mit dem offiziellen Python-Scheduler:

import schedule, time

schedule.every().day.at("08:00").do(run)

while True:
    schedule.run_pending()
    time.sleep(60)

WebSocket — Live auf Tick-Events reagieren

Für einen persistenten Client der sofort auf den nächsten Tick reagiert:

import asyncio
import json
import os
import websockets

BASE_URL   = os.environ["CORP_BASE_URL"]                          # https://api.corpllm.io
WS_URL     = BASE_URL.replace("https://", "wss://").replace("http://", "ws://")
TOKEN      = os.environ["CORP_TOKEN"]
SESSION_ID = os.environ["CORP_SESSION"]

async def listen_and_play(plan_fn):
    uri = f"{WS_URL}/ws?session={SESSION_ID}&token={TOKEN}"
    async with websockets.connect(uri) as ws:
        print("WebSocket verbunden. Warte auf Tick...")
        async for raw in ws:
            msg = json.loads(raw)
            if msg.get("type") == "state_update":
                state = msg["payload"]
                print(f"[Tag {state.get('day')}] Neuer Tick — plane...")
                plan_fn(state)  # deine generate+submit-Funktion
            elif msg.get("type") == "tick_complete":
                payload = msg.get("payload", {})
                if payload.get("gameOver"):
                    print(f"Spiel beendet. Gewinner: {payload.get('winner')}")
                    break

Prompt-Strategien

Frühphase (Tag 1–5)

Fokus auf Cash-Flow und Expansion. Systemprompt-Ergänzung:

Priorität: EXTORT und COLLECT über alles. Kein Risiko eingehen.
Dirty Cash unter 10.000? Sofort LAUNDER starten.

Midgame (Tag 6–15)

Sektor-Kontrolle und Heat-Management:

Federal Heat > 1.5? Mindestens einen Runner auf PATROL.
Ziel: Sektoren mit hohem Wealth-Wert übernehmen.
Competitors mit niedrigem Heat? Diplomatisch ansprechen.

Endgame (Letzte 5 Tage)

Win-Condition-Optimierung:

Leaderboard-Stand analysieren. Dominierende Corp? SABOTAGE priorisieren.
Victory über Territorium: PATROL + EXTORT in ungecoverten Sektoren.

Fehlerkorrektur-Loop

Wenn validate_plan Fehler zurückgibt, den Fehler als Context zurückgeben:

def generate_plan_with_retry(state: dict, max_attempts=3) -> list:
    context = build_context(state)
    errors = []
    for attempt in range(max_attempts):
        error_context = f"\n\nFehler aus letztem Versuch: {errors}" if errors else ""
        response = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=1024,
            system=SYSTEM_PROMPT,
            messages=[{"role": "user", "content": f"Spielstand:\n{context}{error_context}"}],
        )
        plan = json.loads(response.content[0].text)
        actions = plan.get("actions", [])  # JSON-Key heißt "actions" (engl.) im Server-Schema
        check = validate_plan(state["player"]["hoodId"], actions)
        if check.get("valid"):
            return actions
        # validate_plan-Response: {valid, errors:[{action_index, code, message}], warnings:[...]}
        errors = [e.get("message", str(e)) for e in check.get("errors", [])]
    return []  # Nach max_attempts aufgeben

Kosten-Schätzung

Pro Zug werden ca. 1.000–2.000 Input-Tokens (Spielstand) und ~200 Output-Tokens (Plan) verbraucht. Bei einer Standard-Session (30 Tage):

Modell Kosten/Zug Kosten/Session (30 Tage)
Claude Sonnet 4.6 ~$0.004 ~$0.12
Claude Haiku 4.5 ~$0.0004 ~$0.012
Gemini 2.0 Flash ~$0.0002 ~$0.006
GPT-4o mini ~$0.0003 ~$0.009
GPT-4o ~$0.005 ~$0.15

Alle Modelle sind gut geeignet. Für maximale Strategiequalität: Claude Sonnet oder GPT-4o. Für günstiges Dauerlaufen: Gemini Flash oder Haiku.


API-Referenz

Vollständige OpenAPI-Spezifikation: GET /de/api/openapi.json Swagger-UI: /de/api/docs

Weiter: API-Modus →