Here is my solution that tries to make all possible arrangements and
displays solutions with the maximum number of overlaps.

Eric

----

Are you interested in on-site Ruby training that uses well-designed,
real-world, hands-on exercises? http://LearnRuby.com

====

# Solution to Ruby Quiz #149 provided by LearnRuby.com
#
# The code to this solution can also be found at:
#    http://learnruby.com/examples/ruby-quiz-149.shtml
#
# This solution tries to find all solutions and then selects those
# with the maximum number of overlaps.  Good words to try are
# "absolvable" and "chincherinchee".  It is implemented with a
# recursive search.

# The Grid class is used to hold a grid where each cell contains a
# character or, if it's empty, nil.  Furthermore, the grid keeps track
# of how many overlaps there are in the word that spins within it,
# since that's the measure of how good the arrangement is.
class Grid
  attr_reader :overlaps

  def initialize
    @grid = Array.new
    @overlaps = 0
  end

  def [](position)
    sub_array(position[0])[position[1]]
  end

  def []=(position, value)
    sub_array(position[0])[position[1]] = value
  end

  def inc_overlaps
    @overlaps += 1
  end

  def dec_overlaps
    @overlaps -= 1
  end

  def to_s
    @grid.map do |row|
      if row
        row.map { |c| c || ' ' }.join('')
      else
        ''
      end
    end.join("\n")
  end

  def dup
    other = Grid.new
    @grid.each_with_index do |row, i|
      other.grid[i] = row.dup unless row.nil?
    end
    other.overlaps = @overlaps
    other
  end

  # Trim grid so it's as small as possible.  Assumes that filled areas
  # are contiguous, so that an empty row would not sit between
  # non-empty rows.
  def trim!
    @grid.delete_if { |row| row.nil? || row.all? { |c| c.nil? } }
    trim_columns = empty_columns
    @grid.each_index do |i|
      @grid[i] = @grid[i][trim_columns..-1]
      @grid[i].pop while @grid[i].last.nil?
    end
    self
  end


  protected

  attr_reader :grid
  attr_writer :overlaps

  # Utility method that returns the sub-array of @grid at index.
  # Since the sub-array may have not yet been created, will create it
  # if necessary.
  def sub_array(index)
    sub_array = @grid[index]
    sub_array = @grid[index] = Array.new if sub_array.nil?
    sub_array
  end

  # returns the number of entries at the start of each sub-array that
  # are empty, so the grid can be "left-trimmed".
  def empty_columns
    index = 0
    index += 1 while @grid.all? { |row| row.nil? || row[index].nil? }
    index
  end
end


# This module contains some convenience methods to move a position in
# a direction, and to figure out a new direction when a right turn is
# made.
module Direction
  RightTurns = {
    [0, 1] => [1, 0],
    [1, 0] => [0, -1],
    [0, -1] => [-1, 0],
    [-1, 0] => [0, 1],
  }

  # returns the rightwards direction
  def self.rightwards
    [0, 1]
  end

  def self.turn_right(direction)
    RightTurns[direction]
  end

  def self.move(position, direction)
    [position[0] + direction[0], position[1] + direction[1]]
  end
end


# Recursively tries placing characters on grid until the full word is
# placed or a conflict is found.  During the recursion we try two
# options -- continuing in same direction or making a right turn.
# Results are accumulated in result parameter, which is an array of
# Grids.
def find_loops(word, letter_index, grid, position, direction, result)
  new_position = Direction.move(position, direction)

  character_placed = false

  # one of three conditions -- cell is empty, cell contains a letter
  # that matches the current word letter, cell contains a letter that
  # doesn't match current word letter
  if grid[new_position].nil?
    # empty cell -- put character in
    grid[new_position] = word[letter_index, 1]
    character_placed = true
  elsif grid[new_position] == word[letter_index, 1]
    # cell with matching character, increment overlap count
    grid.inc_overlaps
  else
    # cell with non-matching character, quit searching this branch
    return
  end

  # have we placed the entire word; add to results, otherwise recurse
  # continuting in same direction and after making a right turn
  if letter_index == word.size - 1
    result << grid.dup
  else
    # try going in the same direction and also try making a right turn
    find_loops(word, letter_index + 1, grid,
               new_position, direction, result)
    find_loops(word, letter_index + 1, grid,
               new_position, Direction.turn_right(direction), result)
  end

  # restore the grid or overlap count depending on earlier condition
  if character_placed
    grid[new_position] = nil
  else
    grid.dec_overlaps
  end
end


# This is the entry point to the loop-finding process.  It places
# first letter in a new grid and calls the recursive find_loops
# methods.
def solve(word)
  size = word.length
  grid = Grid.new

  # starting position allows room for word to go leftwards or upwards
  # without bumping into left or top edges; assumes initially heading
  # rightwards and only right turns can be made
  position = [size - 5, size - 4]
  direction = Direction.rightwards

  # put first character at starting position
  grid[position] = word[0, 1]

  # generate solutions in results parameter
  results = []
  find_loops(word, 1, grid, position, direction, results)
  results
end


# find the best results and display them
def display_best_results(results)
  # the best results have most overlaps, so sort so most overlaps
  # appear at front of array, and then use the first element's
  # overlaps as best score
  results = results.sort_by { |r| -r.overlaps }
  best_score = results.first.overlaps

  puts "Number of overlaps: #{best_score}"
  puts "=" * 40
  results.select { |r| r.overlaps == best_score }.each do |result|
    puts result.trim!
    puts "=" * 40
  end
end


if $0 == __FILE__
  if ARGV.size == 1
    display_best_results(solve(ARGV[0].downcase))
  else
    $stderr.puts "Usage: #{$0} _word_"
    exit 1
  end
end