{"id":50592086,"url":"https://github.com/ericmann/battlesnake-next","last_synced_at":"2026-06-05T11:02:01.213Z","repository":{"id":358987054,"uuid":"1234169570","full_name":"ericmann/battlesnake-next","owner":"ericmann","description":"Next-Gen Battlesnake bot","archived":false,"fork":false,"pushed_at":"2026-05-19T22:19:53.000Z","size":280,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-20T01:33:56.458Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ericmann.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-09T20:49:25.000Z","updated_at":"2026-05-19T22:19:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ericmann/battlesnake-next","commit_stats":null,"previous_names":["ericmann/battlesnake-next"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/ericmann/battlesnake-next","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericmann%2Fbattlesnake-next","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericmann%2Fbattlesnake-next/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericmann%2Fbattlesnake-next/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericmann%2Fbattlesnake-next/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ericmann","download_url":"https://codeload.github.com/ericmann/battlesnake-next/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericmann%2Fbattlesnake-next/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33939227,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-05T02:00:06.157Z","response_time":120,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-05T11:02:00.064Z","updated_at":"2026-06-05T11:02:01.200Z","avatar_url":"https://github.com/ericmann.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# Battlesnake vNext\n\n```\n     ██████╗  █████╗ ████████╗████████╗██╗     ███████╗███████╗███╗   ██╗ █████╗ ██╗  ██╗███████╗\n     ██╔══██╗██╔══██╗╚══██╔══╝╚══██╔══╝██║     ██╔════╝██╔════╝████╗  ██║██╔══██╗██║ ██╔╝██╔════╝\n     ██████╔╝███████║   ██║      ██║   ██║     █████╗  ███████╗██╔██╗ ██║███████║█████╔╝ █████╗\n     ██╔══██╗██╔══██║   ██║      ██║   ██║     ██╔══╝  ╚════██║██║╚██╗██║██╔══██║██╔═██╗ ██╔══╝\n     ██████╔╝██║  ██║   ██║      ██║   ███████╗███████╗███████║██║ ╚████║██║  ██║██║  ██╗███████╗\n     ╚═════╝ ╚═╝  ╚═╝   ╚═╝      ╚═╝   ╚══════╝╚══════╝╚══════╝╚═╝  ╚═══╝╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝\n                                                ██╗   ██╗███╗   ██╗███████╗██╗  ██╗████████╗\n                                                ██║   ██║████╗  ██║██╔════╝╚██╗██╔╝╚══██╔══╝\n                                                ██║   ██║██╔██╗ ██║█████╗   ╚███╔╝    ██║\n                                                ╚██╗ ██╔╝██║╚██╗██║██╔══╝   ██╔██╗    ██║\n                                                 ╚████╔╝ ██║ ╚████║███████╗██╔╝ ██╗   ██║\n                                                  ╚═══╝  ╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝   ╚═╝\n```\n\n***A third-generation Battlesnake competitor from the Mann lab.***\n*Built in PHP 8.3. Verified to decide a move in 150 ms. Two trophies on the shelf and counting.*\n\n[![CI](https://github.com/ericmann/battlesnake-next/actions/workflows/ci.yml/badge.svg)](https://github.com/ericmann/battlesnake-next/actions/workflows/ci.yml)\n[![PHP](https://img.shields.io/badge/php-8.3-777BB4?logo=php\u0026logoColor=white)](https://www.php.net/)\n[![Release](https://img.shields.io/badge/release-v2026.05.19--1-blue)](https://github.com/ericmann/battlesnake-next/releases)\n[![License](https://img.shields.io/badge/license-MIT-green)](./LICENSE)\n[![Tests](https://img.shields.io/badge/tests-71%20passing-brightgreen)](./tests)\n\n\u003c/div\u003e\n\n---\n\n\u003e **Genus:** *Serpens elegantissima*\n\u003e **Habitat:** PHP-Fpm. Conference WiFi. Cloudflare Tunnels.\n\u003e **Diet:** Hexagonal pellets. Smaller snakes. The hopes of the bracket.\n\u003e **Conservation status:** Apex.\n\nThe previous two animals in this lineage have a perfect record at PHP\nconferences — the v1 specimen swept Cascadia PHP, the v2 specimen swept\nPHP Tek (with v1 finishing as runner-up, because of course it did). This\none is built to continue the streak. It has opinions about your snake.\nThey are not generous opinions.\n\nIf you are reading this and your snake is **not** a third-generation\nPHP-fpm-and-curl-multi competitor with a dual-LLM racing brain and a\nflood-fill safety net... well. It was a good run.\n\n### What the snake actually sees\n\nMid-game, mid-tournament, mid-decision — this is the world the snake\ngets handed every 500 ms:\n\n```\n10│ .  .  .  .  .  .  .  .  .  .  .         ┌──────────────────────────────┐\n 9│ .  .  .  .  .  .  .  .  .  .  .         │ Turn 47   Health 32   Len 6  │\n 8│ .  .  F  .  .  .  .  .  s  s  t         │                              │\n 7│ .  .  .  .  .  .  .  .  e+ .  .         │ Legal moves (sorted by space)│\n 6│ .  .  .  .  H  .  .  .  .  .  .         │   1. up                      │\n 5│ .  .  e- .  B  F  .  .  .  .  .         │   2. right                   │\n 4│ .  .  s  .  B  B  B  .  .  .  .         │   3. left                    │\n 3│ .  .  s  s  .  .  T  .  .  .  .         │                              │\n 2│ X  X  .  s  .  .  .  .  .  .  .         │ Strategy : MCTS              │\n 1│ X  X  .  s  s  t  .  .  .  F  .         │ Rollouts : 2,214             │\n 0│ X  X  .  .  .  .  .  .  .  .  .         │ Latency  : 150 ms            │\n   └─────────────────────────────────        └──────────────────────────────┘\n     0  1  2  3  4  5  6  7  8  9  10\n```\n\n`H` is the snake's head, `B` its body, `T` its tail. `e+` / `e=` / `e-`\nare enemy heads tagged by length relative to ours: kill, mutual death,\nor run away. `F` is food, `X` is hazard sauce, `.` is open space.\n\nThe y-axis is flipped on render so up on the page is up on the board —\nmatches how a human (or a language model) reads a grid.\n\n---\n\n\u003e ### Current brain: Llama on Groq, racing against itself\n\u003e\n\u003e The first attempt routed to Google Vertex EU at ~500 ms p50 — already\n\u003e at the Battlesnake budget. Disabling the LLM path looked inevitable\n\u003e until we tried Groq-hosted Llama via OpenRouter's strict provider\n\u003e pinning (`provider.allow_fallbacks = false`). The numbers came back\n\u003e clean:\n\u003e\n\u003e | Model                                  | p50    | p95    |\n\u003e |----------------------------------------|--------|--------|\n\u003e | `meta-llama/llama-3.3-70b-instruct`    | 349 ms | 419 ms |\n\u003e | `meta-llama/llama-3.1-8b-instruct`     | 320 ms | 560 ms |\n\u003e\n\u003e Counter-intuitive but real: the **70B model is faster than the 8B**\n\u003e on Groq, because the bottleneck is end-to-end round trip rather than\n\u003e token generation, and the 70B's reasoning is qualitatively better\n\u003e (it correctly chases food at low health where the 8B chases space).\n\u003e\n\u003e So the live config: **primary = 70B, secondary = 8B, both pinned to\n\u003e Groq**. The Decider runs them in parallel with MCTS rollouts during\n\u003e the curl_multi idle window, picks LLM \u003e MCTS \u003e flood-fill at the\n\u003e deadline.\n\n---\n\n## What you are looking at\n\nA 500-line PHP 8.3 server that responds to Battlesnake's `/move` endpoint\nwithin the engine's 500ms budget — every time, on every board, on every\nnetwork. It does this by running **three decision strategies inside a single\ntime window** and picking the best signal it has at the deadline:\n\n1. **Live LLM inference** (the primary brain) — two models on\n   [OpenRouter](https://openrouter.ai) raced against each other via\n   `curl_multi`. The first one to return a legal move wins.\n2. **Incremental MCTS** (the muscle memory) — random rollouts that run\n   *in the same poll loop* the LLM is waiting in. Hundreds of them per turn.\n   Even when the LLM wins, the MCTS has already done its homework.\n3. **Flood-fill winner** (the spinal reflex) — the safety layer's\n   most-open-space direction. Pre-computed before the loop even starts.\n   Always available. Zero additional latency. The snake does not fall over\n   if the internet does.\n\nAt `DECISION_MS` (default 450ms) the loop stops, the priorities resolve\n(LLM → MCTS → flood-fill), and a single JSON response goes out the door.\nA separate log line tells the operator which strategy won and how long\neach piece took.\n\n```mermaid\nsequenceDiagram\n    participant E  as Game Engine\n    participant S  as Snake Server (PHP)\n    participant SF as Safety Layer\n    participant OR1 as OpenRouter — Gemini 2.5 Flash Lite\n    participant OR2 as OpenRouter — Gemini 2.0 Flash Lite\n    participant M  as Incremental MCTS\n\n    E-\u003e\u003eS: POST /move (board state)\n    S-\u003e\u003eSF: legalMoves(me, board)\n    SF--\u003e\u003eS: [up, left, right] (sorted by flood-fill space)\n    S-\u003e\u003eS: Decider starts, t=0\n\n    par Race the brain\n        S-\u003e\u003eOR1: curl_multi POST (immediate)\n        S-\u003e\u003eOR2: curl_multi POST (after 50ms stagger)\n    and Run the muscle memory\n        loop every ~1ms until DECISION_MS\n            S-\u003e\u003eM: runOne()  (one MCTS rollout)\n        end\n    end\n\n    alt LLM returns within budget\n        OR1--\u003e\u003eS: {\"move\":\"left\",\"reasoning\":\"cuts the e+ off\"}\n        S--\u003e\u003eE: {\"move\":\"left\",\"shout\":\"Striking. Do try to keep up.\"}\n    else No LLM in budget, MCTS has rollouts\n        S--\u003e\u003eE: {\"move\":\"up\",\"shout\":\"Vibes-based routing. The brain is on a coffee break.\"}\n    else Nothing useful at all\n        S--\u003e\u003eE: {\"move\":\"up\",\"shout\":\"A purely procedural decision.\"}\n    end\n```\n\n## The design philosophy, narrated by an Australian zoologist\n\n\u003e \"What you're seeing here, mate, is a creature that *refuses* to lose to a\n\u003e dropped TCP packet. Most snakes — beautiful animals, very impressive,\n\u003e perfectly competent at the game — they hand their entire decision off to\n\u003e the network and pray. Not this one. This one has a backup brain in a\n\u003e backup brain. Watch.\"\n\nEvery turn, three different decision processes are running at the same\ntime. The LLM is doing its thing in the cloud. MCTS is rolling out random\nfutures on the local CPU. The flood-fill winner is sitting there, already\ncomputed, just in case. None of them are blocking the others. None of them\nare wasting the others' time. When the deadline arrives, the snake reaches\ninto its pocket, pulls out the best result it has, and moves.\n\n\u003e \"And here's the *brilliant* bit — even when the LLM wins, the MCTS has\n\u003e still done about three hundred rollouts in parallel. *Three hundred*\n\u003e simulated futures. The snake didn't waste a millisecond. Look at that\n\u003e tail flick. That's confidence, that is.\"\n\n---\n\n## Lineage\n\n| Generation | Year | Approach | Result |\n|---|---|---|---|\n| **v1** ([github](https://github.com/ericmann/battlesnake)) | 2024 | Hand-tuned heuristic scoring (food distance, tail chase, flood-fill, head-on avoidance) | **Won** Cascadia PHP. Swept the bracket. |\n| **v2** (private repo) | 2025 | v1 + minimax-style trap detection, head-collision economics, board-division penalties | **Won** PHP Tek. v1 finished runner-up. |\n| **v3** (this repo) | 2026 | Dual-LLM racing brain over `curl_multi`, MCTS interleaved into the poll loop, flood-fill safety net underneath | *To be determined. By winning.* |\n\nThere was a v3 attempt before this one. It tried to train a custom value\nfunction in Python and serve inferences from PHP. It never reached\nproduction because the training data was, in the project's own technical\ndocumentation, \"junk.\" We do not speak of it.\n\nThis v3 takes a different approach: instead of training a value function,\n*rent* one. Two of them, actually. Race them. Trust no individual model\nto beat a 500ms deadline; trust the *fastest of two* to do it most of the\ntime, and have a real plan for when neither does.\n\n---\n\n## How it actually works\n\n### The four endpoints\n\n```\nGET  /         → snake metadata (color, head, tail, author, version)\nPOST /start    → {} (we don't keep state between turns)\nPOST /move     → {\"move\": \"...\", \"shout\": \"...\"}\nPOST /end      → {} (still don't keep state)\n```\n\n`public/index.php` is a 25-line front controller. There is no framework.\nThere will not be a framework. The match expression is the framework.\n\n### The Decider\n\n`src/Decider.php` is the loop. Given an `LlmDriver` and an\n`IncrementalMcts`, it polls one, ticks the other, sleeps a millisecond,\nand stops at the deadline. That's it. The class is 60 lines because the\nproblem, when you describe it correctly, is small.\n\n```php\nwhile (hrtime(true) \u003c $deadline) {\n    if ($llmResult === null) $llmResult = $this-\u003ellm-\u003estep();\n    $this-\u003emcts-\u003erunOne();\n    usleep(1000);\n}\n```\n\nAnything more clever than this would be a regression.\n\n### The safety layer\n\n`src/Safety.php::legalMoves()` is the seatbelt. It takes the raw\nBattlesnake payload, filters out every move that would walk into a wall,\na body segment, or a head-on collision with an equal-or-larger snake,\nand returns what remains sorted by flood-fill space (most open space\nfirst). It also handles the **just-eaten tail** rule properly: a snake\nthat just ate this turn does not vacate its tail next turn, and pretending\notherwise gets you killed in the late game.\n\n`legalMoves()` is *guaranteed non-empty*. Even when the snake is boxed\nin and every option is fatal, it returns the least-bad one so the engine\nalways gets a legal-shaped response. If we're going to die, we're going\nto die *gracefully*.\n\n### The board renderer\n\n`src/Board.php::format()` produces the ASCII grid shown at the top of\nthis README, plus a metadata block listing turn / health / length / head\nposition / facing direction / pre-filtered legal moves. It exists to\nfeed the (currently dormant) LLM brain something it can reason about\nspatially. The y-axis is flipped on output so up on the page is up on\nthe board, which dramatically helps both human debugging and any model\nasked to interpret the layout.\n\n### The shouts\n\nThe Battlesnake game viewer shows a `shout` string under each move.\n`src/Shouts.php` keeps a small library of on-brand commentary, indexed\nby situation: hunting, attacking, eating, hungry, escaping, cornering,\ncoiling, cornered, fallback, generic. The Controller infers the situation\nfrom the chosen move and the game state, then picks a line salted by the\nturn number so successive turns rotate naturally.\n\nA small selection from the *attacking* pool:\n\n\u003e \"Striking. Do try to keep up.\"\n\u003e \"Head-on. The shorter snake will not be writing memoirs.\"\n\u003e \"A textbook collision in the making. Eight out of ten judges.\"\n\u003e \"You weren't using that head, were you?\"\n\nThe voice is half-NatGeo, half-ESPN, half-Australian-naturalist, all\ndeadpan. No gen-Z slang. No emoji. The snake has standards.\n\n---\n\n## Quick start\n\n```bash\n# 1. Clone and install dependencies\ngit clone git@github.com:ericmann/battlesnake-next.git\ncd battlesnake-next\ncomposer install\n\n# 2. Configure your OpenRouter key\ncp .env.example .env\n$EDITOR .env  # set OPENROUTER_API_KEY\n\n# 3. Spin it up\ndocker compose up -d\n\n# 4. Smoke test\ncurl http://localhost:9595/\ncurl -X POST http://localhost:9595/move \\\n  -H 'Content-Type: application/json' \\\n  -d @tests/Fixtures/sample_board.json\n```\n\nThe container exposes `127.0.0.1:9595 → :9000` so the existing host\n`cloudflared` named tunnel can route `snake.eamann.com` to\n`http://127.0.0.1:9595` without colliding with anything else listening\non `:9000`.\n\n### Running the test suite\n\n```bash\ncomposer test\n# or, with prettier output:\n./vendor/bin/phpunit --testdox tests/\n```\n\n51 unit tests, 155 assertions, no network. The OpenRouter race tests\nuse a `FakeTransport` that replays canned responses with controlled\nlatencies; the Decider tests use a `FakeLlmDriver` that delivers a\ncanned `RaceResult` on the Nth `step()`. The whole suite runs in well\nunder a second.\n\n### Tournament-day latency probe\n\nBefore a tournament, run from the venue network:\n\n```bash\nphp LATENCY_CHECK.php\n```\n\nIt fires 20 sequential OpenRouter calls with a minimal prompt, prints\np50 / p95 / max, and recommends a tournament-tuned `LLM_TIMEOUT_MS`\nbased on what it observed. The recommendation is conservative — adds\n~200ms slack for the full board prompt and caps at the 500ms Battlesnake\nbudget minus 50ms safety margin.\n\n---\n\n## Configuration (`.env`)\n\n| Variable | Default | What it does |\n|---|---|---|\n| `OPENROUTER_API_KEY` | — | Your OpenRouter API key. Snake works without it (pure MCTS + flood-fill), but you bought a Lamborghini for a reason. |\n| `PRIMARY_MODEL` | `google/gemini-2.5-flash-lite` | Fastest reliable TTFT we found; almost always wins the race when it wins at all. |\n| `SECONDARY_MODEL` | `google/gemini-2.0-flash-lite-001` | Hedge against tail-latency dropouts on the primary. |\n| `STAGGER_MS` | `50` | How long after the primary to fire the secondary. |\n| `DECISION_MS` | `450` | Hard wall-clock cap on the unified decision loop. Keep ≤ 450 — Battlesnake gives 500. |\n| `OPENROUTER_APP_NAME` | `battlesnake-next` | Shown on OpenRouter's leaderboards. |\n| `OPENROUTER_REFERER` | `https://snake.eamann.com` | Same. |\n| `SNAKE_AUTHOR` / `SNAKE_COLOR` / `SNAKE_HEAD` / `SNAKE_TAIL` | — | Cosmetics. The snake also has standards about its appearance. |\n\n---\n\n## Observability\n\nEvery `/move` emits one JSON line on stderr (which Docker captures into\n`docker compose logs -f snake`). Sample:\n\n```json\n{\n  \"ts\": \"2026-05-09T22:18:34.121Z\",\n  \"event\": \"move\",\n  \"game_id\": \"tournament-final-game-3\",\n  \"turn\": 47,\n  \"strategy\": \"llm\",\n  \"model_used\": \"google/gemini-2.5-flash-lite\",\n  \"model_label\": \"primary\",\n  \"move\": \"up\",\n  \"reasoning\": \"health is low, routing to nearest food at (5,5)\",\n  \"safe_moves\": [\"up\", \"right\", \"left\"],\n  \"llm_latency_ms\": 428,\n  \"mcts_rollouts\": 341,\n  \"total_latency_ms\": 453,\n  \"fallback_used\": false,\n  \"own_health\": 32,\n  \"own_length\": 6\n}\n```\n\nWhat to grep for during a tournament:\n\n```bash\ndocker compose logs -f snake | grep '\"strategy\":\"llm\"'      # LLM-decided turns\ndocker compose logs -f snake | grep '\"strategy\":\"mcts\"'     # fallback turns\ndocker compose logs -f snake | grep '\"event\":\"warn\"'        # something surprising happened\n```\n\nIf `total_latency_ms` ever shows north of 470, it is time to lower\n`DECISION_MS` and let the venue's network breathe.\n\n---\n\n## File map\n\n```\nbattlesnake-next/\n├── docs/\n│   ├── TDD.md                  Full design spec\n│   └── CLAUDE.md               Operational build driver\n├── public/\n│   └── index.php               25-line front controller\n├── src/\n│   ├── Controller.php          /move pipeline orchestration\n│   ├── Decider.php             The interleaved decision loop\n│   ├── Decision.php            Value object: strategy + telemetry\n│   ├── LlmDriver.php           Interface\n│   ├── CurlMultiLlmDriver.php  Production driver (real curl_multi)\n│   ├── NullLlmDriver.php       No-API-key fallback\n│   ├── IncrementalMcts.php     One rollout per call\n│   ├── Safety.php              legalMoves, floodFill, mctsMove, singleRollout\n│   ├── Board.php               ASCII rendering for the LLM\n│   ├── Prompts.php             SYSTEM constant\n│   ├── OpenRouter.php          Body builders + parsers\n│   ├── Shouts.php              On-brand commentary library\n│   ├── Context.php             Shouts enum\n│   ├── Logger.php              JSON lines → stderr → Docker\n│   └── Env.php                 Tiny .env reader\n├── tests/                      51 unit tests, 155 assertions\n├── nginx/default.conf          Cloudflare-friendly keepalive (65s)\n├── docker/entrypoint.sh        nginx + php-fpm under tini\n├── Dockerfile                  Multi-stage; opcache on; ~80 MB image\n├── docker-compose.yml          Binds 127.0.0.1:9595 only\n├── LATENCY_CHECK.php           Tournament-day venue probe\n└── .github/workflows/ci.yml    PHPUnit + GHCR image push\n```\n\n---\n\n## Things this snake will not do\n\n- **Use a framework.** PHP 8.3's built-in routing is a `match` expression.\n- **Persist state between turns.** Each `/move` is fully stateless. The board\n  is the source of truth.\n- **Wait for one strategy at a time.** Sequential decisioning was the v2\n  pipeline. v3 races everything in one window.\n- **Lose to a dropped TCP packet.** The flood-fill winner is computed\n  before the LLM call even starts.\n- **Apologize.**\n\n---\n\n## Things this snake will do\n\n- Win.\n\n---\n\n## License\n\nMIT. See [LICENSE](./LICENSE). If your snake adopts these techniques and\nwins something, the polite thing to do is buy the original a beer.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericmann%2Fbattlesnake-next","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fericmann%2Fbattlesnake-next","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericmann%2Fbattlesnake-next/lists"}