lib/battlesnake/board.rb in battlesnake-0.1.2 vs lib/battlesnake/board.rb in battlesnake-0.1.3
- old
+ new
@@ -50,30 +50,35 @@
end
##
# Whether the supplied location is occupied.
#
- # @param *coordinates [Location,Hash,String,Array] can be specified as a _Location_ object,
- # hash containing x/y keys, JSON string of such a hash, or a pair of x,y coordinates expressed
- # as a 2-element array or two separate parameters.
+ # @param [Location] location being checked for occupancy.
#
# @return [Boolean] true if location is occupied by snakes, food, hazards, etc.
- def occupied?(*coordinates)
- location = coordinates.first.is_a?(Location) ? coordinates.first : Location.new(*coordinates)
+ def occupied?(location)
occupied_locations.include?(location)
end
##
+ # Where the supplied location falls within the boundaries of the board.
+ #
+ # @param [Location] location being tested.
+ #
+ # @return [Boolean] true if location is within the boundaries of the board.
+ def on_board?(location)
+ location.x >= 0 && location.y >= 0 && location.x < width && location.y < height
+ end
+
+ ##
# Whether the supplied location is available (unoccupied).
#
- # @param *coordinates [Location,Hash,String,Array] can be specified as a _Location_ object,
- # hash containing x/y keys, JSON string of such a hash, or a pair of x,y coordinates expressed
- # as a 2-element array or two separate parameters.
+ # @param [Location] location being tested for availability.
#
# @return [Boolean] true if location is available (unoccupied by snakes, food, hazards, etc).
- def available?(*coordinates)
- !occupied?(*coordinates)
+ def available?(location)
+ on_board?(location) && !occupied?(location)
end
##
# List of directions (up, down, left, right) available for moving from given _Location_.
#
@@ -81,9 +86,133 @@
#
# @return [Array<String>] list of direction strings ("up", "down", "left", "right")
def available_directions(location)
Location::DIRECTIONS.select do |direction|
available?(location.move(direction))
+ end
+ end
+
+ ##
+ # List of valid, consecutive paths from one location to the next. Paths may not:
+ #
+ # - wander outside board boundaries.
+ # - use the same location more than once.
+ # - contain occupied locations, EXCEPT the start/end locations.
+ #
+ # The exception for start/end locations allows us to generate paths, for example, from a snake
+ # to a food location, without having to calulate the starting/ending permutations ourselves.
+ #
+ # @param from [Location] starting location, may be occupied
+ # @param to [Location] starting location, may be occupied
+ #
+ # @return [Array<Path>] a list of paths, which themselves are lists of consecutive, valid locations.
+ def find_path(from, to, max_distance: nil)
+ @paths = []
+ @ideal_path_size = from.distance(to) + 1
+ @shortest_path_size = max_distance || @ideal_path_size
+ @ideal_path_size_found = false
+
+ recursive_paths(from, to, [from])
+
+ @paths.select{ |path| path.size == @shortest_path_size }.first
+ end
+
+ def recursive_paths(from, to, path)
+ head = path.last
+
+ # give up if path is too long already.
+ return [] if path.size > @shortest_path_size || @ideal_path_size_found
+
+ # if we've made it to "to", we have a successful candidate path.
+ if head.as_json == to.as_json
+ @paths << path
+ @shortest_path_size = [@shortest_path_size, path.size].min
+ @ideal_path_size_found = true if path.size == @ideal_path_size
+
+ return path
+ end
+
+ available_directions(head).sort_by do |direction|
+ # prefer to continue in same direction
+ neck = path[-2]
+
+ if neck && neck.direction(head) == direction
+ 0
+ else
+ rand
+ end
+ end.map do |direction|
+ # convert direction string to a location
+ head.move(direction)
+ end.map do |location|
+ # convert location to a full path
+ path + [location]
+ end.select do |candidate|
+ # don't allow paths that overlap themselves
+ candidate.size == candidate.uniq(&:as_json).size
+ end.each do |candidate|
+ # recurse into remaining candidate paths
+ recursive_paths(from, to, candidate)
+ end
+ end
+
+ def shorty(location)
+ "#{location.x}:#{location.y}"
+ end
+
+ def shorties(list)
+ case list.first
+ when Array
+ list.map{ |l| shorties(l) }
+ when Location
+ list.map{|x| shorty(x)}.join(' ')
+ end
+ end
+
+ def print_grid(path, prefix: ' ')
+ max_x = path.map(&:x).max
+ max_y = path.map(&:y).max
+
+ (0..max_y).each do |row|
+ y = max_y - row
+
+ cols = (0..max_x).map do |x|
+ loc = Location.new(x, y)
+
+ if path.include?(loc)
+ after = path.index{ |l| l.as_json == loc.as_json} + 1
+
+ if after >= path.size
+ "\u00d7"
+ elsif loc.as_json == path.first.as_json
+ case loc.direction(path[after])
+ when 'up'
+ "\u21a5"
+ when 'down'
+ "\u21a7"
+ when 'left'
+ "\u21a4"
+ when 'right'
+ "\u21a6"
+ end
+ else
+ case loc.direction(path[after])
+ when 'up'
+ "\u2191"
+ when 'down'
+ "\u2193"
+ when 'left'
+ "\u2190"
+ when 'right'
+ "\u2192"
+ end
+ end
+ else
+ 'O'
+ end
+ end.join(' ')
+
+ puts "#{prefix} #{cols}"
end
end
end
end
\ No newline at end of file