```Here is my solution. On average, for a 10x10 grid with a 10 sized shape, it
will plot the shape in about 31.7 guesses. If you change the neighbors
definition to exclude diagonals, it does it in around 24 guesses.  Worst
case scenario is of course the size of the grid.

I spent a fair amount of effort trying to collect statistical information
about the shapes I am generating, but experiments have shown that even
though there is a certain amount of non-uniformity in the distribution
across the board, it is not enough to gain a significant advantage in the
total number of guesses (probably less than a half a guess.)

--Chris

# The Shape class accomplishes a couple of things.
# First, it randomly generates shapes and provides some
# accounting, keeping track of how many lookups one does.

# Next, it acts as a kind of geometry manager for the board.
# (each, each_neighbors, get_neighbors. Changing the definition
# of each_neighbors can change the rules for what it means for

# Finally, it keeps track of occupancy statistics for the board
# over successive regenerations of the shape.
class Shape
def initialize(hh = 10, ww = 10, ll = 10)
@h,@w,@l=hh,ww,ll #height,width, shape size
@regens = @count = 0 #number of times shape generated, times dereferenced

@stats = [] #used to count frequency of occupancy

# seed the stats array with all 1s.
h.times { |y| @stats[y] = [ 1 ] * w }
@regens = h*w/(1.0 * l)
rebuild
end

def each_neighbors(xxx,yyy=nil)
if xxx.kind_of?( Array ) then
x,y = xxx,xxx
else
x,y = xxx,yyy
end
lowx,hix = [x-1,0].max, [x+1,w-1].min
lowy,hiy = [y-1,0].max, [y+1,h-1].min
(lowy..hiy).each do |yy|
(lowx..hix).each do |xx|
yield([xx,yy]) unless x==xx and y==yy
end
end
end

def get_neighbors(x,y=nil)
result = []
each_neighbors(x,y) do |coords|
result.push(coords)
end
return result
end

def each
h.times { |y| w.times { |x| yield [x,y] } }
end

def rebuild()
@regens += 1 #increment the build count
@count = 0 #clear the deref count

#initialize board to contain only spaces
@board=[]
h.times { |y| @board[y] = [" "] * w  }

neighbors = []
shape = []

l.times do
if neighbors.length == 0 then
# first piece - place it anywhere
x,y = [w,h].map {|z| (rand*z).to_i}
else
# subsequent pieces - pick a random neighbor
x,y = neighbors[ (rand * neighbors.length).to_i ]
end
@board[y][x] = "@" #mark occupancy
@stats[y][x] += 1  #track occupancy

shape |= [ [x,y] ] # add choice to list of shape coords

# update neigbors
neighbors -= [[x,y]]
neighbors |= get_neighbors(x,y) - shape
end
return self
end

def to_s
return @board.map { |x| x.join "" }.join("\n") + "\nTotal Lookups:
#{@count}\n"
end

def [](xx,yy=nil)
if xx.kind_of?(Array) then
x,y = xx,xx
else
x,y = xx,yy
end
@count += 1
return @board[y][x]
end

def stats
norm_stats = []
h.times do |y|
norm_stats[y] = []
w.times do |x|
# correct stats for rotation and reflection symmetry
norm_stats[y][x] = (@stats[y][x] + @stats[-y-1][x] + @stats[-y-1][-x-1]
+ @stats[y][-x-1] + @stats[x][y] + @stats[-x-1][y] + @stats[-x-1][-y-1] +
@stats[x][-y-1])/(8.0*@regens)
end
end
return norm_stats
end

def statstring
return stats.map { |y| y.map { |x| "%0.3f" % x}.join(" ") }.join("\n")
end
end

class ShapePlot
def initialize(r = Shape.new, c=0)
@shape = r
c.times {@shape.rebuild}
@stats = @shape.stats
@plays = 0
@count = 0
reset
end

def reset
@guesses = []
@members = []
@neighbors = []
@choices = []
@shape.each { |coords| @choices << coords }
end

def to_s
board = []
@shape.each { |x,y| @shape.h.times { |y| board[y] = [ " " ] * @shape.w  }}
@neighbors.each { |x,y| board[y][x] = "." }
@guesses.each { |x,y| board[y][x] = "x" }
@members.each { |x,y| board[y][x] = "@" }
header = "+" + ("-"*(@shape.w*2-1)) + "+\n"
return header + "|" + board.map{|x| x.join(" ")}.join("|\n|") + "|\n" +
end

def choose_random(p_list)
sum = 0
#choose from among the choices, probibilistiacally, weighted by
#the occupation probabilities
p_list.each { |p,c| sum += p }
r = rand * sum
p_list.each do |p,c|
r -= p
return c if r <= 0
end

# shouldnt ever be here, but return the last one anyway
puts p_list
puts @shape
return p_list[-1]
end

def build_weighted(list)
return list.map {|x| [ @stats[x][x], x ]}
end

def guess_none_known
return choose_random( build_weighted( @choices ) )
end

def guess_some_known
choices = @neighbors - @guesses
return choose_random( build_weighted( choices ) )
end

def found_a_hit(coords)
# update the members of the shape
@members += [ coords ]
x,y=coords
# calculate the neigbors of the new piece
@neighbors += @shape.get_neighbors(x,y)
# the intersection of @members and @neighbors should be empty, but
# we are subtracting out @guesses, which includes all @members when
# we go to pick a choice list anyway...
end

def guess
#choose a square to look at
# if we know some part of the shape is known, restrict guesses
# to neighbors of the stuff we know
#if we dont know any of them yet, choose from the whole board
x,y = coords = (@members.length > 0 ? guess_some_known : guess_none_known)
@guesses += [coords]
@choices -= [coords]
if @shape[x,y]=="@" then
found_a_hit(coords)
end
end

def play( draw = false)
reset

# upldate statistics before we update the shape
@stats = @shape.stats
@shape.rebuild
while @members.length < @shape.l
guess
puts self if draw
end

@plays +=1
@count += @shape.count
return @shape.count
end

def report
mean = @count / (1.0 * @plays)
puts "After #{@plays} plays, the mean score is: #{mean}"
end
end

q = ShapePlot.new
q.play(true)
999.times { q.play }
q.report
```