How the live bot thinks
The Live page does not play a recording. It loads the real game in an
<iframe>, injects a JavaScript bot into that running game, and lets the bot press the same
arrow-key scancodes a player would press. The bot is a small planner: every move it rebuilds the snake state,
prioritizes nearby food, rejects tempting routes that become traps, watches for tail-following loops, and falls
back to pure survival when no safe food path exists.
live.html; it reads the port's live variables
and sends keys, but it does not change the game rules.
1. Where the bot runs
The live page has one iframe whose source is index.html. On every iframe load, live.html
appends CSS that hides the surrounding game chrome, then runs the bot string with eval() inside
the iframe:
cell.frame.contentWindow.eval('var IDX=0,TARGET=' + activeLevel + ';' + BOT);
That placement is deliberate. The game uses const and let globals such as
T, BTEL, ETEL, LEVEL, HART, KLAVER,
and pushKey(). Those names are visible to code evaluated in the same script realm, but they are
not ordinary window properties. Injecting the bot into the iframe lets it call peek(),
read T[BTEL], and press keys through pushKey() as if it were part of the page.
Level jump
It dismisses level 1, sets LEVEL = TARGET - 1, presses F10 once, and lets the game's own loop enter the selected level.
Real input
It sends DOS-style extended-key strings: up ' H', down ' P', left ' K', right ' M'.
Live status
It reports score and remaining items to the parent page with parent.botStatus(...), and reports success or failure with parent.botEnd(...).
2. The data it sees
The bot uses the same screen-memory model as the game. It does not have a neat list of "objects"; it reads
characters from VRAM with peek(offset). The snake body is reconstructed from the game's
T array, from tail index ETEL through head index BTEL.
| Game value | Meaning to the bot | Planning effect |
|---|---|---|
32 | Empty cell | Freely enterable. |
3 ♥ | Heart | Food worth 10 points; grows the snake. |
5 ♣ | Club | Food worth 25 points; grows the snake. |
1 ☺ | Smiley | Normally avoided; allowed only in escape mode. |
10 ◙ | Stone | Pushable if the cell behind it is empty. |
24, 26, 27 ↑→← | Moving arrows | Treated as fatal, including cells they are about to enter. |
219 | Snake head | Used by the game for collision; the bot also tracks the head through T[BTEL]. |
For simulated routes, the bot keeps an overlay map called cells. This overlay records only changes
caused by imaginary moves, such as a pushed stone or an emptied tail cell. If a cell is not in the overlay, the
bot reads the real game screen with peek(). That keeps route simulation cheap without cloning the
whole 4000-byte VRAM array for every branch.
3. One decision cycle
Every bot move follows the same ladder. The first strategy that returns a direction wins; the bot presses that key, waits according to the speed slider, then recalculates from the new live game state.
Arrow danger is recalculated for this tick, with memoization so repeated checks are cheap.
First try a shallow search for close hearts or clubs, without eating smileys.
If no nearby pickup is safe, run the deeper food planner without smileys.
If clean routes fail, repeat nearby and wider food searches with smileys allowed.
If the head is repeating positions or the score has been idle, push toward food instead of circling the tail.
Only when not under loop pressure, try to reach the moving tail and buy space.
If all planners fail, pick the best legal one-step survival move.
const decide = (idle, looping) => {
resetDanger();
const urgent = idle >= 45 || looping;
return nearFood(false) ?? routeFood(false) ?? nearFood(true) ?? routeFood(true) ??
(urgent ? (pressureFood(false, true) ?? pressureFood(true, true)) : null) ??
(!urgent ? tailFirst() : null) ??
pressureFood(false, urgent) ?? pressureFood(true, urgent) ??
survivalMove();
};
The looping flag comes from a short head-position trail. If the current head offset has appeared
repeatedly while the score has not changed, the bot treats tail-following as suspicious and switches to food
pressure before the red stuck timer has to fire.
4. Arrow danger model
The bot treats arrow levels differently because a square can be empty now and fatal one tick later. The
danger(offset) function marks a destination unsafe if an arrow is already there, if an arrow will
step into it on the next enemy update, or if a wrapping arrow will reappear there.
peek(o) is 24, 26, or 27.This is a one-tick hazard model, not a full future enemy simulation. That is intentional: the bot moves, the real game advances, and the next decision recalculates danger from the fresh screen. That gives good arrow avoidance without making every search branch simulate all enemy routines.
5. Move simulation
The core planner primitive is move(state, scancode, allowSmile). It returns a new imaginary
state if the move is legal, or null if it would collide, reverse, die, or push an impossible stone.
| Rule | How the bot applies it |
|---|---|
| No instant reverse | If the requested scancode is opposite to the current direction, reject it. |
| No danger | If danger(next) is true, reject the move. |
| No self-hit | If the next square is in the simulated bodySet, reject it. |
| Smileys optional | Reject smileys during normal search; allow them during escape search. |
| Stones push | A stone can move one cell forward only if that cell is empty and not occupied by the simulated body. |
| Growth | Hearts, clubs, and smileys grow the body. Empty moves remove the tail first. |
| First move preserved | Every simulated branch remembers only the first real key to press once that branch wins. |
This is why the bot can reason about pushing stones and about its own tail moving away. It is not only finding a geometric path through the current picture; it is simulating what the snake's body will look like after each step on that path.
6. Food search
Food search is split into three planners. They all run breadth-first from the current head position, simulate the snake body and pushed stones, and preserve only the first key press from a winning route. The split exists because the bot needs different behavior in different moments: grab safe nearby food quickly, make cautious long routes when there is time, and force progress when it starts looping.
| Planner | When it runs | Limits | Main bias |
|---|---|---|---|
nearFood() | Before every wider search, first without smileys and then with smileys if needed. | Depth 9 normally, 12 when 6 or fewer items remain; at most 260 states. | Very strong distance penalty, so safe nearby food wins. |
routeFood() | The normal cautious route finder. | Depth 78 normally, 115 when 6 or fewer items remain; at most 950 states; can stop after 28 food candidates once a good route exists. | Survival first: tail reach, future exits, and open space beat shortness. |
pressureFood() | When the bot is idle, looping, or when tail-following did not help. | Depth 70-125 depending on endgame and urgency; 720-1050 states; 22-38 checked food candidates. | Looser survival gates and a smaller distance penalty, to break circles without grabbing obvious traps. |
The endgame gets deeper search because the last few hearts and clubs are exactly where traps and long detours become most expensive. Earlier in the level, a shallower search is usually enough and keeps the bot fast. Smileys are still second-class targets: the bot only allows them after clean heart/club searches fail.
7. Trap checks
Reaching a heart is not enough. Most bad snake bots die because they follow the shortest route to food and discover too late that the food was in a cul-de-sac. Sneekie's bot therefore tests a candidate route with three different survival signals.
Exit count
legalCount() asks how many moves will still be available immediately after eating. Zero exits reject the route; one-exit corridors are heavily penalized.
Reachable space
spaceInfo() flood-fills from the simulated head, respecting current direction, walls, body, stones, food, and danger.
Tail reach
The same flood fill records whether the simulated head can reach the tail. If the tail is reachable, the snake probably has a moving escape route.
Survival depth
survivalDepth() runs a small beam search to see how many future moves remain possible after the candidate is eaten.
The minimum space requirement grows with the snake length: the longer the snake, the more room a candidate must leave behind. In the final items, the bot demands more space and a deeper survival horizon.
8. Fallback moves
The bot has three fallback behaviors after the normal safe food planners. They keep it from freezing, but they are ordered carefully so it does not spend too long following its own tail while food is available.
| Fallback | Purpose | Behavior |
|---|---|---|
pressureFood() | Break loops | Runs when idle >= 45 or the recent head trail shows repeated positions. It searches for food with lighter survival gates, first without smileys, then with smileys. |
tailFirst() | Buy time | Runs a BFS toward the current tail only while the bot is not under loop pressure. Following the tail is safe as a short bridge, but it is skipped once it starts looking like a circle. |
survivalMove() | Last resort | Scores each immediately legal move by reachable space, tail reach, exits, food, smiley penalty, stone penalty, and straight-ahead preference. |
This gives the bot a survival instinct without letting that instinct become endless circling. If the map says "do not eat yet", it can follow the tail briefly or move into a larger open region, but repeated head positions make the next decisions prefer food pressure instead.
9. Route scoring
Once a food candidate passes the safety gates, it gets a score. The normal route score is intentionally biased toward survival first, points second, and shortness only after those are satisfied:
score += survivalDepth * 5600
score += exits * 2400
score += reachableSpace * 16
score += itemPoints * 150
score -= routeDistance * 260
score -= smileysEaten * 1200
score -= stonesPushed * 55
score -= oneExitAfterEating ? 18000 : 0
The large tail-reach and survival-depth weights are the key anti-trap behavior. A slightly longer route that keeps access to the tail beats a short route into a tight pocket. Clubs still matter because they are worth more points than hearts, but they do not override the survival tests.
The two newer food planners deliberately use different weights:
| Planner | Important scoring difference |
|---|---|
nearFood() | Applies a very large -distance * 6200 term. This is what stops the bot from ignoring safe food that is only a few moves away. |
pressureFood() | Uses smaller trap and smiley penalties when urgent, plus a smaller distance penalty. This lets it escape tail loops by making real progress. |
survivalMove() | Does not search for a full food route. It scores immediate legal moves by open space, tail reach, exits, direct food, smiley cost, stone cost, and straight-ahead preference. |
10. Speed limits
The bot has to think between visible game moves. Several caps keep that work bounded:
danger()uses a generation-stamped cache so repeated hazard checks in one decision are cheap.nearFood()is deliberately shallow: 260 states and only 9-12 moves deep.routeFood()stops at 950 dequeued search states and keeps only useful food candidates.pressureFood()uses a separate cap: 720 states normally, 1050 when urgent.survivalDepth()is a beam search: after each depth, it keeps the 40 states with the most immediate exits.- Food search is shallower on ordinary mid-level decisions and deeper only near the end of a level.
- The page speed slider changes the delay between key presses, not the search itself.
The slider maps 0–100 onto a nonlinear delay. At low values the bot waits longer between moves; at high values it presses keys much faster, down to roughly 45 ms per move.
delay = round(45 + 375 * ((100 - sliderValue) / 100) ** 1.6)
11. Win, stuck, restart
The live page has 16 selectable tabs: levels 1-8 and 25-32. A win flashes green, a failure flashes red, and both outcomes advance to the next tab. After level 32 it wraps back to level 1. The flash pulses five times, one second apart, before the next level loads.
| Condition | Result |
|---|---|
LEVEL === TARGET + 1 && LIVE > 0 | Clean clear. Flash green and advance to the next listed level. |
LEVEL !== TARGET | The game jumped away or ended. Treat as failure and flash red. |
BTEL stops advancing | The snake is boxed, dying, or not moving. Flash red after the stall threshold. |
| No safe move | All planners failed. Flash red. |
| No score gain past the dynamic idle limit | Progress stalled. Flash red. The limit is 160 moves normally, then 210, 280, 400, or 520 moves as the remaining item count drops to 8, 4, 2, or 1. |
Score is used for the idle timer instead of item count because late hearts can spawn clubs. Near the end this matters: the last club can require a long safe path without changing the score, so the dynamic timeout gives it more room before declaring the bot stuck.
12. Limits and tradeoffs
The bot is deliberately practical rather than perfect. It does not solve the whole level as one enormous plan, because the board changes after every pickup, pushed stone, spawned club, and enemy tick. Instead it replans constantly from the live state. That makes it resilient and fast enough to watch.
- It simulates the snake body and pushed stones, but not a full multi-tick future of every enemy routine.
- It treats current and next-tick arrow danger conservatively, then recalculates after the real game advances.
- Its state hash is compact and meant for pruning, not for preserving every possible board detail.
- It may choose a smiley only when clean food routes fail, because smileys cost points and grow the snake.
- The survival beam favors positions with many exits, so it can miss rare narrow routes that are technically safe but expensive to prove.
In short: the bot thinks like a cautious snake player. It wants nearby food first, but only when that food leaves breathing room. When the board gets tight, it values its tail, open space, and future exits more than the nearest unsafe points.