# Ruby Quiz #27, Knight's Travails
#
# Author: Kero van Gelder
# License: LGPL, see http://www.gnu.org/licenses/lgpl.html
#
# Given: Chess board, start_pos, finish_pos and forbidden squares
# Find: a shortest route from start to finish, for a knight, without using the
# forbidden squares.
#
# Observations:
# - shortest path requires Dijkstra, but all distances are 1, so we do not need
# a priority queue, we can use a regular queue
# - breadth first search like this (dynamic programming style, too) keeps
# pointers (steps) towards the point where you start the algorithm, so we
# have to start at the finish. Quite normal for Dijkstra, now that I think of
# it...
#
# Apologies for:
# - not checking the input (ignoring commas happily, no spaces)
# - the use of @board and @q which are pseudo-global variables
# - not returning an array, but printing the result (hey, you left the
# *content* of the array undescribed; mine is like [[?b, 7], [?c, 5]],
# perhaps you need ["b7", "c5"] ?) Printing is with spaces before the commas,
# too? How tasteless :P
# Input helper
def decode_pos(str)
[str[0], str[1,1].to_i]
end
# Used in breadth first search
def try_pos(c, d, rc, rd)
(c >= ?a and c <= ?h) or return
(d >= 1 and d <= 8) or return
unless @board[c][d]
@board[c][d] = [rc, rd]
@q << [c, d]
end
end
start_pos, finish_pos, *forbidden = *ARGV
@board = {}
(?a..?h).each { |c| @board[c] = [] }
forbidden.each { |pos|
c, d = decode_pos(pos)
@board[c][d] = :forbidden
}
fc, fd = decode_pos(finish_pos)
@board[fc][fd] = :finish
@q = [[fc, fd]]
sc, sd = decode_pos(start_pos)
while not @q.empty?
c, d = *@q.shift
break if c == sc and d == sd
try_pos(c-2, d-1, c, d)
try_pos(c-2, d+1, c, d)
try_pos(c-1, d-2, c, d)
try_pos(c-1, d+2, c, d)
try_pos(c+1, d-2, c, d)
try_pos(c+1, d+2, c, d)
try_pos(c+2, d-1, c, d)
try_pos(c+2, d+1, c, d)
end
# It is a good defensive programming habit to look whether you actually found a
# solution (and don't check q.empty? as I did, 'coz the queue will be empty
# when the search looked at all 64 squares for a route from e.g. a8 to h1).
if @board[sc][sd]
route = []
rc, rd = sc, sd
while rc != fc or rd != fd
next_pos = @board[rc][rd]
route << "#{next_pos[0].chr}#{next_pos[1]}"
rc, rd = *next_pos
end
puts "[ #{route.join" , "} ]"
else
puts nil
end