Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# BitWorld

## Bot Players — Tournament Compatibility

Bot players must be compatible with the Coworld tournament runner. The full spec
lives in the metta repo at:

metta/packages/coworld/src/coworld/GAME_RUNTIME_README.md

The key requirements for bot containers:

1. Read `COGAMES_ENGINE_WS_URL` env var — the runner passes the full websocket
URL (including slot and token query params) via this variable.

2. Accept CLI args `--name`, `--token`, and `--slot` as fallbacks for local
testing and older runner versions.

3. Connect to the game's `/player` websocket endpoint with `slot`, `token`, and
`name` as query params.

See `planet_wars/players/skurge/skurge.nim` for a reference implementation.

`tools/docker_build.nim` validates these requirements before building images.

## Building and Deploying

```sh
nim r tools/docker_build.nim --push stag_hunt --bots
```

See README.md for full deploy workflow.
1 change: 1 addition & 0 deletions CLAUDE.md
20 changes: 15 additions & 5 deletions stag_hunt/players/coordinator/coordinator.nim
Original file line number Diff line number Diff line change
Expand Up @@ -771,13 +771,17 @@ proc addQueryParam(url, key, value: string): string =
result.add('=')
result.add(value.queryEscape())

proc connectUrl(address, url, name: string, port: int): string =
proc connectUrl(address, url, name, token: string, port, slot: int): string =
## Builds the player websocket URL.
if url.len > 0:
result = url.withPath(CoordinatorWebSocketPath)
else:
result = "ws://" & address & ":" & $port & CoordinatorWebSocketPath
result = result.addQueryParam("name", name)
if slot >= 0:
result = result.addQueryParam("slot", $slot)
if token.len > 0:
result = result.addQueryParam("token", token)

proc initBot(): Bot =
result.selfFound = false
Expand Down Expand Up @@ -826,10 +830,12 @@ proc runBot(
address = DefaultHost,
port = CoordinatorDefaultPort,
url = "",
name = "coordinator"
name = "coordinator",
token = "",
slot = -1
) =
## Connects coordinator to Stag Hunt and runs the coalition policy.
let endpoint = connectUrl(address, url, name, port)
let endpoint = connectUrl(address, url, name, token, port, slot)
while true:
try:
echo "coordinator connecting to ", endpoint
Expand All @@ -852,8 +858,10 @@ when isMainModule:
var
address = DefaultHost
port = CoordinatorDefaultPort
url = ""
url = getEnv("COGAMES_ENGINE_WS_URL")
name = "coordinator"
token = ""
slot = -1

for kind, key, value in getopt():
case kind
Expand All @@ -863,11 +871,13 @@ when isMainModule:
of "port": port = parseInt(value)
of "url": url = value
of "name": name = value
of "token": token = value
of "slot": slot = parseInt(value)
else:
raise newException(ValueError, "Unknown option: --" & key)
of cmdArgument, cmdShortOption:
raise newException(ValueError, "Unexpected argument: " & key)
of cmdEnd:
discard

runBot(address, port, url, name)
runBot(address, port, url, name, token, slot)
32 changes: 19 additions & 13 deletions stag_hunt/players/nearest_hunter/nearest_hunter.nim
Original file line number Diff line number Diff line change
Expand Up @@ -666,13 +666,17 @@ proc addQueryParam(url, key, value: string): string =
result.add('=')
result.add(value.queryEscape())

proc connectUrl(address, url, name: string, port: int): string =
proc connectUrl(address, url, name, token: string, port, slot: int): string =
## Builds the player websocket URL.
if url.len > 0:
result = url.withPath(WebSocketPath)
else:
result = "ws://" & address & ":" & $port & WebSocketPath
result = result.addQueryParam("name", name)
if slot >= 0:
result = result.addQueryParam("slot", $slot)
if token.len > 0:
result = result.addQueryParam("token", token)

proc initBot(): Bot =
## Creates a fresh nearest-hunter bot state.
Expand Down Expand Up @@ -723,10 +727,12 @@ proc runBot(
address = DefaultHost,
port = DefaultPort,
url = "",
name = "nearest_hunter"
name = "nearest_hunter",
token = "",
slot = -1
) =
## Connects to a stag_hunt server and pursues the closest visible prey.
let endpoint = connectUrl(address, url, name, port)
let endpoint = connectUrl(address, url, name, token, port, slot)
while true:
try:
echo "nearest_hunter connecting to ", endpoint
Expand All @@ -749,26 +755,26 @@ when isMainModule:
var
address = DefaultHost
port = DefaultPort
url = ""
url = getEnv("COGAMES_ENGINE_WS_URL")
name = "nearest_hunter"
token = ""
slot = -1

for kind, key, value in getopt():
case kind
of cmdLongOption:
case key
of "address":
address = value
of "port":
port = parseInt(value)
of "url":
url = value
of "name":
name = value
of "address": address = value
of "port": port = parseInt(value)
of "url": url = value
of "name": name = value
of "token": token = value
of "slot": slot = parseInt(value)
else:
raise newException(ValueError, "Unknown option: --" & key)
of cmdArgument, cmdShortOption:
raise newException(ValueError, "Unexpected argument: " & key)
of cmdEnd:
discard

runBot(address, port, url, name)
runBot(address, port, url, name, token, slot)
35 changes: 19 additions & 16 deletions stag_hunt/players/rabbiteer/rabbiteer.nim
Original file line number Diff line number Diff line change
Expand Up @@ -493,16 +493,17 @@ proc addQueryParam(url, key, value: string): string =
result.add('=')
result.add(value.queryEscape())

proc connectUrl(
address, url, name: string,
port: int
): string =
proc connectUrl(address, url, name, token: string, port, slot: int): string =
## Builds the player websocket URL for Stag Hunt.
if url.len > 0:
result = url.withPath(WebSocketPath)
else:
result = "ws://" & address & ":" & $port & WebSocketPath
result = result.addQueryParam("name", name)
if slot >= 0:
result = result.addQueryParam("slot", $slot)
if token.len > 0:
result = result.addQueryParam("token", token)

proc initBot(): Bot =
## Creates a fresh rabbiteer bot state.
Expand Down Expand Up @@ -553,10 +554,12 @@ proc runBot(
address = DefaultHost,
port = DefaultPort,
url = "",
name = "rabbiteer"
name = "rabbiteer",
token = "",
slot = -1
) =
## Connects rabbiteer to Stag Hunt and chases visible rabbits forever.
let endpoint = connectUrl(address, url, name, port)
let endpoint = connectUrl(address, url, name, token, port, slot)
while true:
try:
echo "rabbiteer connecting to ", endpoint
Expand All @@ -580,26 +583,26 @@ when isMainModule:
var
address = DefaultHost
port = DefaultPort
url = ""
url = getEnv("COGAMES_ENGINE_WS_URL")
name = "rabbiteer"
token = ""
slot = -1

for kind, key, value in getopt():
case kind
of cmdLongOption:
case key
of "address":
address = value
of "port":
port = parseInt(value)
of "url":
url = value
of "name":
name = value
of "address": address = value
of "port": port = parseInt(value)
of "url": url = value
of "name": name = value
of "token": token = value
of "slot": slot = parseInt(value)
else:
raise newException(ValueError, "Unknown option: --" & key)
of cmdArgument, cmdShortOption:
raise newException(ValueError, "Unexpected argument: " & key)
of cmdEnd:
discard

runBot(address, port, url, name)
runBot(address, port, url, name, token, slot)
20 changes: 15 additions & 5 deletions stag_hunt/players/sidekick/sidekick.nim
Original file line number Diff line number Diff line change
Expand Up @@ -560,12 +560,16 @@ proc addQueryParam(url, key, value: string): string =
result.add('=')
result.add(value.queryEscape())

proc connectUrl(address, url, name: string, port: int): string =
proc connectUrl(address, url, name, token: string, port, slot: int): string =
if url.len > 0:
result = url.withPath(WebSocketPath)
else:
result = "ws://" & address & ":" & $port & WebSocketPath
result = result.addQueryParam("name", name)
if slot >= 0:
result = result.addQueryParam("slot", $slot)
if token.len > 0:
result = result.addQueryParam("token", token)

proc initBot(): Bot =
result.selfObjectId = -1
Expand Down Expand Up @@ -611,9 +615,11 @@ proc runBot(
address = DefaultHost,
port = DefaultPort,
url = "",
name = "sidekick"
name = "sidekick",
token = "",
slot = -1
) =
let endpoint = connectUrl(address, url, name, port)
let endpoint = connectUrl(address, url, name, token, port, slot)
while true:
try:
echo "sidekick connecting to ", endpoint
Expand All @@ -635,8 +641,10 @@ when isMainModule:
var
address = DefaultHost
port = DefaultPort
url = ""
url = getEnv("COGAMES_ENGINE_WS_URL")
name = "sidekick"
token = ""
slot = -1

for kind, key, value in getopt():
case kind
Expand All @@ -646,11 +654,13 @@ when isMainModule:
of "port": port = parseInt(value)
of "url": url = value
of "name": name = value
of "token": token = value
of "slot": slot = parseInt(value)
else:
raise newException(ValueError, "Unknown option: --" & key)
of cmdArgument, cmdShortOption:
raise newException(ValueError, "Unexpected argument: " & key)
of cmdEnd:
discard

runBot(address, port, url, name)
runBot(address, port, url, name, token, slot)
20 changes: 15 additions & 5 deletions stag_hunt/players/stag_hunter/stag_hunter.nim
Original file line number Diff line number Diff line change
Expand Up @@ -444,12 +444,16 @@ proc addQueryParam(url, key, value: string): string =
result.add('=')
result.add(value.queryEscape())

proc connectUrl(address, url, name: string, port: int): string =
proc connectUrl(address, url, name, token: string, port, slot: int): string =
if url.len > 0:
result = url.withPath(WebSocketPath)
else:
result = "ws://" & address & ":" & $port & WebSocketPath
result = result.addQueryParam("name", name)
if slot >= 0:
result = result.addQueryParam("slot", $slot)
if token.len > 0:
result = result.addQueryParam("token", token)

proc initBot(): Bot =
result.selfObjectId = -1
Expand Down Expand Up @@ -490,9 +494,11 @@ proc runBot(
address = DefaultHost,
port = DefaultPort,
url = "",
name = "stag_hunter"
name = "stag_hunter",
token = "",
slot = -1
) =
let endpoint = connectUrl(address, url, name, port)
let endpoint = connectUrl(address, url, name, token, port, slot)
while true:
try:
echo "stag_hunter connecting to ", endpoint
Expand All @@ -515,8 +521,10 @@ when isMainModule:
var
address = DefaultHost
port = DefaultPort
url = ""
url = getEnv("COGAMES_ENGINE_WS_URL")
name = "stag_hunter"
token = ""
slot = -1

for kind, key, value in getopt():
case kind
Expand All @@ -526,11 +534,13 @@ when isMainModule:
of "port": port = parseInt(value)
of "url": url = value
of "name": name = value
of "token": token = value
of "slot": slot = parseInt(value)
else:
raise newException(ValueError, "Unknown option: --" & key)
of cmdArgument, cmdShortOption:
raise newException(ValueError, "Unexpected argument: " & key)
of cmdEnd:
discard

runBot(address, port, url, name)
runBot(address, port, url, name, token, slot)
Loading