I am a bit late with my solution, but it's my first Ruby Quiz, and my
largest ruby program so far, so I'll send it in anyway... I didn't know
the problem, but found the same solution everybody else did. I have not
used recursion in the solution, in order to not compute the low-level
solutions over and over again. Instead I compute the 'overlays'
(partial solutions) from the smallest to the largest, then I rotate
them properly, and finally put them all on the board.
I have some requests, if anyone would be so kind as to take a look:
1) Style in general. What have I done that is 'unrubyish'? Are there
more elegant ways of doing things?
2) If a want a 'mathematical' vector, that treats + as addition and not
as concatenation, what can I do? I added the method 'add!' to class
Array for this, but it would be nice to know if there is a standard
facility for this.
3) I gave my Board class a copy method. Is there a better way to copy
an array than concatenating the source to an empty ([]) target? I found
no 'copy' method in the docs.
The code:
===============================================
class Array
def add!(other)
if other.length != length
somethingswrong
end
each_index { |i| self[i] += other[i] }
self
end
end
# Represents the board. Handles rotation and board manipulation.
class Board
attr_reader :n, :squares
attr_writer :n, :squares
# The board size will be 2**r by 2**r.
def initialize(r = 0, val = 0)
@n = 2**r
@squares = Array.new(@n**2, val)
end
# Make a deep copy
def copy
retval = Board.new
retval.n = @n
retval.squares = []
retval.squares.concat(@squares)
retval
end
# Prettyprinting the board
def to_s
output = "Board is #{@n} by #{@n}.\n"
@n.times do |i|
@squares[@n*i...@n*(i+1)].each do |s|
output += sprintf("%5d ", s)
end
output += "\n"
end
output += "\n"
end
# Rotate the board by 90 degrees, CCW.
def rotate!
rotated = Array.new(@n**2, 0)
@n.times do |i|
@n.times do |j|
rotated[@n*i + j] = @squares[@n*j + (@n-i-1)]
end
end
@squares = rotated
self
end
def increment!(val = 1)
@squares.each_index { |i| @squares[i] += val if @squares[i] > 0 }
self
end
# Set a square to a particular value
def set!(row, col, val)
@squares[@n*row + col] = val
self
end
# Overlay a sub-board over this one, at a specific location.
# The (row, col) coordinates should be the upper left corner
# of the area to overlay. Overlaying means we simply add the
# values of the inserted board to the current board values.
# We do not check that the insertion is valid wrt size of
# sub-board, position, etc.! So be careful...
def insert!(row, col, subboard)
sn = subboard.n
row.upto(row + sn - 1) do |r|
rowtoadd = subboard.squares[(r-row)*sn...(r-row+1)*sn]
target = @squares[(r*@n + col)...(r*@n + col + sn)]
# puts "----" + target.to_s
# puts "++++" + rowtoadd.to_s
target.add!(rowtoadd)
@squares[(r*@n + col)...(r*@n + col + sn)] = target
end
self
end
end
class TilingPuzzle
def initialize(r)
@board = Board.new(r)
@r = r
end
def to_s
@board.to_s
end
def solve!(row, col)
# Make some overlays of increasing size
overlays = []
# Initialize the first overlays
overlays[0] = Board.new(0, 0)
overlays[1] = Board.new(1, 1)
overlays[1].set!(0, 0, 0)
# Now build every successive overlay
2.upto(@r) do |i|
# Every overlay consists of four copies of the previous one,
# incremented by the number of L-tiles in it every time.
overlays[i] = Board.new(i)
o = overlays[i-1].copy
inc = 4**(i-2)
pl = 2**(i-1)
o.increment!(inc)
overlays[i].insert!(pl/2, pl/2, o)
o.increment!(inc)
overlays[i].insert!(pl, pl, o)
o.rotate!.increment!(inc)
overlays[i].insert!(0, pl, o)
o.rotate!.rotate!.increment!(inc)
overlays[i].insert!(pl, 0, o)
end
# Now we can simply tile every overlay around the empty spot,
# as long as we rotate them properly. Let's first compute the
number
# of rotations necessary
rots = [0]
@r.downto(1) do |i|
# Can I make this more elegant?
if (row >= 2**(i-1)) && (col < 2**(i-1))
rots[i] = 1;
elsif (row >= 2**(i-1)) && (col >= 2**(i-1))
rots[i] = 2;
elsif (row < 2**(i-1)) && (col >= 2**(i-1))
rots[i] = 3
else
rots[i] = 0
end
# puts "row = #{row}, col = #{col}"
row = row % (2**(i-1))
col = col % (2**(i-1))
end
# Now, let's put everything in place!
offsetrow, offsetcol = 0, 0
@r.downto(1) do |i|
(rots[i]).times { overlays[i].rotate! }
@board.insert!(offsetrow, offsetcol, overlays[i])
offsetrow += 2**(i-1) if [1, 2].include?(rots[i])
offsetcol += 2**(i-1) if [2, 3].include?(rots[i])
end
self
end
end
size = 4
row = rand(2**size)
col = rand(2**size)
puts TilingPuzzle.new(size).solve!(row, col)
====================================================
Atgeirr FlRasmussen