By Katzir • June 22, 2025
Hey there! If you’re a PM like me—fascinated by game AI but intimidated by all the math-y jargon—this is for you. A few weekends ago I thought, “What if I built a Snake game that plays itself?” Seven snakes, each with its own brain: Random, Greedy, Tail-Aware, Loop-Safe, Lookahead, Expectimax, and MCTS. The result? The most fun I’ve ever had with vanilla JavaScript.
This post is half tutorial, half war story. We’ll explore how each AI works—why it makes its moves, not just what it does—and I’ll sprinkle in debugging anecdotes so you can laugh (or cry) along. Ready?
Try the live
demo first →
Check it out on GitHub →
Game AI isn’t magic—it’s code that answers one question:
“Where should the snake move next?”
Each brain implements a different decision rule, from chaos to clever planning.
All in pure HTML/CSS/JS—no frameworks required.
snake-ai-showdown/
├── index.html
├── game.html
├── style.css
├── snake.js
└── README.md
Each AI gets its own <canvas> and a fresh copy of the Snake logic.
Theory: No planning. Every tick, pick one of the legal neighbors at random.
function chooseRandom() {
const moves = getValidMoves(head);
return moves[Math.floor(Math.random() * moves.length)];
}
Anecdote: It often dies on the very first move—hilarity ensues.
Theory: A* searches for the shortest grid path to the apple using
f(n)=g(n)+h(n)
, with Manhattan h
.
function chooseGreedy() {
const path = aStar(head, apple);
return path ? path[1] : null;
}
Fast—but blind; it often crashes into its own tail.
Theory: “If I take that apple, can I still reach my tail afterward?”
function chooseTailAware() {
const pA = aStar(head, apple);
if (pA && isPathSafe(pA)) return pA[1];
const pT = aStar(head, tail);
return pT ? pT[1] : chooseRandom();
}
isPathSafe(path) simulates growing along path
then checks head→tail
reachability.
Theory: Tail-Aware plus loop detection & auto-restart:
restart()
function chooseLoopSafe() {
if (isSafe(pathToApple)) return pathToApple[1];
if (pathToTail) return pathToTail[1];
if (isStuckInLoop()) { restart(); return; }
return chooseRandom();
}
Theory: Minimax to depth 3. Simulate moves, score each state by distance, free space, length.
function chooseLookahead(depth=3) {
let best, bestScore = -Infinity;
for (let mv of getValidMoves(head)) {
const state = simulateState(snake, apple, mv);
const score = minimax(state, depth-1);
if (score > bestScore) { bestScore = score; best = mv; }
}
return best;
}
Theory: Like lookahead, but treat apple‐spawn as a chance node—average over spawns.
function chooseExpectimax(depth=2) {
// maximize over moves, then average over random apple positions
}
Theory: For each move, run 15 random rollouts (max 30 steps), pick highest average survival.
function chooseMCTS(sims=15, maxSteps=30) {
// for each move: simulateRandomPlay, return argmax
}
getValidMoves(head)
— adjacent cells not blockedaStar(start, goal)
— A* with Manhattan heuristicisPathSafe(path)
— grow then BFS head→tailisStuckInLoop()
— checks last 200 heads for loopssimulateState(...)
— clone & apply one movesimulateRandomPlay(...)
— random playouts for MCTS.tabs { display:flex; }
.pane { position:absolute; }
canvas { aspect-ratio:1/1; }
git init
git add .
git commit -m "Snake AI Showdown"
git remote add origin https://github.com/yourusername/snake-ai-showdown.git
git push -u origin main
Then GitHub → Settings → Pages → main / root. Live at
yourusername.github.io/snake-ai-showdown
.
Seed your snakes with DNA and watch evolution unfold in your browser.← Back to all posts
I'm always on the lookout for new quests—collaborate, chat, or challenge me to build the next VR dragon simulator!