diff --git a/.github/workflows/test-go-agent.yaml b/.github/workflows/test-go-agent.yaml new file mode 100644 index 00000000..6d052e5f --- /dev/null +++ b/.github/workflows/test-go-agent.yaml @@ -0,0 +1,64 @@ +name: Test Go Starter Kit + +# Public repo: this runs on GitHub-hosted runners (ubuntu-latest), never the +# self-hosted homeserver runners, so untrusted fork PRs can't execute on our +# infrastructure. +# +# The engine's JSON schema is the source of truth for the wire protocol. This +# workflow validates the Go starter kit's fixtures against that schema, and is +# triggered whenever the schema changes — so a protocol update to the engine +# surfaces any drift in the Go kit immediately. + +on: + push: + branches: + - master + pull_request: + branches: + - master + paths: + - "agents/go/**" + - "engine/bomberland-engine/src/runtime-validation/validation.schema.json" + - ".github/workflows/test-go-agent.yaml" + workflow_dispatch: + +concurrency: + group: test-go-agent-${{ github.ref }} + cancel-in-progress: true + +env: + ENGINE_SCHEMA: engine/bomberland-engine/src/runtime-validation/validation.schema.json + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: stable + cache-dependency-path: agents/go/go.sum + + # The engine schema is the source of truth. Fail if the kit's bundled copy has + # drifted from it — that copy is what the Docker build and standalone clones use, + # since they don't have the engine tree available. + - name: Check bundled schema is in sync with engine + run: | + if ! cmp -s "$ENGINE_SCHEMA" agents/go/validation.schema.json; then + echo "::error::agents/go/validation.schema.json is out of sync with $ENGINE_SCHEMA" + echo "Re-sync with: cp $ENGINE_SCHEMA agents/go/validation.schema.json" + diff -u agents/go/validation.schema.json "$ENGINE_SCHEMA" || true + exit 1 + fi + + # Validate fixtures against the engine's live schema (not just the bundled copy), + # so fixture/protocol drift fails CI even before the copy is re-synced. + - name: Run Go tests against engine schema + working-directory: agents/go + env: + BOMBERLAND_SCHEMA_PATH: ${{ github.workspace }}/engine/bomberland-engine/src/runtime-validation/validation.schema.json + run: go test ./... -count=1 diff --git a/README.md b/README.md index c066de2f..8d9623b6 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ docker-compose up --abort-on-container-exit --force-recreate | Python3-gym-wrapper | [Link](https://github.com/CoderOneHQ/bomberland/tree/master/agents/python3) | Open AI Gym wrapper | ✅ | Coder One | | TypeScript | [Link](https://github.com/CoderOneHQ/bomberland/tree/master/agents/typescript) | Basic TypeScript starter | ✅ | Coder One | | TypeScript-fwd | [Link](https://github.com/CoderOneHQ/bomberland/tree/master/agents/typescript) | Includes example for using forward model simulator | ❌ | Coder One | -| Go | [Link](https://github.com/CoderOneHQ/bomberland/tree/master/agents/go) | Basic Go starter | ❌ | [dtitov](https://github.com/dtitov) | +| Go | [Link](https://github.com/CoderOneHQ/bomberland/tree/master/agents/go) | Basic Go starter | ✅ | [dtitov](https://github.com/dtitov) | | C++ | [Link](https://github.com/CoderOneHQ/bomberland/tree/master/agents/cpp) | Basic C++ starter | ✅ | [jfbogusz](https://github.com/jfbogusz) | | Rust | [Link](https://github.com/CoderOneHQ/bomberland/tree/master/agents/rust) | Basic Rust starter | ❌ | [K-JBoon](https://github.com/K-JBoon) | diff --git a/agents/go/bomberland/game_state_test.go b/agents/go/bomberland/game_state_test.go index 54ae1fdd..d5763f7e 100644 --- a/agents/go/bomberland/game_state_test.go +++ b/agents/go/bomberland/game_state_test.go @@ -4,14 +4,26 @@ import ( "encoding/json" "github.com/stretchr/testify/assert" "github.com/xeipuuv/gojsonschema" + "os" "path/filepath" "testing" ) func TestJSONSchema(t *testing.T) { - abs, err := filepath.Abs("../validation.schema.json") + // The engine's schema is the source of truth. CI points BOMBERLAND_SCHEMA_PATH at + // engine/bomberland-engine/src/runtime-validation/validation.schema.json so fixtures are + // validated against the live engine contract; the bundled copy is the fallback used by the + // Docker build and standalone clones (which don't have the engine tree available). + schemaPath := os.Getenv("BOMBERLAND_SCHEMA_PATH") + if schemaPath == "" { + schemaPath = "../validation.schema.json" + } + abs, err := filepath.Abs(schemaPath) assert.NoError(t, err) - schemaLoader := gojsonschema.NewReferenceLoader("file://" + abs) + // Validate against the ServerPacket union specifically. The document root only holds + // `definitions` (no root constraints), so referencing it directly would accept anything; + // the fragment pins validation to the real server->client packet shapes. + schemaLoader := gojsonschema.NewReferenceLoader("file://" + abs + "#/definitions/ServerPacket") tests := []struct { name string payload interface{} @@ -161,10 +173,10 @@ func createMockTickPacket(t *testing.T, tickNumber int, events interface{}) map[ func getMockState(t *testing.T) interface{} { var mockState interface{} err := json.Unmarshal([]byte(` -{"agents": {"a": {"agent_id": "a", "unit_ids": ["c", "e", "g"]}, "b": {"agent_id": "b", "unit_ids": ["d", "f", "h"]}}, "unit_state": {"c": {"coordinates": [3, 10], "hp": 3, "inventory": {"bombs": 3}, "blast_diameter": 3, "unit_id": "c", "agent_id": "a", "invulnerability": 0}, "d": {"coordinates": [11, 10], "hp": 3, "inventory": {"bombs": 3}, "blast_diameter": 3, "unit_id": "d", "agent_id": "b", "invulnerability": 0}, "e": {"coordinates": [1, 9], "hp": 3, "inventory": {"bombs": 3}, "blast_diameter": 3, "unit_id": "e", "agent_id": "a", "invulnerability": 0}, "f": {"coordinates": [13, 9], "hp": 3, "inventory": {"bombs": 3}, "blast_diameter": 3, "unit_id": "f", "agent_id": "b", "invulnerability": 0}, "g": {"coordinates": [12, 7], "hp": 3, "inventory": {"bombs": 3}, "blast_diameter": 3, "unit_id": "g", "agent_id": "a", "invulnerability": 0}, "h": {"coordinates": [2, 7], "hp": 3, "inventory": {"bombs": 3}, "blast_diameter": 3, "unit_id": "h", "agent_id": "b", "invulnerability": 0}}, "entities": [{"created": 0, "x": 11, "y": 7, "type": "m"}, {"created": 0, "x": 3, "y": 7, "type": "m"}, {"created": 0, "x": 10, "y": 11, "type": "m"}, {"created": 0, "x": 4, "y": 11, "type": "m"}, {"created": 0, "x": 11, "y": 2, "type": "m"}, {"created": 0, "x": 3, "y": 2, "type": "m"}, {"created": 0, "x": 4, "y": 8, "type": "m"}, {"created": 0, "x": 10, "y": 8, "type": "m"}, {"created": 0, "x": 14, "y": 14, "type": "m"}, {"created": 0, "x": 0, "y": 14, "type": "m"}, {"created": 0, "x": 13, "y": 13, "type": "m"}, {"created": 0, "x": 1, "y": 13, "type": "m"}, {"created": 0, "x": 14, "y": 0, "type": "m"}, {"created": 0, "x": 0, "y": 0, "type": "m"}, {"created": 0, "x": 2, "y": 3, "type": "m"}, {"created": 0, "x": 12, "y": 3, "type": "m"}, {"created": 0, "x": 3, "y": 14, "type": "m"}, {"created": 0, "x": 11, "y": 14, "type": "m"}, {"created": 0, "x": 0, "y": 1, "type": "m"}, {"created": 0, "x": 14, "y": 1, "type": "m"}, {"created": 0, "x": 5, "y": 2, "type": "m"}, +{"game_id": "dev-game", "agents": {"a": {"agent_id": "a", "unit_ids": ["c", "e", "g"]}, "b": {"agent_id": "b", "unit_ids": ["d", "f", "h"]}}, "unit_state": {"c": {"coordinates": [3, 10], "hp": 3, "inventory": {"bombs": 3}, "blast_diameter": 3, "unit_id": "c", "agent_id": "a", "invulnerable": 0, "stunned": 0}, "d": {"coordinates": [11, 10], "hp": 3, "inventory": {"bombs": 3}, "blast_diameter": 3, "unit_id": "d", "agent_id": "b", "invulnerable": 0, "stunned": 0}, "e": {"coordinates": [1, 9], "hp": 3, "inventory": {"bombs": 3}, "blast_diameter": 3, "unit_id": "e", "agent_id": "a", "invulnerable": 0, "stunned": 0}, "f": {"coordinates": [13, 9], "hp": 3, "inventory": {"bombs": 3}, "blast_diameter": 3, "unit_id": "f", "agent_id": "b", "invulnerable": 0, "stunned": 0}, "g": {"coordinates": [12, 7], "hp": 3, "inventory": {"bombs": 3}, "blast_diameter": 3, "unit_id": "g", "agent_id": "a", "invulnerable": 0, "stunned": 0}, "h": {"coordinates": [2, 7], "hp": 3, "inventory": {"bombs": 3}, "blast_diameter": 3, "unit_id": "h", "agent_id": "b", "invulnerable": 0, "stunned": 0}}, "entities": [{"created": 0, "x": 11, "y": 7, "type": "m"}, {"created": 0, "x": 3, "y": 7, "type": "m"}, {"created": 0, "x": 10, "y": 11, "type": "m"}, {"created": 0, "x": 4, "y": 11, "type": "m"}, {"created": 0, "x": 11, "y": 2, "type": "m"}, {"created": 0, "x": 3, "y": 2, "type": "m"}, {"created": 0, "x": 4, "y": 8, "type": "m"}, {"created": 0, "x": 10, "y": 8, "type": "m"}, {"created": 0, "x": 14, "y": 14, "type": "m"}, {"created": 0, "x": 0, "y": 14, "type": "m"}, {"created": 0, "x": 13, "y": 13, "type": "m"}, {"created": 0, "x": 1, "y": 13, "type": "m"}, {"created": 0, "x": 14, "y": 0, "type": "m"}, {"created": 0, "x": 0, "y": 0, "type": "m"}, {"created": 0, "x": 2, "y": 3, "type": "m"}, {"created": 0, "x": 12, "y": 3, "type": "m"}, {"created": 0, "x": 3, "y": 14, "type": "m"}, {"created": 0, "x": 11, "y": 14, "type": "m"}, {"created": 0, "x": 0, "y": 1, "type": "m"}, {"created": 0, "x": 14, "y": 1, "type": "m"}, {"created": 0, "x": 5, "y": 2, "type": "m"}, {"created": 0, "x": 9, "y": 2, "type": "m"}, {"created": 0, "x": 9, "y": 6, "type": "m"}, {"created": 0, "x": 5, "y": 6, "type": "m"}, {"created": 0, "x": 11, "y": 13, "type": "m"}, {"created": 0, "x": 3, "y": 13, "type": "m"}, {"created": 0, "x": 2, "y": 10, "type": "m"}, {"created": 0, "x": 12, "y": 10, "type": "m"}, {"created": 0, "x": 2, "y": 13, "type": "m"}, {"created": 0, "x": 12, "y": 13, "type": "m"}, {"created": 0, "x": 12, "y": 6, "type": "m"}, {"created": 0, "x": 2, "y": 6, "type": "m"}, {"created": 0, "x": 1, "y": 10, "type": "m"}, {"created": 0, "x": 13, "y": 10, "type": "m"}, {"created": 0, "x": 1, "y": 6, "type": "m"}, {"created": 0, "x": 13, "y": 6, "type": "m"}, {"created": 0, "x": 9, "y": 12, "type": "m"}, {"created": 0, "x": 5, "y": 12, "type": "m"}, {"created": 0, "x": 1, "y": 0, "type": "m"}, {"created": 0, "x": 13, "y": 0, "type": "m"}, {"created": 0, "x": 14, "y": 2, "type": "m"}, {"created": 0, "x": 0, "y": 2, "type": "m"}, {"created": 0, "x": 10, "y": 1, "type": "m"}, {"created": 0, "x": 4, "y": 1, "type": "m"}, {"created": 0, "x": 11, "y": 8, "type": "m"}, {"created": 0, "x": 3, "y": 8, "type": "m"}, {"created": 0, "x": 0, "y": 6, "type": "m"}, {"created": 0, "x": 14, "y": 6, "type": "m"}, {"created": 0, "x": 12, "y": 5, "type": "m"}, {"created": 0, "x": 2, "y": 5, "type": "m"}, {"created": 0, "x": 1, "y": 3, "type": "w", "hp": 1}, {"created": 0, "x": 13, "y": 3, "type": "w", "hp": 1}, {"created": 0, "x": 1, "y": 8, "type": "w", "hp": 1}, {"created": 0, "x": 13, "y": 8, "type": "w", "hp": 1}, {"created": 0, "x": 0, "y": 8, "type": "w", "hp": 1}, {"created": 0, "x": 14, "y": 8, "type": "w", "hp": 1}, {"created": 0, "x": 6, "y": 3, "type": "w", "hp": 1}, {"created": 0, "x": 8, "y": 3, "type": "w", "hp": 1}, {"created": 0, "x": 6, "y": 1, "type": "w", "hp": 1}, {"created": 0, "x": 8, "y": 1, "type": "w", "hp": 1}, {"created": 0, "x": 9, "y": 1, "type": "w", "hp": 1}, {"created": 0, "x": 5, "y": 1, "type": "w", "hp": 1}, {"created": 0, "x": 13, "y": 14, "type": "w", "hp": 1}, {"created": 0, "x": 1, "y": 14, "type": "w", "hp": 1}, {"created": 0, "x": 5, "y": 9, "type": "w", "hp": 1}, {"created": 0, "x": 9, "y": 9, "type": "w", "hp": 1}, {"created": 0, "x": 3, "y": 1, "type": "w", "hp": 1}, {"created": 0, "x": 11, "y": 1, "type": "w", "hp": 1}, {"created": 0, "x": 13, "y": 11, "type": "w", "hp": 1}, {"created": 0, "x": 1, "y": 11, "type": "w", "hp": 1}, {"created": 0, "x": 8, "y": 12, "type": "w", "hp": 1}, {"created": 0, "x": 6, "y": 12, "type": "w", "hp": 1}, {"created": 0, "x": 14, "y": 3, "type": "w", "hp": 1}, {"created": 0, "x": 0, "y": 3, "type": "w", - "hp": 1}, {"created": 0, "x": 10, "y": 13, "type": "w", "hp": 1}, {"created": 0, "x": 4, "y": 13, "type": "w", "hp": 1}, {"created": 0, "x": 14, "y": 13, "type": "w", "hp": 1}, {"created": 0, "x": 0, "y": 13, "type": "w", "hp": 1}, {"created": 0, "x": 10, "y": 14, "type": "w", "hp": 1}, {"created": 0, "x": 4, "y": 14, "type": "w", "hp": 1}, {"created": 0, "x": 5, "y": 3, "type": "w", "hp": 1}, {"created": 0, "x": 9, "y": 3, "type": "w", "hp": 1}, {"created": 0, "x": 2, "y": 11, "type": "w", "hp": 1}, {"created": 0, "x": 12, "y": 11, "type": "w", "hp": 1}, {"created": 0, "x": 10, "y": 2, "type": "w", "hp": 1}, {"created": 0, "x": 4, "y": 2, "type": "w", "hp": 1}, {"created": 0, "x": 9, "y": 0, "type": "w", "hp": 1}, {"created": 0, "x": 5, "y": 0, "type": "w", "hp": 1}, {"created": 0, "x": 1, "y": 4, "type": "w", "hp": 1}, {"created": 0, "x": 13, "y": 4, "type": "w", "hp": 1}, {"created": 0, "x": 1, "y": 2, "type": "w", "hp": 1}, {"created": 0, "x": 13, "y": 2, "type": "w", "hp": 1}, {"created": 0, "x": 8, "y": 5, "type": "w", "hp": 1}, {"created": 0, "x": 6, "y": 5, "type": "w", "hp": 1}, {"created": 0, "x": 6, "y": 2, "type": "w", "hp": 1}, {"created": 0, "x": 8, "y": 2, "type": "w", "hp": 1}, {"created": 0, "x": 0, "y": 4, "type": "w", "hp": 1}, {"created": 0, "x": 14, "y": 4, "type": "w", "hp": 1}, {"created": 0, "x": 13, "y": 1, "type": "w", "hp": 1}, {"created": 0, "x": 1, "y": 1, "type": "w", "hp": 1}, {"created": 0, "x": 5, "y": 14, "type": "w", "hp": 1}, {"created": 0, "x": 9, "y": 14, "type": "w", "hp": 1}, {"created": 0, "x": 14, "y": 11, "type": "w", "hp": 1}, {"created": 0, "x": 0, "y": 11, "type": "w", "hp": 1}, {"created": 0, "x": 5, "y": 5, "type": "w", "hp": 1}, {"created": 0, "x": 9, "y": 5, "type": "w", "hp": 1}, {"created": 0, "x": 10, "y": 4, "type": "o", "hp": 3}, {"created": 0, "x": 4, "y": 4, "type": "o", "hp": 3}, {"created": 0, "x": 9, "y": 11, "type": "o", "hp": 3}, {"created": 0, "x": 5, "y": 11, "type": "o", "hp": 3}, {"created": 0, "x": 4, "y": 12, "type": "o", "hp": 3}, {"created": 0, "x": 10, "y": 12, "type": "o", "hp": 3}, {"created": 0, "x": 2, "y": 1, "type": "o", "hp": 3}, {"created": 0, "x": 12, "y": 1, "type": "o", "hp": 3}, {"created": 0, "x": 10, "y": 6, "type": "o", "hp": 3}, {"created": 0, "x": 4, "y": 6, "type": "o", "hp": 3}, {"created": 0, "x": 11, "y": 3, "type": "o", "hp": 3}, {"created": 0, "x": 3, "y": 3, "type": "o", "hp": 3}, {"created": 0, "x": 2, "y": 4, "type": "o", "hp": 3}, {"created": 0, "x": 12, "y": 4, "type": "o", "hp": 3}], "world": {"width": 15, "height": 15}, "tick": 0, "config": {"tick_rate_hz": 10, "game_duration_ticks": 1, "fire_spawn_interval_ticks": 5}, "connection": {"id": 2, "role": "agent", "agent_id": "b"}} + "hp": 1}, {"created": 0, "x": 10, "y": 13, "type": "w", "hp": 1}, {"created": 0, "x": 4, "y": 13, "type": "w", "hp": 1}, {"created": 0, "x": 14, "y": 13, "type": "w", "hp": 1}, {"created": 0, "x": 0, "y": 13, "type": "w", "hp": 1}, {"created": 0, "x": 10, "y": 14, "type": "w", "hp": 1}, {"created": 0, "x": 4, "y": 14, "type": "w", "hp": 1}, {"created": 0, "x": 5, "y": 3, "type": "w", "hp": 1}, {"created": 0, "x": 9, "y": 3, "type": "w", "hp": 1}, {"created": 0, "x": 2, "y": 11, "type": "w", "hp": 1}, {"created": 0, "x": 12, "y": 11, "type": "w", "hp": 1}, {"created": 0, "x": 10, "y": 2, "type": "w", "hp": 1}, {"created": 0, "x": 4, "y": 2, "type": "w", "hp": 1}, {"created": 0, "x": 9, "y": 0, "type": "w", "hp": 1}, {"created": 0, "x": 5, "y": 0, "type": "w", "hp": 1}, {"created": 0, "x": 1, "y": 4, "type": "w", "hp": 1}, {"created": 0, "x": 13, "y": 4, "type": "w", "hp": 1}, {"created": 0, "x": 1, "y": 2, "type": "w", "hp": 1}, {"created": 0, "x": 13, "y": 2, "type": "w", "hp": 1}, {"created": 0, "x": 8, "y": 5, "type": "w", "hp": 1}, {"created": 0, "x": 6, "y": 5, "type": "w", "hp": 1}, {"created": 0, "x": 6, "y": 2, "type": "w", "hp": 1}, {"created": 0, "x": 8, "y": 2, "type": "w", "hp": 1}, {"created": 0, "x": 0, "y": 4, "type": "w", "hp": 1}, {"created": 0, "x": 14, "y": 4, "type": "w", "hp": 1}, {"created": 0, "x": 13, "y": 1, "type": "w", "hp": 1}, {"created": 0, "x": 1, "y": 1, "type": "w", "hp": 1}, {"created": 0, "x": 5, "y": 14, "type": "w", "hp": 1}, {"created": 0, "x": 9, "y": 14, "type": "w", "hp": 1}, {"created": 0, "x": 14, "y": 11, "type": "w", "hp": 1}, {"created": 0, "x": 0, "y": 11, "type": "w", "hp": 1}, {"created": 0, "x": 5, "y": 5, "type": "w", "hp": 1}, {"created": 0, "x": 9, "y": 5, "type": "w", "hp": 1}, {"created": 0, "x": 10, "y": 4, "type": "o", "hp": 3}, {"created": 0, "x": 4, "y": 4, "type": "o", "hp": 3}, {"created": 0, "x": 9, "y": 11, "type": "o", "hp": 3}, {"created": 0, "x": 5, "y": 11, "type": "o", "hp": 3}, {"created": 0, "x": 4, "y": 12, "type": "o", "hp": 3}, {"created": 0, "x": 10, "y": 12, "type": "o", "hp": 3}, {"created": 0, "x": 2, "y": 1, "type": "o", "hp": 3}, {"created": 0, "x": 12, "y": 1, "type": "o", "hp": 3}, {"created": 0, "x": 10, "y": 6, "type": "o", "hp": 3}, {"created": 0, "x": 4, "y": 6, "type": "o", "hp": 3}, {"created": 0, "x": 11, "y": 3, "type": "o", "hp": 3}, {"created": 0, "x": 3, "y": 3, "type": "o", "hp": 3}, {"created": 0, "x": 2, "y": 4, "type": "o", "hp": 3}, {"created": 0, "x": 12, "y": 4, "type": "o", "hp": 3}, {"created": 10, "x": 7, "y": 7, "type": "fp", "expires": 50, "hp": 1}, {"created": 10, "x": 6, "y": 7, "type": "bp", "expires": 50, "hp": 1}], "world": {"width": 15, "height": 15}, "tick": 0, "config": {"tick_rate_hz": 10, "game_duration_ticks": 1, "fire_spawn_interval_ticks": 5}, "connection": {"id": 2, "role": "agent", "agent_id": "b"}} `), &mockState) assert.NoError(t, err) return mockState @@ -198,7 +210,7 @@ func getMockTickExpiredPacket(t *testing.T) map[string]interface{} { func getMockUnitStatePayload(t *testing.T) interface{} { var mockUnitStatePayload interface{} err := json.Unmarshal([]byte(` -{"coordinates": [3, 10], "hp": 3, "inventory": {"bombs": 2}, "blast_diameter": 3, "unit_id": "c", "agent_id": "a", "invulnerability": 0} +{"coordinates": [3, 10], "hp": 3, "inventory": {"bombs": 2}, "blast_diameter": 3, "unit_id": "c", "agent_id": "a", "invulnerable": 0, "stunned": 0} `), &mockUnitStatePayload) assert.NoError(t, err) return mockUnitStatePayload diff --git a/agents/go/main.go b/agents/go/main.go index dc0459c3..5ec2c904 100644 --- a/agents/go/main.go +++ b/agents/go/main.go @@ -69,7 +69,7 @@ func tickHandler(tickNumber float64, state map[string]interface{}) (err error) { } logrus.WithField("action", "detonate").Info() x, y := getBombToDetonate(unitID, state) - if x > 0 && y > 0 { + if x >= 0 && y >= 0 { err = gameState.SendDetonate(x, y, unitID) if err != nil { logrus.Error(err) @@ -84,7 +84,9 @@ func tickHandler(tickNumber float64, state map[string]interface{}) (err error) { func getBombToDetonate(unitID string, state map[string]interface{}) (float64, float64) { for _, iEntity := range state["entities"].([]interface{}) { entity := iEntity.(map[string]interface{}) - if entity["owner_unit_id"] == unitID && entity["type"] == "b" { + // Bombs are type "b"; the placing unit is identified by "unit_id" + // (the engine renamed this from the old "owner_unit_id"). + if entity["unit_id"] == unitID && entity["type"] == "b" { return entity["x"].(float64), entity["y"].(float64) } } diff --git a/agents/go/main_test.go b/agents/go/main_test.go new file mode 100644 index 00000000..7a5ef1d6 --- /dev/null +++ b/agents/go/main_test.go @@ -0,0 +1,54 @@ +package main + +import ( + "encoding/json" + "testing" +) + +// stateWithEntities builds a minimal tick state whose entities list matches the +// current engine wire shape (bombs identified by "unit_id", type "b"). +func stateWithEntities(t *testing.T, entitiesJSON string) map[string]interface{} { + t.Helper() + var entities []interface{} + if err := json.Unmarshal([]byte(entitiesJSON), &entities); err != nil { + t.Fatalf("failed to build entities: %v", err) + } + return map[string]interface{}{"entities": entities} +} + +func TestGetBombToDetonate_FindsOwnBombByUnitID(t *testing.T) { + // A bomb placed by unit "c" plus an opponent's bomb and a non-bomb entity. + state := stateWithEntities(t, `[ + {"type": "w", "x": 1, "y": 1, "created": 0, "hp": 1}, + {"type": "b", "x": 5, "y": 6, "unit_id": "d", "agent_id": "b", "created": 0}, + {"type": "b", "x": 3, "y": 4, "unit_id": "c", "agent_id": "a", "created": 0} + ]`) + + x, y := getBombToDetonate("c", state) + if x != 3 || y != 4 { + t.Fatalf("expected bomb for unit c at (3,4), got (%v,%v)", x, y) + } +} + +func TestGetBombToDetonate_ReturnsSentinelWhenNoOwnedBomb(t *testing.T) { + state := stateWithEntities(t, `[ + {"type": "b", "x": 5, "y": 6, "unit_id": "d", "agent_id": "b", "created": 0} + ]`) + + x, y := getBombToDetonate("c", state) + if x != -1 || y != -1 { + t.Fatalf("expected sentinel (-1,-1) when unit owns no bomb, got (%v,%v)", x, y) + } +} + +func TestGetBombToDetonate_FindsBombOnBoardEdge(t *testing.T) { + // A bomb at the origin must still be detonatable (regression for the old x>0 && y>0 guard). + state := stateWithEntities(t, `[ + {"type": "b", "x": 0, "y": 0, "unit_id": "c", "agent_id": "a", "created": 0} + ]`) + + x, y := getBombToDetonate("c", state) + if x != 0 || y != 0 { + t.Fatalf("expected edge bomb at (0,0), got (%v,%v)", x, y) + } +} diff --git a/agents/go/validation.schema.json b/agents/go/validation.schema.json index 2b0ecba7..7cc0aa25 100644 --- a/agents/go/validation.schema.json +++ b/agents/go/validation.schema.json @@ -2,1867 +2,947 @@ "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { "IHashMapObject": { + "type": "object", "additionalProperties": { "type": "string" - }, - "type": "object" + } }, "ValidAdminPacket": { + "$ref": "#/definitions/AdminPacket" + }, + "AdminPacket": { "anyOf": [ { - "additionalProperties": false, - "properties": { - "type": { - "enum": [ - "request_tick" - ], - "type": "string" - } - }, - "required": [ - "type" - ], - "type": "object" + "$ref": "#/definitions/EvaluateNextStatePacket" + }, + { + "$ref": "#/definitions/RequestTickPacket" }, { - "additionalProperties": false, + "$ref": "#/definitions/RequestGameReset" + } + ] + }, + "EvaluateNextStatePacket": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "evaluate_next_state" + }, + "sequence_id": { + "type": "number" + }, + "state": { + "type": "object", "properties": { - "actions": { - "items": { - "additionalProperties": false, - "properties": { - "action": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "move": { - "enum": [ - "down", - "left", - "right", - "up" - ], - "type": "string" - }, - "type": { - "enum": [ - "move" - ], - "type": "string" - }, - "unit_id": { - "type": "string" - } - }, - "required": [ - "move", - "type", - "unit_id" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "type": { - "enum": [ - "bomb" - ], - "type": "string" - }, - "unit_id": { - "type": "string" - } - }, - "required": [ - "type", - "unit_id" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "coordinates": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "type": { - "enum": [ - "detonate" - ], - "type": "string" - }, - "unit_id": { - "type": "string" - } - }, - "required": [ - "coordinates", - "type", - "unit_id" - ], - "type": "object" - } - ] - }, - "agent_id": { - "type": "string" - } - }, - "required": [ - "action", - "agent_id" - ], - "type": "object" - }, - "type": "array" + "game_id": { + "type": "string" }, - "sequence_id": { + "tick": { "type": "number" }, - "state": { - "additionalProperties": false, + "agents": { + "$ref": "#/definitions/IAgentHashMap" + }, + "unit_state": { + "$ref": "#/definitions/IUnitStateHashMap" + }, + "entities": { + "type": "array", + "items": { + "$ref": "#/definitions/IEntity" + } + }, + "world": { + "type": "object", "properties": { - "agents": { - "additionalProperties": { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "unit_ids": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "agent_id", - "unit_ids" - ], - "type": "object" - }, - "type": "object" - }, - "config": { - "additionalProperties": false, - "properties": { - "fire_spawn_interval_ticks": { - "type": "number" - }, - "game_duration_ticks": { - "type": "number" - }, - "tick_rate_hz": { - "type": "number" - } - }, - "required": [ - "fire_spawn_interval_ticks", - "game_duration_ticks", - "tick_rate_hz" - ], - "type": "object" - }, - "entities": { - "items": { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "blast_diameter": { - "type": "number" - }, - "created": { - "type": "number" - }, - "expires": { - "type": "number" - }, - "hp": { - "type": "number" - }, - "type": { - "enum": [ - "a", - "b", - "bp", - "m", - "o", - "t", - "w", - "x" - ], - "type": "string" - }, - "unit_id": { - "type": "string" - }, - "x": { - "type": "number" - }, - "y": { - "type": "number" - } - }, - "required": [ - "created", - "type", - "x", - "y" - ], - "type": "object" - }, - "type": "array" + "width": { + "type": "number" }, - "tick": { + "height": { + "type": "number" + } + }, + "required": [ + "width", + "height" + ], + "additionalProperties": false + }, + "config": { + "type": "object", + "properties": { + "game_duration_ticks": { "type": "number" }, - "unit_state": { - "additionalProperties": { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "blast_diameter": { - "type": "number" - }, - "coordinates": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "hp": { - "type": "number" - }, - "inventory": { - "additionalProperties": false, - "properties": { - "bombs": { - "type": "number" - } - }, - "required": [ - "bombs" - ], - "type": "object" - }, - "invulnerability": { - "type": "number" - }, - "unit_id": { - "type": "string" - } - }, - "required": [ - "agent_id", - "blast_diameter", - "coordinates", - "hp", - "inventory", - "invulnerability", - "unit_id" - ], - "type": "object" - }, - "type": "object" + "tick_rate_hz": { + "type": "number" }, - "world": { - "additionalProperties": false, - "properties": { - "height": { - "type": "number" - }, - "width": { - "type": "number" - } - }, - "required": [ - "height", - "width" - ], - "type": "object" + "fire_spawn_interval_ticks": { + "type": "number" } }, "required": [ - "agents", - "config", - "entities", - "tick", - "unit_state", - "world" + "game_duration_ticks", + "tick_rate_hz", + "fire_spawn_interval_ticks" ], - "type": "object" + "additionalProperties": false + } + }, + "required": [ + "game_id", + "tick", + "agents", + "unit_state", + "entities", + "world", + "config" + ], + "additionalProperties": false + }, + "actions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "agent_id": { + "type": "string" + }, + "action": { + "$ref": "#/definitions/AgentPacket" + } }, - "type": { - "enum": [ - "evaluate_next_state" - ], - "type": "string" + "required": [ + "agent_id", + "action" + ], + "additionalProperties": false + } + } + }, + "required": [ + "type", + "sequence_id", + "state", + "actions" + ], + "additionalProperties": false + }, + "IAgentHashMap": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/IAgentState" + } + }, + "IAgentState": { + "type": "object", + "properties": { + "agent_id": { + "type": "string" + }, + "unit_ids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "agent_id", + "unit_ids" + ], + "additionalProperties": false + }, + "IUnitStateHashMap": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/IUnitState" + } + }, + "IUnitState": { + "type": "object", + "properties": { + "agent_id": { + "type": "string" + }, + "unit_id": { + "type": "string" + }, + "coordinates": { + "type": "array", + "items": { + "type": "number" + }, + "minItems": 2, + "maxItems": 2 + }, + "inventory": { + "type": "object", + "properties": { + "bombs": { + "type": "number" } }, "required": [ - "actions", - "sequence_id", - "state", - "type" + "bombs" ], - "type": "object" + "additionalProperties": false + }, + "blast_diameter": { + "type": "number" + }, + "hp": { + "type": "number" + }, + "invulnerable": { + "type": "number" + }, + "stunned": { + "type": "number" + } + }, + "required": [ + "agent_id", + "unit_id", + "coordinates", + "inventory", + "blast_diameter", + "hp", + "invulnerable", + "stunned" + ], + "additionalProperties": false + }, + "IEntity": { + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/EntityType" + }, + "hp": { + "type": "number" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + }, + "unit_id": { + "type": "string" + }, + "agent_id": { + "type": "string" + }, + "created": { + "type": "number" + }, + "expires": { + "type": "number" + }, + "blast_diameter": { + "type": "number" + } + }, + "required": [ + "type", + "x", + "y", + "created" + ], + "additionalProperties": false + }, + "EntityType": { + "type": "string", + "enum": [ + "a", + "b", + "x", + "bp", + "fp", + "m", + "o", + "t", + "w" + ] + }, + "AgentPacket": { + "anyOf": [ + { + "$ref": "#/definitions/AgentDetonatePacket" + }, + { + "$ref": "#/definitions/AgentMovePacket" + }, + { + "$ref": "#/definitions/AgentBombPacket" + } + ] + }, + "AgentDetonatePacket": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "detonate" + }, + "coordinates": { + "type": "array", + "items": { + "type": "number" + }, + "minItems": 2, + "maxItems": 2 + }, + "unit_id": { + "type": "string" + } + }, + "required": [ + "type", + "coordinates", + "unit_id" + ], + "additionalProperties": false + }, + "AgentMovePacket": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "move" + }, + "move": { + "$ref": "#/definitions/UnitMove" + }, + "unit_id": { + "type": "string" } + }, + "required": [ + "type", + "move", + "unit_id" + ], + "additionalProperties": false + }, + "UnitMove": { + "type": "string", + "enum": [ + "up", + "down", + "left", + "right" ] }, + "AgentBombPacket": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "bomb" + }, + "unit_id": { + "type": "string" + } + }, + "required": [ + "type", + "unit_id" + ], + "additionalProperties": false + }, + "RequestTickPacket": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "request_tick" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "RequestGameReset": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "request_game_reset" + }, + "world_seed": { + "type": "number" + }, + "prng_seed": { + "type": "number" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, "ValidAgentPacket": { + "$ref": "#/definitions/AgentPacket" + }, + "ValidServerPacket": { + "$ref": "#/definitions/ServerPacket" + }, + "ServerPacket": { "anyOf": [ { - "additionalProperties": false, + "$ref": "#/definitions/GameStatePacket" + }, + { + "$ref": "#/definitions/GameTickPacket" + }, + { + "$ref": "#/definitions/EndGameStatePacket" + }, + { + "$ref": "#/definitions/InfoPacket" + }, + { + "$ref": "#/definitions/NextGameStatePacket" + } + ] + }, + "GameStatePacket": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "game_state" + }, + "payload": { + "$ref": "#/definitions/IGameState" + } + }, + "required": [ + "type", + "payload" + ], + "additionalProperties": false + }, + "IGameState": { + "type": "object", + "properties": { + "game_id": { + "type": "string" + }, + "tick": { + "type": "number" + }, + "agents": { + "$ref": "#/definitions/IAgentHashMap" + }, + "unit_state": { + "$ref": "#/definitions/IUnitStateHashMap" + }, + "entities": { + "type": "array", + "items": { + "$ref": "#/definitions/IEntity" + } + }, + "world": { + "type": "object", "properties": { - "move": { - "enum": [ - "down", - "left", - "right", - "up" - ], - "type": "string" - }, - "type": { - "enum": [ - "move" - ], - "type": "string" + "width": { + "type": "number" }, - "unit_id": { - "type": "string" + "height": { + "type": "number" } }, "required": [ - "move", - "type", - "unit_id" + "width", + "height" ], - "type": "object" + "additionalProperties": false }, - { - "additionalProperties": false, + "config": { + "type": "object", "properties": { - "type": { - "enum": [ - "bomb" - ], - "type": "string" + "game_duration_ticks": { + "type": "number" }, - "unit_id": { - "type": "string" + "tick_rate_hz": { + "type": "number" + }, + "fire_spawn_interval_ticks": { + "type": "number" } }, "required": [ - "type", - "unit_id" + "game_duration_ticks", + "tick_rate_hz", + "fire_spawn_interval_ticks" ], - "type": "object" + "additionalProperties": false }, - { - "additionalProperties": false, + "connection": { + "type": "object", "properties": { - "coordinates": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" + "id": { + "type": "number" }, - "type": { - "enum": [ - "detonate" - ], - "type": "string" + "role": { + "$ref": "#/definitions/GameRole" }, - "unit_id": { - "type": "string" + "agent_id": { + "type": [ + "string", + "null" + ] } }, "required": [ - "coordinates", - "type", - "unit_id" + "id", + "role", + "agent_id" ], - "type": "object" + "additionalProperties": false } + }, + "required": [ + "game_id", + "tick", + "agents", + "unit_state", + "entities", + "world", + "config", + "connection" + ], + "additionalProperties": false + }, + "GameRole": { + "type": "string", + "enum": [ + "admin", + "agent", + "spectator" ] }, - "ValidServerPacket": { + "GameTickPacket": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "tick" + }, + "payload": { + "$ref": "#/definitions/IGameTick" + } + }, + "required": [ + "type", + "payload" + ], + "additionalProperties": false + }, + "IGameTick": { + "type": "object", + "properties": { + "events": { + "type": "array", + "items": { + "$ref": "#/definitions/GameEvent" + } + }, + "tick": { + "type": "number" + } + }, + "required": [ + "events", + "tick" + ], + "additionalProperties": false + }, + "GameEvent": { "anyOf": [ { - "additionalProperties": false, + "$ref": "#/definitions/IAgentActionEvent" + }, + { + "$ref": "#/definitions/IEntitySpawnedEvent" + }, + { + "$ref": "#/definitions/IEntityExpiredEvent" + }, + { + "$ref": "#/definitions/IAgentStateEvent" + }, + { + "$ref": "#/definitions/IEntityStateEvent" + } + ] + }, + "IAgentActionEvent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "unit" + }, + "agent_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/AgentPacket" + } + }, + "required": [ + "type", + "agent_id", + "data" + ], + "additionalProperties": false + }, + "IEntitySpawnedEvent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "entity_spawned" + }, + "data": { + "$ref": "#/definitions/IEntity" + } + }, + "required": [ + "type", + "data" + ], + "additionalProperties": false + }, + "IEntityExpiredEvent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "entity_expired" + }, + "data": { + "type": "array", + "items": { + "type": "number" + }, + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "type", + "data" + ], + "additionalProperties": false + }, + "IAgentStateEvent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "unit_state" + }, + "data": { + "$ref": "#/definitions/IUnitState" + } + }, + "required": [ + "type", + "data" + ], + "additionalProperties": false + }, + "IEntityStateEvent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "entity_state" + }, + "coordinates": { + "type": "array", + "items": { + "type": "number" + }, + "minItems": 2, + "maxItems": 2 + }, + "updated_entity": { + "$ref": "#/definitions/IEntity" + } + }, + "required": [ + "type", + "coordinates", + "updated_entity" + ], + "additionalProperties": false + }, + "EndGameStatePacket": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "endgame_state" + }, + "payload": { + "$ref": "#/definitions/IEndGameState" + } + }, + "required": [ + "type", + "payload" + ], + "additionalProperties": false + }, + "IEndGameState": { + "type": "object", + "properties": { + "initial_state": { + "type": "object", "properties": { - "payload": { - "additionalProperties": false, + "game_id": { + "type": "string" + }, + "tick": { + "type": "number" + }, + "agents": { + "$ref": "#/definitions/IAgentHashMap" + }, + "unit_state": { + "$ref": "#/definitions/IUnitStateHashMap" + }, + "entities": { + "type": "array", + "items": { + "$ref": "#/definitions/IEntity" + } + }, + "world": { + "type": "object", "properties": { - "events": { - "items": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "data": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "move": { - "enum": [ - "down", - "left", - "right", - "up" - ], - "type": "string" - }, - "type": { - "enum": [ - "move" - ], - "type": "string" - }, - "unit_id": { - "type": "string" - } - }, - "required": [ - "move", - "type", - "unit_id" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "type": { - "enum": [ - "bomb" - ], - "type": "string" - }, - "unit_id": { - "type": "string" - } - }, - "required": [ - "type", - "unit_id" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "coordinates": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "type": { - "enum": [ - "detonate" - ], - "type": "string" - }, - "unit_id": { - "type": "string" - } - }, - "required": [ - "coordinates", - "type", - "unit_id" - ], - "type": "object" - } - ] - }, - "type": { - "enum": [ - "unit" - ], - "type": "string" - } - }, - "required": [ - "agent_id", - "data", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "data": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "type": { - "enum": [ - "entity_expired" - ], - "type": "string" - } - }, - "required": [ - "data", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "coordinates": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "type": { - "enum": [ - "entity_state" - ], - "type": "string" - }, - "updated_entity": { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "blast_diameter": { - "type": "number" - }, - "created": { - "type": "number" - }, - "expires": { - "type": "number" - }, - "hp": { - "type": "number" - }, - "type": { - "enum": [ - "a", - "b", - "bp", - "m", - "o", - "t", - "w", - "x" - ], - "type": "string" - }, - "unit_id": { - "type": "string" - }, - "x": { - "type": "number" - }, - "y": { - "type": "number" - } - }, - "required": [ - "created", - "type", - "x", - "y" - ], - "type": "object" - } - }, - "required": [ - "coordinates", - "type", - "updated_entity" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "data": { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "blast_diameter": { - "type": "number" - }, - "created": { - "type": "number" - }, - "expires": { - "type": "number" - }, - "hp": { - "type": "number" - }, - "type": { - "enum": [ - "a", - "b", - "bp", - "m", - "o", - "t", - "w", - "x" - ], - "type": "string" - }, - "unit_id": { - "type": "string" - }, - "x": { - "type": "number" - }, - "y": { - "type": "number" - } - }, - "required": [ - "created", - "type", - "x", - "y" - ], - "type": "object" - }, - "type": { - "enum": [ - "entity_spawned" - ], - "type": "string" - } - }, - "required": [ - "data", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "data": { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "blast_diameter": { - "type": "number" - }, - "coordinates": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "hp": { - "type": "number" - }, - "inventory": { - "additionalProperties": false, - "properties": { - "bombs": { - "type": "number" - } - }, - "required": [ - "bombs" - ], - "type": "object" - }, - "invulnerability": { - "type": "number" - }, - "unit_id": { - "type": "string" - } - }, - "required": [ - "agent_id", - "blast_diameter", - "coordinates", - "hp", - "inventory", - "invulnerability", - "unit_id" - ], - "type": "object" - }, - "type": { - "enum": [ - "unit_state" - ], - "type": "string" - } - }, - "required": [ - "data", - "type" - ], - "type": "object" - } - ] - }, - "type": "array" + "width": { + "type": "number" }, - "tick": { + "height": { "type": "number" } }, "required": [ - "events", - "tick" + "width", + "height" ], - "type": "object" + "additionalProperties": false }, - "type": { - "enum": [ - "tick" - ], - "type": "string" - } - }, - "required": [ - "payload", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "payload": { - "additionalProperties": false, + "config": { + "type": "object", "properties": { - "agents": { - "additionalProperties": { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "unit_ids": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "agent_id", - "unit_ids" - ], - "type": "object" - }, - "type": "object" - }, - "config": { - "additionalProperties": false, - "properties": { - "fire_spawn_interval_ticks": { - "type": "number" - }, - "game_duration_ticks": { - "type": "number" - }, - "tick_rate_hz": { - "type": "number" - } - }, - "required": [ - "fire_spawn_interval_ticks", - "game_duration_ticks", - "tick_rate_hz" - ], - "type": "object" - }, - "connection": { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "id": { - "type": "number" - }, - "role": { - "enum": [ - "admin", - "agent", - "spectator" - ], - "type": "string" - } - }, - "required": [ - "agent_id", - "id", - "role" - ], - "type": "object" - }, - "entities": { - "items": { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "blast_diameter": { - "type": "number" - }, - "created": { - "type": "number" - }, - "expires": { - "type": "number" - }, - "hp": { - "type": "number" - }, - "type": { - "enum": [ - "a", - "b", - "bp", - "m", - "o", - "t", - "w", - "x" - ], - "type": "string" - }, - "unit_id": { - "type": "string" - }, - "x": { - "type": "number" - }, - "y": { - "type": "number" - } - }, - "required": [ - "created", - "type", - "x", - "y" - ], - "type": "object" - }, - "type": "array" - }, - "tick": { + "game_duration_ticks": { "type": "number" }, - "unit_state": { - "additionalProperties": { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "blast_diameter": { - "type": "number" - }, - "coordinates": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "hp": { - "type": "number" - }, - "inventory": { - "additionalProperties": false, - "properties": { - "bombs": { - "type": "number" - } - }, - "required": [ - "bombs" - ], - "type": "object" - }, - "invulnerability": { - "type": "number" - }, - "unit_id": { - "type": "string" - } - }, - "required": [ - "agent_id", - "blast_diameter", - "coordinates", - "hp", - "inventory", - "invulnerability", - "unit_id" - ], - "type": "object" - }, - "type": "object" + "tick_rate_hz": { + "type": "number" }, - "world": { - "additionalProperties": false, - "properties": { - "height": { - "type": "number" - }, - "width": { - "type": "number" - } - }, - "required": [ - "height", - "width" - ], - "type": "object" + "fire_spawn_interval_ticks": { + "type": "number" } }, "required": [ - "agents", - "config", - "connection", - "entities", - "tick", - "unit_state", - "world" - ], - "type": "object" - }, - "type": { - "enum": [ - "game_state" + "game_duration_ticks", + "tick_rate_hz", + "fire_spawn_interval_ticks" ], + "additionalProperties": false + } + }, + "required": [ + "game_id", + "tick", + "agents", + "unit_state", + "entities", + "world", + "config" + ], + "additionalProperties": false + }, + "history": { + "type": "array", + "items": { + "$ref": "#/definitions/IGameTick" + } + }, + "winning_agent_id": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "initial_state", + "history", + "winning_agent_id" + ], + "additionalProperties": false + }, + "InfoPacket": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "info" + }, + "payload": { + "type": "object", + "properties": { + "message": { "type": "string" } }, "required": [ - "payload", - "type" + "message" ], - "type": "object" + "additionalProperties": false + } + }, + "required": [ + "type", + "payload" + ], + "additionalProperties": false + }, + "NextGameStatePacket": { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "next_game_state" }, - { - "additionalProperties": false, + "payload": { + "type": "object", "properties": { - "payload": { - "additionalProperties": false, + "sequence_id": { + "type": "number" + }, + "is_complete": { + "type": "boolean" + }, + "tick_result": { + "$ref": "#/definitions/IGameTick" + }, + "next_state": { + "type": "object", "properties": { - "history": { + "game_id": { + "type": "string" + }, + "tick": { + "type": "number" + }, + "agents": { + "$ref": "#/definitions/IAgentHashMap" + }, + "unit_state": { + "$ref": "#/definitions/IUnitStateHashMap" + }, + "entities": { + "type": "array", "items": { - "additionalProperties": false, - "properties": { - "events": { - "items": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "data": { - "anyOf": [ - { - "additionalProperties": false, - "properties": { - "move": { - "enum": [ - "down", - "left", - "right", - "up" - ], - "type": "string" - }, - "type": { - "enum": [ - "move" - ], - "type": "string" - }, - "unit_id": { - "type": "string" - } - }, - "required": [ - "move", - "type", - "unit_id" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "type": { - "enum": [ - "bomb" - ], - "type": "string" - }, - "unit_id": { - "type": "string" - } - }, - "required": [ - "type", - "unit_id" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "coordinates": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "type": { - "enum": [ - "detonate" - ], - "type": "string" - }, - "unit_id": { - "type": "string" - } - }, - "required": [ - "coordinates", - "type", - "unit_id" - ], - "type": "object" - } - ] - }, - "type": { - "enum": [ - "unit" - ], - "type": "string" - } - }, - "required": [ - "agent_id", - "data", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "data": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "type": { - "enum": [ - "entity_expired" - ], - "type": "string" - } - }, - "required": [ - "data", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "coordinates": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "type": { - "enum": [ - "entity_state" - ], - "type": "string" - }, - "updated_entity": { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "blast_diameter": { - "type": "number" - }, - "created": { - "type": "number" - }, - "expires": { - "type": "number" - }, - "hp": { - "type": "number" - }, - "type": { - "enum": [ - "a", - "b", - "bp", - "m", - "o", - "t", - "w", - "x" - ], - "type": "string" - }, - "unit_id": { - "type": "string" - }, - "x": { - "type": "number" - }, - "y": { - "type": "number" - } - }, - "required": [ - "created", - "type", - "x", - "y" - ], - "type": "object" - } - }, - "required": [ - "coordinates", - "type", - "updated_entity" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "data": { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "blast_diameter": { - "type": "number" - }, - "created": { - "type": "number" - }, - "expires": { - "type": "number" - }, - "hp": { - "type": "number" - }, - "type": { - "enum": [ - "a", - "b", - "bp", - "m", - "o", - "t", - "w", - "x" - ], - "type": "string" - }, - "unit_id": { - "type": "string" - }, - "x": { - "type": "number" - }, - "y": { - "type": "number" - } - }, - "required": [ - "created", - "type", - "x", - "y" - ], - "type": "object" - }, - "type": { - "enum": [ - "entity_spawned" - ], - "type": "string" - } - }, - "required": [ - "data", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "data": { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "blast_diameter": { - "type": "number" - }, - "coordinates": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "hp": { - "type": "number" - }, - "inventory": { - "additionalProperties": false, - "properties": { - "bombs": { - "type": "number" - } - }, - "required": [ - "bombs" - ], - "type": "object" - }, - "invulnerability": { - "type": "number" - }, - "unit_id": { - "type": "string" - } - }, - "required": [ - "agent_id", - "blast_diameter", - "coordinates", - "hp", - "inventory", - "invulnerability", - "unit_id" - ], - "type": "object" - }, - "type": { - "enum": [ - "unit_state" - ], - "type": "string" - } - }, - "required": [ - "data", - "type" - ], - "type": "object" - } - ] - }, - "type": "array" - }, - "tick": { - "type": "number" - } - }, - "required": [ - "events", - "tick" - ], - "type": "object" - }, - "type": "array" + "$ref": "#/definitions/IEntity" + } }, - "initial_state": { - "additionalProperties": false, + "world": { + "type": "object", "properties": { - "agents": { - "additionalProperties": { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "unit_ids": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "agent_id", - "unit_ids" - ], - "type": "object" - }, - "type": "object" - }, - "config": { - "additionalProperties": false, - "properties": { - "fire_spawn_interval_ticks": { - "type": "number" - }, - "game_duration_ticks": { - "type": "number" - }, - "tick_rate_hz": { - "type": "number" - } - }, - "required": [ - "fire_spawn_interval_ticks", - "game_duration_ticks", - "tick_rate_hz" - ], - "type": "object" - }, - "entities": { - "items": { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "blast_diameter": { - "type": "number" - }, - "created": { - "type": "number" - }, - "expires": { - "type": "number" - }, - "hp": { - "type": "number" - }, - "type": { - "enum": [ - "a", - "b", - "bp", - "m", - "o", - "t", - "w", - "x" - ], - "type": "string" - }, - "unit_id": { - "type": "string" - }, - "x": { - "type": "number" - }, - "y": { - "type": "number" - } - }, - "required": [ - "created", - "type", - "x", - "y" - ], - "type": "object" - }, - "type": "array" - }, - "tick": { + "width": { "type": "number" }, - "unit_state": { - "additionalProperties": { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "blast_diameter": { - "type": "number" - }, - "coordinates": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "hp": { - "type": "number" - }, - "inventory": { - "additionalProperties": false, - "properties": { - "bombs": { - "type": "number" - } - }, - "required": [ - "bombs" - ], - "type": "object" - }, - "invulnerability": { - "type": "number" - }, - "unit_id": { - "type": "string" - } - }, - "required": [ - "agent_id", - "blast_diameter", - "coordinates", - "hp", - "inventory", - "invulnerability", - "unit_id" - ], - "type": "object" - }, - "type": "object" - }, - "world": { - "additionalProperties": false, - "properties": { - "height": { - "type": "number" - }, - "width": { - "type": "number" - } - }, - "required": [ - "height", - "width" - ], - "type": "object" + "height": { + "type": "number" } }, "required": [ - "agents", - "config", - "entities", - "tick", - "unit_state", - "world" + "width", + "height" ], - "type": "object" + "additionalProperties": false }, - "winning_agent_id": { - "type": "string" - } - }, - "required": [ - "history", - "initial_state", - "winning_agent_id" - ], - "type": "object" - }, - "type": { - "enum": [ - "endgame_state" - ], - "type": "string" - } - }, - "required": [ - "payload", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "payload": { - "additionalProperties": false, - "properties": { - "message": { - "type": "string" - } - }, - "required": [ - "message" - ], - "type": "object" - }, - "type": { - "enum": [ - "info" - ], - "type": "string" - } - }, - "required": [ - "payload", - "type" - ], - "type": "object" - }, - { - "additionalProperties": false, - "properties": { - "payload": { - "additionalProperties": false, - "properties": { - "next_state": { - "additionalProperties": false, + "config": { + "type": "object", "properties": { - "agents": { - "additionalProperties": { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "unit_ids": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "agent_id", - "unit_ids" - ], - "type": "object" - }, - "type": "object" - }, - "config": { - "additionalProperties": false, - "properties": { - "fire_spawn_interval_ticks": { - "type": "number" - }, - "game_duration_ticks": { - "type": "number" - }, - "tick_rate_hz": { - "type": "number" - } - }, - "required": [ - "fire_spawn_interval_ticks", - "game_duration_ticks", - "tick_rate_hz" - ], - "type": "object" - }, - "entities": { - "items": { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "blast_diameter": { - "type": "number" - }, - "created": { - "type": "number" - }, - "expires": { - "type": "number" - }, - "hp": { - "type": "number" - }, - "type": { - "enum": [ - "a", - "b", - "bp", - "m", - "o", - "t", - "w", - "x" - ], - "type": "string" - }, - "unit_id": { - "type": "string" - }, - "x": { - "type": "number" - }, - "y": { - "type": "number" - } - }, - "required": [ - "created", - "type", - "x", - "y" - ], - "type": "object" - }, - "type": "array" - }, - "tick": { + "game_duration_ticks": { "type": "number" }, - "unit_state": { - "additionalProperties": { - "additionalProperties": false, - "properties": { - "agent_id": { - "type": "string" - }, - "blast_diameter": { - "type": "number" - }, - "coordinates": { - "items": [ - { - "type": "number" - }, - { - "type": "number" - } - ], - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "hp": { - "type": "number" - }, - "inventory": { - "additionalProperties": false, - "properties": { - "bombs": { - "type": "number" - } - }, - "required": [ - "bombs" - ], - "type": "object" - }, - "invulnerability": { - "type": "number" - }, - "unit_id": { - "type": "string" - } - }, - "required": [ - "agent_id", - "blast_diameter", - "coordinates", - "hp", - "inventory", - "invulnerability", - "unit_id" - ], - "type": "object" - }, - "type": "object" + "tick_rate_hz": { + "type": "number" }, - "world": { - "additionalProperties": false, - "properties": { - "height": { - "type": "number" - }, - "width": { - "type": "number" - } - }, - "required": [ - "height", - "width" - ], - "type": "object" + "fire_spawn_interval_ticks": { + "type": "number" } }, "required": [ - "agents", - "config", - "entities", - "tick", - "unit_state", - "world" + "game_duration_ticks", + "tick_rate_hz", + "fire_spawn_interval_ticks" ], - "type": "object" - }, - "sequence_id": { - "type": "number" + "additionalProperties": false } }, "required": [ - "next_state", - "sequence_id" - ], - "type": "object" - }, - "type": { - "enum": [ - "next_game_state" + "game_id", + "tick", + "agents", + "unit_state", + "entities", + "world", + "config" ], - "type": "string" + "additionalProperties": false } }, "required": [ - "payload", - "type" + "sequence_id", + "is_complete", + "tick_result", + "next_state" ], - "type": "object" + "additionalProperties": false } - ] + }, + "required": [ + "type", + "payload" + ], + "additionalProperties": false + }, + "IPostTelemetryBody": { + "type": "object", + "properties": { + "_initId": { + "type": "string" + }, + "event": { + "type": "string" + }, + "data": {} + }, + "required": [ + "_initId", + "event", + "data" + ], + "additionalProperties": false } } -} - +} \ No newline at end of file