Hi,

Here is my first submission to a ruby quiz :)
I tried to make the A* algorithm as re-usable as possible (class
A_star).

As a parameter it gets an instance of the class Problem that need to
implement :
- neighbors(node) that returns a list of the neighbor nodes and their
distance to that node
- heuristic(node) that give to heuristical distance to the goal
- end_node?(node) that returns true if node is the goal

Thus the A_star class doesn't need to know anything about the problem
(it doesn't even care about what are nodes)

To run it just call the script with the map in stdio; it will print the
solution.

The implementation is rather simple and not optimized (the heuristic
and A_star#add_to_open are critical), and not very nice.

However, here is the code :

class Problem
  attr_reader :start, :goal

  def initialize
    @data = []
    STDIN.readlines.each do |line|
      @data << line.chomp
      start = line =~ /@/
      @start = [start, @data.length-1] if start != nil
      goal = line =~ /X/
      @goal = [goal, @data.length-1] if goal != nil
    end
  end

  def cost(x,y)
    if x < 0 || x >= @data.length || y < 0 || y >= @data[0].length
      nil
    elsif @data[x][y..y].match(/[@\.X]/)
      1
    elsif @data[x][y..y] == '*'
      2
    elsif @data[x][y..y] == '^'
      3
    else
      nil
    end
  end

  # Returns the list of all the neighbors
  def neighbors node
    neighbors_list = []
    x,y = node
    for i in -1..1 do
      for j in -1..1 do
	if i != 0 || j != 0
	  cost = cost(x+i, y+j)
	  neighbors_list << [[x+i, y+j],cost] unless cost == nil
	end
      end
    end
    neighbors_list
  end

  def heuristic node
    x, y = node
    gx, gy = @goal
    (gx-x)**2 + (gy-y)**2
  end

  def end_node? node; node == @goal; end

  def print_solution path
    data = @data
    path.each{|x,y| data[x][y] = '#'}
    data.each{|line| puts line}
  end
end

class A_star
  attr_reader :closed

  def initialize problem
    @problem = problem
    @open = [problem.start]
    @closed = []
    @f = {problem.start => 0} # Estimated cost
    @g = {problem.start => 0} # Cost so far
  end

  def run
    while @open != []
      node = @open.pop
      @closed << node
      return @closed if @problem.end_node? node

      @problem.neighbors(node).each do |n|
	neighbor, cost = n
	add_to_open(neighbor, @g[node] + cost)
      end
    end
    return nil
  end

  def add_to_open(node, cost)
    unless @closed.include? node
      if @open.include? node
	@g[node] = cost if cost < @g[node]
      else
	@open << node
	@g[node] = cost
      end
      @f[node] = @g[node] + @problem.heuristic(node)
      @open.sort! {|a,b| @f[b] <=> @f[a]}
    end
  end
end

pb = Problem.new
test = A_star.new pb
pb.print_solution test.run



Ruby Quiz wrote:
> The three rules of Ruby Quiz:
>
> 1.  Please do not post any solutions or spoiler discussion for this quiz until
> 48 hours have passed from the time on this message.
>
> 2.  Support Ruby Quiz by submitting ideas as often as you can:
>
> http://www.rubyquiz.com/
>
> 3.  Enjoy!
>
> Suggestion:  A [QUIZ] in the subject of emails about the problem helps everyone
> on Ruby Talk follow the discussion.  Please reply to the original quiz message,
> if you can.
>
> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
>
> by Steven Davidovitz
>
> The A* (A-Star) search algorithm is a path-finding algorithm with many uses,
> including Artificial Intelligence for games. The search builds the route from
> tile to tile until it reaches the goal. To help choose the best possible tile
> the algorithm will use an equation that takes into account the distance from the
> tile to the goal and the cost of moving to that tile.
>
> Let's work through an example, so you can see how this comes together.
>
> 	Movement Cost for Terrain:
> 	  Non-walkable:
> 	    N/A = Water (~)
> 	  Walkable:
> 	    1 = Flatlands (. or @ or X)
> 	    2 = Forest (*)
> 	    3 = Mountain (^)
>
> 	Test Map:
> 	  @*^^^    @ = User start
> 	  ~~*~.    X = The goal tile
> 	  **...
> 	  ^..*~
> 	  ~~*~X
>
> Step 1:  Search the surrounding tiles for walkable choices.
>
> The only possible tile for the fist step is to the right: a forest (*) tile.
>
> Step 2:  Go through that list finding the cost of movement for each of those
> choice tiles.  The cost of movement is the path cost so far, plus the cost to
> move to the tile being considered.
>
> Our cost so far is 0, since we just started.  The cost of a forest is 2 so the
> total cost of this move is 2 (0 + 2).
>
> Step 3:  Determine the distance from the choice tiles to the goal and add that
> to the cost from Step 2 to find the score for each tile.
>
> You can calculate the distance from the tile to the goal using Manhattan
> distance formula |x1 - x2| + |y1 - y2|.  For our example that is:
>
> 	|1 - 4| + |0 - 4| = |-3| + |-4| = 7
>
> Knowing that we can produce the final score for this move:
>
> 	score = cost (2) + distance to goal (7) = 9
>
> Step 4: Choose the best tile to move to by comparing the score of the
> surrounding tiles and choosing the lowest.
>
> Step 6:  Repeat Steps 1-4 until you reach the goal tile.  Be careful to prevent
> checking tiles twice and/or circling back.
>
> 	Test Map Solution:
> 	  ##^^^    # = Best path
> 	  ~~#~.
> 	  **.#.
> 	  ^..#~
> 	  ~~*~#
>
> When you have a working solution, try it out on this move involved map:
> 
> 	http://www.rubyquiz.com/large_map.txt