Bot-Anleitung (Python)
Ein Bot ist eine Python-Datei mit einer Klasse Bot. In jeder Runde bekommt er eine
Beobachtung des Spielzustands und gibt Befehle für seine Schiffe zurück.
Der kleinste Bot
from kobaltmeer.sdk import BaseBot, Observation, Orders
class Bot(BaseBot):
name = "Mein Bot"
def decide(self, observation: Observation, player: int) -> Orders:
orders = Orders()
for ship in observation.fleet(player):
orders.ships[ship.id] = "O" # stehen bleiben und abbauen
return orders
Dieser Bot baut nur ab und liefert nie etwas ab. Ein vollständiges Beispiel mit Bewegung, Heimfahrt und Schiffbau ist mein_bot.py. Kopiere es als Ausgangspunkt:
cp bots/mein_bot.py bots/test_bot.py
./start.sh gui --bots bots/test_bot.py collector --seed 42 --turns 240
Tipp: Ändere zunächst nur eine Entscheidung, etwa die Frachtgrenze für die Heimfahrt.
Teste auf derselben Karte, schau dir das Replay an und vergleiche erst dann über mehrere
Seeds und Startplätze. Im Fenster startet R dieselbe Karte mit frisch geladenem Code.
Lebenszyklus
Der Konstruktor muss das Schlüsselwort seed annehmen. Wer von BaseBot erbt, bekommt
self.rng, einen NumPy-Zufallsgenerator aus diesem Seed. Nutze ihn für jeden Zufall,
dann bleiben deine Partien reproduzierbar.
class Bot(BaseBot):
name = "Mein Strategieexperiment"
def __init__(self, seed=0):
super().__init__(seed)
self.heimfahrt = set()
Pro Partie und Startplatz entsteht eine neue Instanz. Attribute bleiben zwischen den Runden erhalten, zwischen Partien nicht. Belegt derselbe Bot mehrere Startplätze, laufen unabhängige Instanzen in eigenen Prozessen.
Die Beobachtung
Alle Flotten entscheiden auf demselben Zustand vor dem Zug. Kein Bot sieht die Befehle eines anderen.
| Zugriff | Bedeutung |
|---|---|
observation.rules |
Regeln, z. B. size, turns, capacity, ship_cost, relay_cost |
observation.turn |
Bisher gespielte Runden, beginnend bei 0 |
observation.resource[y, x] |
Lumen auf einem Feld – zuerst y, dann x |
observation.ships |
Alle Schiffe aller Flotten |
observation.players |
Alle Flotten in der Reihenfolge ihrer IDs |
observation.sites |
Alle Werften und Relais |
observation.fleet(player) |
Deine Schiffe |
observation.harbors(player) |
Deine Ablieferstellen als (x, y) |
observation.distance(a, b) |
Entfernung über den umlaufenden Kartenrand |
observation.move_cost(ship) |
Bewegungskosten mit der Rundung des aktuellen Profils |
observation.inspired(ship) |
Ob ein Schiff an seiner Position inspiriert ist |
observation.destination(pos, action) |
Zielfeld einer Aktion |
observation.toward(pos, target) |
Eine Richtung auf kürzestem Weg; O, wenn schon dort |
Das Raster ist schreibgeschützt. Änderungen an den Daten in deinem Bot ändern das Spiel nicht.
Schiff (ShipInfo): id, owner, x, y, cargo, inspired und pos.
Eine Schiff-ID bleibt das ganze Schiffsleben gleich und wird nie wiederverwendet.
Flotte (FleetInfo): id, name, bank (Lager), yard (Werft), deposited,
mined, lost, built, spent, bonus, alive, last_turn_alive und rank.
minedist die vom Raster entnommene Menge, einschließlich des Feldvorkommens bei einem Relaisbau.bonusist die zusätzlich durch Inspiration entstandene Fracht.depositedzählt alle Ablieferungen, auch die Gutschrift beim Relaisbau.spentzählt alle Ausgaben, einschließlich Treibstoff (der aus der Fracht bezahlt wird).aliveundrankzeigen Ausscheiden und aktuelle Rangfolge.
Hafen (Site): owner, x, y, kind (yard oder relay) und pos.
Neue Schiffe entstehen nur an der Werft (yard).
Befehle
orders = Orders()
orders.ships[ship.id] = "E"
orders.spawn = True
return orders
| Aktion | Bedeutung |
|---|---|
O |
Stehen bleiben und abbauen |
N |
Nach Norden, y − 1 |
E |
Nach Osten, x + 1 |
S |
Nach Süden, y + 1 |
W |
Nach Westen, x − 1 |
B |
Schiff in ein eigenes Relais umwandeln |
spawn = True beantragt ein neues Schiff. Fehlende Schiffsbefehle werden zu O.
Befehle für fremde oder nicht vorhandene Schiffe werden ignoriert. Ein unbekannter
Aktionsname gilt dagegen als Fehler und schaltet den Bot für den Rest der Partie ab.
Statt Orders darfst du auch ein Dictionary liefern:
return {"ships": {str(ship.id): "N"}, "spawn": False}
Verwende dabei normale Python-bool-Werte, keine NumPy-Werte.
Heimfahrt merken
Fährt ein Schiff ab 560 Fracht heim, kostet jeder Schritt Treibstoff. Prüfst du danach erneut
nur cargo >= 560, bricht es die Heimfahrt ab. Merke dir deshalb, wer gerade heimfährt:
if ship.cargo >= 560:
self.heimfahrt.add(ship.id)
if ship.pos in observation.harbors(player):
self.heimfahrt.discard(ship.id)
if ship.id in self.heimfahrt:
... # weiter zum Hafen
Denk auch an das Spielende: Volle Fracht weit weg vom Hafen zählt nicht. Ein neues Schiff lohnt sich nur, wenn es seine Kosten bis zum Ende wieder einsammeln kann.
Eigene Kollisionen vermeiden
observation.toward() kennt nur Entfernungen, keine anderen Schiffe. Der SDK-Koordinator
plant deine ganze Flotte gemeinsam so, dass keine zwei eigenen Schiffe auf demselben Feld landen:
from kobaltmeer.sdk import coordinate
preferences = {}
for ship in observation.fleet(player):
preferred = observation.toward(ship.pos, observation.players[player].yard)
preferences[ship.id] = [preferred, "O", "N", "E", "S", "W"]
orders.ships = coordinate(observation, player, preferences)
- Jedes Schiff bekommt eine Liste bevorzugter Aktionen;
Oist immer der Notfall. - Platztausch und größere Kreisbewegungen sind erlaubt.
- Schiffe ohne genug Treibstoff bleiben automatisch stehen.
- Später eingetragene Schiffe haben Vorrang. Trage dringende Heimfahrer deshalb zuletzt ein.
Gegnerische Kollisionen kann der Koordinator nicht verhindern. Schiffe, die du mit B in ein
Relais umwandelst, plane mit ["O"] ein und setze danach wieder B. So bleibt ihr Feld
reserviert, falls der Bau nicht bezahlt werden kann.
Diagnose
print()landet im Debugprotokoll (stderr_tailin Berichten und Replays).- Ausnahmen erscheinen unter
faultsund alsbot_errorim Replay. - Ein hängender Bot wird nach dem Zeitlimit beendet; seine Flotte wartet danach.
timings_msenthält die Summe deiner Entscheidungszeiten.
./start.sh play --bots bots/test_bot.py collector --seed 42 \
--replay replays/fehleranalyse.json.gz --output runs/fehleranalyse.json
./start.sh gui --replay replays/fehleranalyse.json.gz
Ziele und Gründe aufzeichnen
Mit explain() hängst du an einen Befehl an, was ein Schiff vorhat und warum.
Der Replay-Viewer zeigt das mit „Ziele & Gründe“ an. Das ist sehr
hilfreich, um eigene Fehlentscheidungen zu finden.
orders.ships[ship.id] = "E"
orders.explain(ship.id, target=(12, 8), reason="Volle Fracht zum Hafen bringen",
path=[ship.pos, observation.destination(ship.pos, "E")])
explain() erteilt keinen Befehl und ändert nichts am Spiel. Erlaubt sind nur eigene Schiffe;
pro Runde werden höchstens 128 Einträge mit je 64 Pfadpunkten übernommen, Gründe werden auf
240 Zeichen gekürzt. Fehlerhafte Einträge werden verworfen, ohne den Bot abzuschalten.
Regelprofile berücksichtigen
Lies Kosten und Grenzen immer aus observation.rules und nutze observation.move_cost(ship)
statt einer festen Rundung. Dann funktioniert dein Bot in beiden Profilen. Im Profil Classic
gibt es kein kostenloses Startschiff: Dein Bot muss zuerst spawn = True beantragen.
Die Engine direkt verwenden
Die Engine braucht weder Fenster noch Botprozesse. Für eigene Experimente:
from kobaltmeer.engine import Game, Rules, Orders
from kobaltmeer.sdk import Observation
game = Game(Rules.for_profile("classic", players=1, seed=42, turns=240))
observation = Observation.from_snapshot(game.rules, game.snapshot())
game.step({0: Orders(spawn=True)})
Rules.for_profile("kobaltmeer" | "classic", **werte) lädt ein Profil und überschreibt
einzelne Felder. Eine Lernumgebung mit reset() und step() beschreibt die Seite Training.
Replays selbst auswerten
Replays sind gzip-komprimiertes JSON. Lies sie mit load_replay(), das liefert für jede
Runde den vollständigen Zustand:
from kobaltmeer.replay import load_replay
replay = load_replay("replays/erste-partie.json.gz")
for frame in replay["frames"]:
print(frame["turn"], [p["bank"] for p in frame["players"]])
Jeder Frame enthält turn, resource, ships, players, sites, events sowie die
angeforderten orders und aufgezeichneten debug-Einträge des Übergangs in diese Runde.
In der Datei selbst speichert das Format kobaltmeer-replay-v2 die Ressourcen ab Runde 1
nur als Änderungen; load_replay() setzt sie wieder zusammen.
Andere Sprachen
Bots in C++, C#, Go, Rust oder JavaScript sprechen dasselbe Spiel über ein einfaches JSON-Protokoll. Siehe Andere Sprachen.