Battlesnake is a programming competition where your code controls a snake on a grid. Each turn, the game engine sends you a POST request with the board state. You return a move within 500 milliseconds. The longer your snake survives, the higher you rank on the global leaderboard.
It's a great resume project. You build an HTTP API, ship it to production, and get quantifiable results. You can start with a simple snake, then evolve it to use minimax, pathfinding, or machine learning.
This guide covers strategy first, then hosting.
Part 1: Strategy
The Core Mechanic
Your snake loses one health per turn. Food restores health to 100 and grows your snake by one segment. You die if you run out of health or hit your head.
Step 1: Avoid Guaranteed Death
A snake that survives longer than another wins, even if it never eats. Write a function that checks if a move kills you on the next turn. Eliminate any direction that leads to a wall, your own body, or another snake's body.
def get_safe_moves(state):
board = state["board"]
my_head = state["you"]["body"][0]
my_body = state["you"]["body"]
safe = []
for direction in ["up", "down", "left", "right"]:
next_pos = move_head(my_head, direction)
# Check bounds
if next_pos["x"] < 0 or next_pos["x"] >= board["width"]:
continue
if next_pos["y"] < 0 or next_pos["y"] >= board["height"]:
continue
# Check self collision
if next_pos in my_body[:-1]: # Exclude tail; it moves
continue
# Check other snakes
for snake in state["board"]["snakes"]:
if snake["id"] == state["you"]["id"]:
continue
if next_pos in snake["body"]:
continue
safe.append(direction)
return safe
For now, just pick a random safe move if multiple exist.
Notice you tail moves each turn unless you eat since you grow one block. That means unless you eat, your tail will move out of the way and you can move into the square your tail occupied. This is an optimization you can make to the above code.
Step 2: Chase Food When Hungry
Your snake dies after 100 turns without food. When health is low (below 40), we should move toward the nearest food using Manhattan distance: abs(head_x - food_x) + abs(head_y - food_y).
def get_nearest_food(my_head, food_list):
if not food_list:
return None
return min(food_list, key=lambda f: abs(f["x"] - my_head["x"]) + abs(f["y"] - my_head["y"]))
def move_toward(current, target):
if current["x"] < target["x"]:
return "right"
elif current["x"] > target["x"]:
return "left"
elif current["y"] < target["y"]:
return "down"
else:
return "up"
This is just an example. There are many ways to implement this than the function above.
Recall eating an apple adds length. Due to this, some people choose to go for every apple and leverage their length against others while others avoid them to maintain a smaller footprint.
Step 3: Avoid Other Snakes
Running into another snake's body kills you but it's better than running into a yourself or a wall. Technically there is a small chance the snake dies the turn you run into it's body which would free the space and allow you to keep living. If you are surrounded going into a corner it's better to take this chance than to give up and hit a wall. Furthermore, remember a snake's tail will move unless the snake eats an apple. Unlike your own snake it's harder to predict if an enemy snake will eat an apple but it's still less risky to run into an opposing snake's tail than their body.
Head-on collisions follow different rules. The longer snake wins. Same length means both die. If you are longer, you can ram a smaller snake and kill it.
Unfortunately you cannot know where another snake will move making avoiding or attempting head on collisions more complex than the previous dangers.
Step 4: Map Territory
After eliminating obviously deadly moves, prefer moves that leave you more options on the next turn. A move into a corner is worse than a move into the center, even if both are technically safe.
def count_free_squares(pos, board, my_body, other_snakes):
count = 0
for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
next_x, next_y = pos["x"] + dx, pos["y"] + dy
if 0 <= next_x < board["width"] and 0 <= next_y < board["height"]:
point = {"x": next_x, "y": next_y}
if point not in my_body and not any(point in snake["body"] for snake in other_snakes):
count += 1
return count
This function helps to pick the safe move that preserves the most freedom. This isn't perfect however since it doesn't account for the dynamic nature of the board such as snakes moving.
Part 2: Simple Strategies
Head-On Aggression
If you are longer than an opponent, attempt collide with them. The game will eliminate them next turn.
Tail Chasing
When you need food but are surrounded, follow your own tail. The tail moves away as you move forward, so you stay in a loop without growing.
Only do this when you haven't eaten. If you eat, you grow and the tail stops moving. You'll collide with yourself on the next turn.
Note this can be vulnerable to head on collisions as most snakes will be longer than yours.
def follow_own_tail(state):
my_head = state["you"]["body"][0]
my_body = state["you"]["body"]
health = state["you"]["health"]
if health > 40: # Only follow tail if not eating
tail = my_body[-1]
return move_toward(my_head, tail)
else:
# Chase food instead
pass
This buys time when hunting is dangerous.
Part 3: Advanced Strategies
Minimax and Game Trees
For every possible board state, compute the outcome of each move. Use time-limited search: evaluate all moves up to ~400ms (leave time for latency). Score each outcome. Pick the best move. You'll need a good evaluation function. Is a board state "good" if your snake is longer? If you have more food nearby? If you control the center?
Machine Learning
Train a neural network on thousands of games. Feed the board state, get a move. This has traditionally been worse than trees due to it's non deterministic nature.
Flood Fill and Pathfinding
Use flood fill to find connected spaces. If a move traps you in a space smaller than your length, you'll starve in that space even if there's food. Eliminate such moves.
Djikstra or A* can find the shortest path to any food, accounting for moving obstacles.
Part 4: Game Modes
Royale: Walls shrink toward the center every few turns. You must move toward the center or die.
Squad: Teams of two snakes share health and length. You coordinate with a teammate and must anticipate their moves.
Wrapped: Moving off one edge wraps you to the opposite edge. The board is a torus.
Each mode requires different strategy.
Part 5: Hosting
Your snake is an HTTP server. The game engine sends you a POST request with the board state and expects a move within 500ms.
Battlesnake's main game engine runs in Oregon. When you enter a game, you can select which engine region your snake will connect to in your Battlesnake settings. Other regions include Toronto, Frankfurt, Singapore, Mumbai, and Virginia.
Host your snake close to the engine region you use. Network latency and cold start times eat into your 500ms budget.
Vercel
Vercel is easy to deploy to. You write a handler function and Vercel manages the rest.
Vercel has a limited number of function invocation on the free tier. I've found it to be enough for my Battlesnake to run on all 4 leaderboards but if you have other projects on vercel this may be an issue.
Railway
Railway gives you $1 a month in free credits on the Trial tier. Your snake runs as a Docker container.
Self-Hosting (VPS)
Rent a VPS from in a region close to your chosen Battlesnake engine. Run your snake as a simple HTTP server. No cold starts, no invocation limits, full control of CPU and RAM.
You manage uptime, security, and deployment yourself. For competitive play with compute-heavy snakes, this is the right choice.
Closing Thoughts
Build a heuristic snake first. Watch replays of your losses, spot patterns, and write rules to handle them. Then add advanced strategies if they interest you.