Snake with zero data, head to head with Jev
Jev's own demo repository has a Snake game that asks Jev for every move. Its request holds the game state, with the board and each legal move's facts (does it eat the food, how far is the food after it, how much room is left, is it a dead end, can the snake still reach its tail), a goal in plain words, and a Choice question over the moves.
This cookbook builds your own Snake player from that request as the demo sends it, with no recorded moves, through POST /v1/build, and runs it inside the game loop.
The result
The model below was built by POST /v1/build from the request in step 1, with no recorded moves and no hand-written help, in about 3 minutes. It's 445 KB to download and reads numbers and categories only, so it needs no text reader.
The demo's 20 test games on its 16×16 board, each stopped at the step where Jev's game on that seed stopped (about 700 moves), so both sides had the same number of moves:
| 20 games | Your model, zero data | Jev, the demo's default safe strategy |
|---|---|---|
| Food eaten, per game | 42.0 ± 4.1 | 40.0 ± 3.3 |
| Games that ended in a crash | 0 | 1 |
| Games ahead / level / behind Jev | 12 / 2 / 6 | |
| Time per move | 0.16 ms, downloaded, in the game's process | 131 ms, median round trip |
Against Jev's greedy strategy, Jev ate more: 40.8 against 35.6. It crashed in 13 of those 20 games; our model in none.
Read these before quoting the numbers
- 20 games is a small sample. Jev's games were stopped after about 700 moves to cap what the test spent, so the comparison is over the same number of steps. Jev's side is from our recorded run of Jev on the demo's own code; ours played the same engine and the same 20 games.
- This is the best of four automatic builds on crashes. Three of the four ate more than Jev (40.9, 42.0 and 43.4 against 40.0) and one ate less (36.5); the other three crashed in 1, 3 and 5 games.
- 97% of its moves came back with
route: "act"; the game plays the answer either way, as below. - Our 0.16 ms is the downloaded model on a laptop CPU, with each move's facts already worked out by the demo; Jev's 131 ms is a round trip over the internet.
- For comparison, hand-built (not what a build gives you): a player we built by hand from the same request ate 47.1 and never crashed. A model built through the API from 3,000 moves recorded from a simple scripted player ate 46.1 and crashed in 3 of 20. See A Snake bot from recorded moves.
Build it
1. Take the request the demo sends Jev
Save one real request, as the demo sends it, as snake-jev-request.json (the board is shortened here):
{
"model": "jev-latest",
"state": {
"board": ["........ooooo...", "........o...o...", "…", "...F........H...", "…"],
"legend": "H = snake head, o = snake body, T = snake tail, F = food, . = empty. Row 0 is the top row, column 0 is the leftmost column. Leaving the grid hits a wall.",
"head": { "row": 8, "col": 12 }, "food": { "row": 8, "col": 3 }, "heading": "down", "snakeLength": 20,
"gridSize": { "rows": 16, "cols": 16 }, "foodIsAdjacent": false,
"facts": [
{ "dir": "right", "turn": "left turn", "target": { "r": 8, "c": 13 }, "eats": false, "foodDistance": 10, "reachable": 236, "freeTotal": 236, "deadEnd": false, "canReachTail": true },
{ "dir": "down", "turn": "straight", "target": { "r": 9, "c": 12 }, "eats": false, "foodDistance": 10, "reachable": 236, "freeTotal": 236, "deadEnd": false, "canReachTail": true },
{ "dir": "left", "turn": "right turn", "target": { "r": 8, "c": 11 }, "eats": false, "foodDistance": 8, "reachable": 236, "freeTotal": 236, "deadEnd": false, "canReachTail": true } ]
},
"questions": {
"move": { "type": "choice",
"instructions": "You are playing the game Snake. Choose the direction the snake's head moves on the next step. Every listed direction is safe for this single step; the facts next to each direction were computed by code and are exact. A move marked DEAD END almost always loses the game a few steps later. The snake grows by one when it eats the food, and the game ends when the head hits a wall or its own body. Player strategy: Stay alive above all. Prefer the move that keeps the most empty cells reachable, never enter a dead end, and only go for the food when that does not shrink your room. Make no mistakes.",
"criteria": { "up": "move up", "down": "move down", "left": "move left", "right": "move right" } }
}
}Send the whole state, as the demo sends it. The instructions are the demo's rules and its safe strategy, word for word.
The build above also had a one-line goal in description ("Eat as much food as possible without dying.") and 10 more real states in examples, from games on other seeds than the 20 test games: states only, no moves.
2. Build
curl https://api.canonopylabs.com/v1/build -H "Authorization: Bearer $CANONOPY_API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq '. + {name: "snake", description: "Eat as much food as possible without dying."}' snake-jev-request.json)"Or canonopy build snake-jev-request.json --name snake --wait.
No rules are needed here: the question's options will be the legal moves, so a move into a wall or the snake is never on offer. understood.fields lists what it reads: each board row, the head, the food, the heading, the length, and each move's facts by its direction (facts_up_deadEnd is facts[dir=up].deadEnd, and so on). not_read lists the legend: the same fixed text in every state, so it doesn't help decide.
3. Wait for ready
canonopy build status snake, or --wait on the build. There's nothing to do in between. It's checked against your instructions on fresh situations, and serves once it agrees at least 95% of the time. See the quality check.
4. See how it decides (optional)
canonopy examples snake
canonopy signoff snake --correct ex_07:move=left --retrain # only if one is wrongEach example is a situation with the move your model makes and why. Look for the moves you'd make: take the food when it's safe, never take a dead end.
5. Ask for each move
Like Jev's demo, list only the legal moves as the question's options; the model picks among them. Send the same state the demo sends Jev:
curl https://api.canonopylabs.com/v1/decide -H "Authorization: Bearer $CANONOPY_API_KEY" \
-H "Content-Type: application/json" -d '{
"model": "snake@latest",
"state": { "board": ["…"], "head": { "row": 8, "col": 12 }, "food": { "row": 8, "col": 3 }, "heading": "down", "…": "…",
"facts": [ { "dir": "right", "…": "…" }, { "dir": "down", "…": "…" }, { "dir": "left", "…": "…" } ] },
"questions": { "move": { "type": "choice", "instructions": "Which way should the snake move next?",
"criteria": { "right": "move right", "down": "move down", "left": "move left" } } }
}'The move is answers.move.choice (with no rules there is no decision block: decision is for the question your rules are about).
6. Run it inside the game loop
A move every few milliseconds is where a download pays off: no network, nothing per move.
curl -L -o snake.zip -H "Authorization: Bearer $CANONOPY_API_KEY" \
https://api.canonopylabs.com/v1/models/snake@latest/downloadfrom canonopy_runtime import load
bot = load("snake.zip")
def next_move(state):
options = {f["dir"]: f"move {f['dir']}" for f in state["facts"]} # the legal moves only
res = bot.decide({"state": state, "questions": {"move": {"type": "choice", "criteria": options}}})
return res["answers"]["move"]["choice"]pip install https://canonopylabs.com/dl/canonopy_runtime-0.6.3-py3-none-any.whl. See Running it yourself.
Make it better
- Recorded moves: from a scripted player, a designer or your best players, each with the move that was made. Send them as history and build again under the same name. See A Snake bot from recorded moves.
- A move you'd never make: mark it wrong in the decision log with the right move, and retrain. See Improve your model.
- A different style: change the strategy in the instructions (the demo's
greedy, say) and build again under a new name, then play the two against each other.