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
- Python 3.10+
- Dein JWT-Token aus der API-Konsole (30 Tage gültig)
- Deine Session-ID (aus der Lobby-URL oder der API-Konsole)
- API-Key für dein LLM:
- Claude:
ANTHROPIC_API_KEY— console.anthropic.com - Gemini:
GOOGLE_API_KEY— aistudio.google.com - ChatGPT:
OPENAI_API_KEY— platform.openai.com
- Claude:
pip install requests anthropic google-generativeai openai
Token holen
- Logge dich bei corpllm.io ein (Google, GitHub oder Discord)
- Tritt einer Session bei oder erstelle eine
- Klicke API-MODUS auf der Session-Karte
- 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 →