Develop, deploy and debug algorithms on BattleSnake.
This is not a starter snake repository. The julia starter snake is here
git clone https://github.com/Roboneet/Snake.git
cd Snake
- start julia
using Pkg
# activate project on current directory
Pkg.activate(".")
# this will download all the required packages for this project
# (required to be done only once)
Pkg.instantiate()
# load environment, algorithms and some utilities
include("src/debug/playground.jl")
# interactive
play();
This repository contains somewhat useful algorithms. Here are a few of them.
Basic
provides basic collision avoidance. Filters moves that doesn't collide with a wall, or a snake body.
SpaceChase
divides the board into clusters using reachableclusters
algo and moves to cells that are a part of the biggest cluster nearby.
FoodChase
uses A*
to find the cells closest to nearest food.
Grenade
is my first snake! It is a greedy snake if food is close to it. Otherwise, it chases it's tail
TreeSearch
is a tree-search algorithm parameterised by a policy, value function and a function to guide the lookahead. (The names given to these parameters don't make much sense because I ran out of sensible names! Any suggestions are welcome)
TreeSearch{NotBad,Punk,SeqLocalSearch{2}}
works pretty well without killing my server with state explosion. It does a lookahead of depth 2. SeqLocalSearch assigns a move that Grenade would choose to the other snakes on the board, except when they are too close and have a chance at killing mine. The value function considers the length of the snake and the maximum amount of space reachable by my snake (using my super reachableclusters
algo :P). The policy chooses the better moves based on average of the values.
This algorithm is on BattleSnake as spark
. It recently won a summer league challenge
PartialExplore
adapts reachableclusters
algo to create an approximate value function, hence eliminating tree search (evil laugh...)
In practise, this algo can be as good as TreeSearch
and sometimes even better that TreeSearch
with SeqLocalSearch
lookahead because the approximated worst case is worser than the real worst case. (This is not always a good thing) SeqLocalSearch may not always lookahead to the real worst case.
PartialExplore
is on BattleSnake as Inspector Clouseau
. This recently won another summer league challenge
Create a match with 2 algorithms
# play a game with Grenade and FoodChase snakes
fr = play([Grenade, FoodChase], SnakeEnv((10, 10), 2))
# view the game
viewgame(fr)
Try playing with the an algo
fr = play([Human, PartialExplore], SnakeEnv((10, 10), 2); verbose=true)
To create new snake algorithms
- Create a subtype T of AbstractAlgo
- Implement
pipe
for type T to filter the best moves
struct MyAlgo <: AbstractAlgo
# Find the useful moves at state `st` for snake `i`.
# pipe returns a function which accepts a list of directions
# and returns another list of directions
# (no mutations to the list please!)
function pipe(::Type{MyAlgo}, st, i)
return directions -> begin
# return the first move present
return directions[1:1]
end
end
struct MyBetterAlgo <: AbstractAlgo
# combines Basic and MyAlgo (the hard way)
function pipe(::Type{MyBetterAlgo}, st, i)
return dir -> begin
# basic collision avoidance
# (check bounds and check if next cell is free)
moves_in_bounds = pipe(Basic, st, i)(dir)
# edge cases
# none of the moves are good enough, return the previous result
length(moves_in_bounds) == 0 && return dir
# only one move is good, no more filtering required
length(moves_in_bounds) == 1 && return moves_in_bounds
first_move = pipe(MyAlgo, st, i)(moves_in_bounds)
# edge case
length(first_move) == 0 && return moves_in_bounds
return first_move
end
end
fr = play([MyAlgo, MyBetterAlgo], SnakeEnv((7, 7), 2))
The code for creating new algorithms by combining existing algos can be made shorter using flow
function.
This is equivalent to the pipe function above
pipe(::Type{MyBetterAlgo}, st, i) = flow(pipe(Basic, st, i), pipe(MyAlgo, st, i))
All you need to do to setup a server that can respond to the battlesnake engine is add your snake to algoDict
inside app/controller.jl
algoDict["mysnake"] = MyAlgo
To spin up the server locally:
julia --project="." -i src/Snakes.jl
The server provides the battlesnake API at http://localhost:8080/mysnake
The port number can be changed by setting the environment variable PORT
before starting the server.
The -i flag starts julia in interactive mode after running app/main.jl. So you can update your algorithms using the repl without a restart
To make your algorithms available on a heroku server, connect a fork of the repository to heroku and add a julia buildpack to the heroku instance
Buildpack options:
View heroku logs : heroku logs --tail
This server is written using Mux.jl
# create a local game
fr = play([MyAlgo, MyAlgo], SnakeEnv((7, 7), 2))
# view the game frame-by-frame
# use arrow keys to shift frames
kr = viewgame(fr)
# press q when you find the frame where things begin to go downhill
# or
# kr = goto(fr, your_frame_number)
# get the state of the frame
st = kr.state
# get the list of moves returned by your algorithm
moves = pipe(MyAlgo, st, 1)(DIRECTIONS)
- Open a game on the battlesnake website.
- Navigate to Networks tab in DevTools
- Filter out WebSocket requests (WS)
- Pick a message from the messages tab
- Copy message (right click on red arrow to get the option)
# one more utility
include("src/debug/socket.jl")
str = """<paste the message inside triple quotes>"""
wp = WSParser(11, 11) # for a 11x11 board
# get the game state
st = wp(str)
# view the state
Frame(st, nothing)
# get the list of moves returned by your algorithm
moves = pipe(MyAlgo, st, 1)(DIRECTIONS)