Skip to content

Commit

Permalink
Improve strategy choice based on path (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
fdocr authored Sep 29, 2023
1 parent 6f0a722 commit 39c0b03
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 46 deletions.
50 changes: 36 additions & 14 deletions spec/server_spec.cr
Original file line number Diff line number Diff line change
@@ -1,26 +1,48 @@
require "./spec_helper"

describe "Crystal Snake Server" do
it "returns metadata on root path" do
get "/"
response.status_code.should eq 200
request_headers = HTTP::Headers{"Content-Type" => "application/json"}
SUPPORTED_STRATEGIES = [
"random",
"random_valid",
"blast_random_valid",
"chase_closest_food",
"chase_random_food",
"cautious_carol"
]

describe "Crystal Snake Battlesnake endpoints for all supported strategies" do
it "responds with metadata for all supported strategies" do
SUPPORTED_STRATEGIES.each do |strategy_name|
get "/#{strategy_name}"
response.status_code.should eq 200
res = Hash(String, String).from_json(response.body)
res.keys.should eq(["apiversion", "author", "color", "head", "tail", "version"])
end
end

it "returns 200 on /start" do
payload = File.read("./spec/fixtures/start.json")
post "/start", body: payload, headers: HTTP::Headers{"Content-Type" => "application/json"}
response.status_code.should eq 200
SUPPORTED_STRATEGIES.each do |strategy_name|
payload = File.read("./spec/fixtures/start.json")
post "/#{strategy_name}/start", body: payload, headers: request_headers
response.status_code.should eq 200
end
end

it "returns 200 on /move" do
payload = File.read("./spec/fixtures/move.json")
post "/move", body: payload, headers: HTTP::Headers{"Content-Type" => "application/json"}
response.status_code.should eq 200
it "returns 200 and valid move on /move" do
SUPPORTED_STRATEGIES.each do |strategy_name|
payload = File.read("./spec/fixtures/move.json")
post "/#{strategy_name}/move", body: payload, headers: request_headers
response.status_code.should eq 200
selected_move = Hash(String, String).from_json(response.body)["move"]
(Strategy::VALID_MOVES.includes?(selected_move)).should be_true
end
end

it "returns 200 on /end" do
payload = File.read("./spec/fixtures/end.json")
post "/end", body: payload, headers: HTTP::Headers{"Content-Type" => "application/json"}
response.status_code.should eq 200
SUPPORTED_STRATEGIES.each do |strategy_name|
payload = File.read("./spec/fixtures/end.json")
post "/#{strategy_name}/end", body: payload, headers: HTTP::Headers{"Content-Type" => "application/json"}
response.status_code.should eq 200
end
end
end
56 changes: 30 additions & 26 deletions src/app.cr
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,17 @@ require "../config/**"
require "./models/**"
require "./jobs/**"

add_context_storage_type(Strategy::Base)
persist_to_db = ENV["DISABLE_DB_PERSIST"]?.nil?
selected_strategy = ENV["STRATEGY"] ||= "RandomValid"
Log.info { "Selected strategy: #{selected_strategy}" }
not_found_response = "
<p style='margin-top: 100px;'>
<center>
Strategy Not Found.
<br><br>
<a href='https://github.com/fdocr/crystalsnake'>Read usage details</a>
</center>
</p>
"

macro persist_turn!
PersistTurnJob.new(
Expand All @@ -25,10 +33,23 @@ def truncate_uuid(str)
end

before_all do |env|
env.response.content_type = "application/json" unless env.request.path =~ /\/games?/
next if env.request.path =~ /\/games?/

env.response.content_type = "application/json"
next if env.params.json.empty?

context = BattleSnake::Context.from_json(env.params.json.to_json)
strategy = Strategy.build(env.params.url["strategy"], context)
halt env, status_code: 404, response: not_found_response if strategy.nil?
env.set("strategy", strategy)
end

get "/strategy_not_found" do |env|
halt env, status_code: 404, response: not_found_response
end

get "/" do
# Battlesnake API Endpoints
get "/:strategy" do |env|
{
"apiversion": "1",
"author": "fdocr",
Expand All @@ -39,39 +60,22 @@ get "/" do
}.to_json
end

post "/start" do |env|
context = BattleSnake::Context.from_json(env.params.json.to_json)
post "/:strategy/start" do |env|
persist_turn!
end

post "/move" do |env|
context = BattleSnake::Context.from_json(env.params.json.to_json)
post "/:strategy/move" do |env|
persist_turn!

case selected_strategy
when "RandomValid"
move = Strategy::RandomValid.new(context).move
when "BlastRandomValid"
move = Strategy::BlastRandomValid.new(context).move
when "ChaseClosestFood"
move = Strategy::ChaseClosestFood.new(context).move
when "ChaseRandomFood"
move = Strategy::ChaseRandomFood.new(context).move
when "CautiousCarol"
move = Strategy::CautiousCarol.new(context).move
else
move = Strategy::RandomValid.new(context).move
end

move = env.get("strategy").as(Strategy::Base).move
res = { "move": move, "shout": "Moving #{move}!" }
res.to_json
end

post "/end" do |env|
context = BattleSnake::Context.from_json(env.params.json.to_json)
post "/:strategy/end" do |env|
persist_turn!
end

# DB-persisted games
get "/games" do |env|
offset = (env.params.query["page"]? || 0).to_i * 50
count = Turn.where { _path == "/end" }.count
Expand Down
33 changes: 28 additions & 5 deletions src/strategy/base.cr
Original file line number Diff line number Diff line change
@@ -1,11 +1,34 @@
# Abstract class of all strategies. They're all initialized with a *@context*
# and their entrypoint is the `#move` method
abstract class Strategy::Base
def initialize(@context : BattleSnake::Context)
module Strategy
VALID_MOVES = ["up", "left", "down", "right"]

def self.build(name, context)
case name
when "random"
Strategy::Random.new(context)
when "random_valid"
Strategy::RandomValid.new(context)
when "blast_random_valid"
Strategy::BlastRandomValid.new(context)
when "chase_closest_food"
Strategy::ChaseClosestFood.new(context)
when "chase_random_food"
Strategy::ChaseClosestFood.new(context)
when "cautious_carol"
Strategy::CautiousCarol.new(context)
else
nil
end
end

# Returns the move (direction) to chose based on the *@context*
def move
"up"
abstract class Base
def initialize(@context : BattleSnake::Context)
end

# Returns the move (direction) to chose based on the *@context*
def move
"up"
end
end
end
2 changes: 1 addition & 1 deletion src/strategy/random.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# A strategy that chooses a random direction to move without any considerations
class Strategy::Random < Strategy::Base
def move
["up", "left", "down", "right"].sample
VALID_MOVES.sample
end
end

0 comments on commit 39c0b03

Please sign in to comment.