Here's my solution for the nonce <G>.

I've probably been focusing more on packaging this and less on
optimizing it than I should.

The approach is a recursive search.
0. Place the first number
1. Pick the best available direction and place the next number there.
2. Try to place the rest of the numbers (by recursive call to step 2)
3. If 2 fails , remove the number placed in step 1, pick the next
direction and try again.
4. If we run out of directions, report failure to the calling recursion.

Picking the best available direction is based on Eliott's suggested
heuristic of trying to use squares which have less available next
steps first.

I did this in two files.  pen_and_paper.rb is the main program, it
does parameter parsing, and can benchmark or profile the code as
dictated by the parameters.  Right now, it only searches for a
solution starting in row 0, column 1.  This should be a parameter and
I should also try alternatives if the starting position doesn't work.

The second file ruby_quiz_90.rb is a module containing the classes
Board, Player and Move.

Most of the time actually still seems to be spent in checking whether
a square is occupied or not.  My initial stab at this did bounds
checking since ruby Arrays just return nil for out of bounds indices.
The current iteration uses 0 instead of nil in empty cells and uses
guard rows above and below the board in order to avoid bounds
checking.  I might next try eliminating the guard rows and just define
[] for NilClass to return nil.

Anyway, here's what I've got now
=== pen_and_paper.rb ===
require 'benchmark'
include Benchmark

require 'optparse'

require 'ruby_quiz_90'

board_size = 5
benchmark = false
profile = false
iterations = 1

opts = OptionParser.new
opts.on('-sBOARDSIZE',
        '-s BOARDSIZE',	
        '--size=BOARDSIZE',
	'--size BOARDSIZE',
	'Height and width of board, default is 5x5',
	Integer)  {|val| board_size = val }
opts.on('--bench',
	'Specifies that a benchmark will be done.'
	)  { | val | benchmark = true }

opts.on('--profile',
	'Profile execution') {  profile = true}

opts.on('--iterations=COUNT',
	'--iterations COUNT',
	'COUNT specifies the number of iterations',
        'for benchmarking or profiling',
	Integer) { | val | iterations = val }

opts.on('--help',
	'produce this help text') { puts opts.to_s
		exit }
				
begin
	opts.parse(ARGV)
rescue
       puts opts.to_s
       exit
end

puts "board_size is #{board_size}x#{board_size}"
puts "benchmark=#{benchmark}, profile=#{profile}, iterations=#{iterations}"

player = nil

if benchmark
	bm(iterations) do | test |
		test.report("#{board_size}x#{board_size}:") do
		player = RubyQuiz90::Player.new(board_size)
		player.play(0,1)
		end
	end
else
	if profile
		require 'profile'
	end
	iterations.times do
		player = RubyQuiz90::Player.new(board_size)
	       	player.play(0,1)
	end
end
puts player.board
=== ruby_quiz_90.rb ===
module RubyQuiz90
	class Board

		# Changes:
		#    pad row with 3 guard rows above and below
		#      this allows available? to avoid having to catch
		#      nil not understanding []
		def initialize(size)
			@row = Array.new(size+6)
		        (0..2).each { | i | @row[i] = Array.new(size) }
			(3..size+2).each { | i | @row[i] = Array.new(size, 0) }
			(size+3..size+5).each { | i | @row[i] = Array.new(size) }
			@size = size
#			@range = (0..size-1)
		end

		def take(row, col, value)
			@row[row+3][col] = value
		end

		def available?(row, col)
			@row[row+3][col] == 0 rescue false
		end

		def take_back(row, col)
			@row[row+3][col] = 0
		end

		def value(row, col)
			@row[row+3][col]
		end

		def to_s
			elem_width = ((@size * @size).to_s.length) + 1
			header = '+-' + ('-' *(@size * elem_width)) + '+'
			result = header.dup
			(0...@size).each do | i |
				row = @row[i+3]
				result << "\n|"
				row.each do | elem |
				           result << ((elem == 0) ?
						      (" " * elem_width) :
						      sprintf("%#{elem_width}d", elem))
				end
			result << ' |'
			end
			result << "\n"
			result << header
		end

		def self.testboard(s)
			b = new(s)
			(0..s-1).each do | r |
				(0..s-1).each do | c |
				b.take(r, c, c + 1 + (r*s))
				end
			end
			b
		end

	end

	class Player
		Direction_vectors = [
			[0, 3],   #move east
			[2, 2],   #move southeast
			[3, 0],   #move south
			[2, -2],  #move southwest
			[0, -3],  #move west
			[-2, -2], #move northwest
			[-3, -3], #move north
			[-2, 2],  #move northeast
		]

		#		No_more_moves = Direction_vectors.length
		#		
		#		Last_move = (No_more_moves) - 1

		def initialize(size, debug = 2)
			@board = RubyQuiz90::Board.new(size)
			@last = size * size
			@debug = debug
		end

		def play (start_row=nil, start_col=nil)
			row = start_row || rand(@board.size)
			col = start_col || rand(@board.size)
			@board.take(row, col, 1)
			fill_all_from(start_row, start_col)
		end

		def board
			@board
		end

		# Fill the board after the last number was placed at r,c
		# If the board can be filled return true
		# If the board cannot be filled restore the board to its
		# initial state at the time of this call and return false
		def fill_all_from(r, c)

			value = @board.value(r,c) + 1

			puts "Trying to fill board starting with #{value}, from #{r},
#{c}}" if @debug > 2
			puts @board if @debug >= 3
			return true if value > @last # our work is done!

			#now try to fill the next value
			optimized_moves(r, c).each do | move |
				row = move.row
			        col = move.col
				puts "Trying to move placing #{value} at #{row}, #{col}" if @debug > 2
				@board.take(row, col, value)
				puts "Placed #{value} at #{row}, #{col}" if @debug > 2
				if fill_all_from(row, col)
						return true
				else
					@board.take_back(row, col)
				end
			end

			# if we get here, it's time to back out
			puts "All trials failed at #{value}" if @debug > 2
			puts @board if @debug > 2
			return false
		end
                # return a list of moves from row, col optimized so that
		# squares with more possible further moves come first
		def optimized_moves(row, col)
			moves = []
			Direction_vectors.each do | dx, dy |
				r = row + dy
			        c = col + dx
				if @board.available?(r,c)
					moves << Move.new(r, c, availability_count(r, c))
				end
			end
			moves.sort!
			moves	
		end
		
                # return the number of available squares from row, col
		def availability_count(row, col)
			Direction_vectors.inject(0) do | sum, (dx, dy)|
			       	@board.available?(row+dy, col+dx) ? sum + 1 : sum
			end
		
		end
	end

	class Move
		attr_accessor :row, :col, :count
		def initialize(r, c, count)
			@row = r
			@col = c
			@count = count
		end

		# a move is greater (should come later) if it has a lower
		# available move count than another
		def <=>(move)
			count - move.count
		end

		def to_s
			"Count=#{@count}, row=#{@row}, col=#{@col}"
		end
	end	
		
end
-- 
Rick DeNatale

http://talklikeaduck.denhaven2.com/