Here's mine.  I use arrays within arrays as well.  I wrote my own loops
to do the folding.  I see now I could have done it much more
succinctly.  I went for all the extra credit.  Can unfold by noticing
the last element of the array hasn't been folded over and the first
element was previously unfolded.  The direction from the first to the
last element gives the fold direction.  Then just keep cutting off the
first part of the array.

#! /usr/bin/env ruby -w

=begin
Manages the matrix of values for folding:
  [[1], [2],
   [3], [4]]

  left_fold returns new matrix:
  [[1, 2],
   [3, 4]]
=end
class FoldMatrix

  attr_reader :values

  def initialize(values, rows, cols)
    @rows = rows
    @cols = cols
    @values = values
  end

  # Fold left side of matrix over to right returning new FoldMatrix
  def left_fold
    fold(:left)
  end

  # Fold right side of matrix over to left returning new FoldMatrix
  def right_fold
    fold(:right)
  end

  # Fold top of matrix down and return new FoldMatrix
  def top_fold
    fold(:top)
  end

  # Fold bottom of matrix up and return new FoldMatrix
  def bottom_fold
    fold(:bottom)
  end

  # Return the result of folding in flattened array
  def result
    if (@rows != 1 && @cols != 1)
      raise ArgumentError, "Paper not completely folded"
    end

    @values.flatten
  end

private

  # Return a matrix element
  def array_element(i, j)
    @values[i*@cols + j]
  end

  # Iterate through items in array by folded direction where direction
  # is one of :left, :right, :top, :bottom.  Iterates going left to
  # right then down.  Values are already in proper order top to
  # bottom.
  # Example:
  #   each_by_fold do |top, bottom|
  #     new_cell_value = top + bottom
  #   end
  def each_by_fold(fold)
    # make sure there are enough rows or columns to fold
    case fold
      when :left, :right
        if @cols <= 1
          raise ArgumentError,
            "Attemting to fold to #{fold.to_s} with only 1 column"
        end
      when :top, :bottom
        if @rows <= 1
          raise ArgumentError,
            "Attemting to fold to #{fold.to_s} with only 1 row"
        end
    end

    # setup loop boundaries to loop through unfolded part of page
    case fold
      when :left
        row_start = 0
        row_end = @rows - 1
        col_start = @cols/2
        col_end = @cols - 1
      when :right
        row_start = 0
        row_end = @rows - 1
        col_start = 0
        col_end = @cols/2 - 1
      when :top
        row_start = @rows/2
        row_end = @rows - 1
        col_start = 0
        col_end = @cols - 1
      when :bottom
        row_start = 0
        row_end = @rows/2 - 1
        col_start = 0
        col_end = @cols - 1
    end

    # loop through row by row reversing items folded to top
    row_start.upto(row_end) do |i|
      col_start.upto(col_end) do |j|
        case fold
        when :left, :right
          top = array_element(i, @cols - j - 1).reverse
          bottom = array_element(i, j)
        when :top, :bottom
          top = array_element(@rows - i - 1, j).reverse
          bottom = array_element(i, j)
        end
        yield(top, bottom)
      end
    end
  end

  # Return a new fold matrix by folding in direction where direction
  # is one of :left, :right, :top, :bottom.
  def fold(direction)
    new_values = []
    each_by_fold(direction) do |top, bottom|
      new_values << top + bottom
    end

    case direction
      when :left, :right
        new_cols = @cols/2
        new_rows = @rows
      when :top, :bottom
        new_cols = @cols
        new_rows = @rows/2
    end
    FoldMatrix.new(new_values, new_rows, new_cols)
  end
end

# Determine if a number is a power of 2
def is_power_of_2(number)
  return false if number < 1

  # keep on shifting left until number equals one (power of 2) or has
  # one bit set but isn't one (not power of 2)
  while number > 1
    number >>= 1
    return false if ((number & 1) == 1 && number != 1)
  end
  true
end

# Get the direction from an unfolded matrix element to the one
# just folded to the top.  Both must be in same row or column.
def direction_to(unfolded, folded, rows, cols)
  unfolded -= 1
  unfolded_i = unfolded / cols
  unfolded_j = unfolded % cols

  folded -= 1
  folded_i = folded / cols
  folded_j = folded % cols

  case
    when unfolded_i == folded_i && unfolded_j < folded_j
      :right
    when unfolded_i == folded_i && unfolded_j > folded_j
      :left
    when unfolded_j == folded_j && unfolded_i < folded_i
      :bottom
    when unfolded_j == folded_j && unfolded_i > folded_i
      :top
    else
      raise ArgumentError, "Values not in same row or column: " +
                           "#{unfolded}, #{folded}, #{rows}x#{cols}"
  end
end

def check_rows_and_cols(rows, cols)
  unless is_power_of_2(rows)
    raise ArgumentError, "Rows must be power of two"
  end
  unless is_power_of_2(cols)
    raise ArgumentError, "Cols must be power of two"
  end
end

# Fold up matrix of numbers using given directions where directions
# are in a string with T = top, B = bottom, L = left, R = right:
# "TLBR".  Throws ArgumentError on invalid direction or rows or cols
# not a power of 2.
def fold(directions, rows=16, cols=16)
  check_rows_and_cols(rows, cols)

  # build array of values
  values = []
  1.upto(rows*cols) do |i|
    values << [i]
  end

  fold_matrix = FoldMatrix.new(values, rows, cols)

  directions.each_byte do |fold_direction|
    case fold_direction
      when ?T
        fold_matrix = fold_matrix.top_fold
      when ?B
        fold_matrix = fold_matrix.bottom_fold
      when ?L
        fold_matrix = fold_matrix.left_fold
      when ?R
        fold_matrix = fold_matrix.right_fold
      else
        raise ArgumentError, "Invalid direction #{fold_direction}"
    end
  end
  fold_matrix.result
end

# Get the folding directions from a fold array.  The item that has
# never been folded over is at end of array.  The item that wasn't
# folded until the last fold and is now at at the first of array.
# Therefore...
#
# while size of array is greater than 1
#   get direction in original matrix from last item number
#     in folded array to first
#   push direction on front of directions
#   cut off front half of array
# end
#
# Throws ArgumentError on array not in fold order or rows or cols not
# power of 2.
def check_fold(folded, rows=16, cols=16)
  check_rows_and_cols(rows, cols)

  directions = ""
  while folded.size > 1
    # get direction in original matrix from last to first
    direction = direction_to(folded.last, folded.first, rows, cols)

    # and push it on front of directions
    case direction
      when :top
        directions = "T" + directions
      when :bottom
        directions = "B" + directions
      when :left
        directions = "L" + directions
      when :right
        directions = "R" + directions
    end

    # cut array in half
    folded = folded[folded.size/2...folded.size]
  end
  directions
end

if __FILE__ == $0
  if (ARGV.size == 1 || ARGV.size == 3)
    if (ARGV.size == 3)
      rows = ARGV[1].to_i
      cols = ARGV[2].to_i
    else
      rows = 16
      cols = 16
    end
    p fold(ARGV[0], rows, cols)
  else
    puts "Usage: #$0 folds [rows cols]"
  end
end