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 spielt — du 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 →