[QUIZ] Pen and Paper (#90)

Justin Collins wrote:

Here's my solution, which works the same way as Elliot's first solution, although with very different code:

Forgot to include some times, for those interested:

[justin@cheese ruby]$ time -p ruby penpaper.rb 5 > /dev/null
real 0.00
user 0.00
sys 0.00
[justin@cheese ruby]$ time -p ruby penpaper.rb 50 > /dev/null
real 0.63
user 0.63
sys 0.00
[justin@cheese ruby]$ time -p ruby penpaper.rb 100 > /dev/null
real 2.66
user 2.65
sys 0.00
[justin@cheese ruby]$ time -p ruby penpaper.rb 200 > /dev/null
real 11.59
user 11.58
sys 0.00
[justin@cheese ruby]$ time -p ruby penpaper.rb 300 > /dev/null
real 233.48
user 233.45
sys 0.02
[justin@cheese ruby]$ time -p ruby penpaper.rb 400 > /dev/null
real 332.94
user 332.91
sys 0.02

Note that the times for the larger boards almost certainly include multiple attempts.

-Justin

My solution also uses the Warnsdorff heuristic (moving to the square
with the least amount of possible moves left).
It works very well for sizes under 100 or so, gets worse after that
and starts taking hundreds of tries at about n=175.

I also included an option to see how long tours are starting from each
square (give the script any second parameter to show this).
This shows how well (or poor) the algorithm works for that size, but
it's obviously quite slow for larger sizes as it runs the algorithm
for every square.

Pastie link: http://pastie.caboo.se/8427
Code:

module Enumerable
  def x(enum) # Cartesian product
    map{|a| enum.map{|b| [a,b].flatten }}.inject([]) {|a,b| a+b}
  end
  def **(n) # Cartesian power
    if n == 1
      self
    else
      self.x(self**(n-1))
    end
  end
end

module OpenStructable
  def method_missing(method,*args)
    (class<<self;self;end).send(:attr_accessor, method.to_s.sub('=','') )
    send(method,*args)
  end
end

class Vertex < Array
  include OpenStructable
  def initialize(name)
    self.name = name
  end
end

class Graph
  def initialize
    @vertices = Hash.new{|h,k| h[k] = Vertex.new(k) }
  end

  def add_edge(from,to)
    @vertices[from] << @vertices[to]
  end

  def warnsdorff_tour(start)
    @vertices.each_value{|v|
      v.score = v.size
      v.done = false
    }
    curr_node = @vertices[start]
    tour = []
    while curr_node
      tour << curr_node
      curr_node.done = true
      curr_node.each{|v| v.score -= 1}
      curr_node = curr_node.reject{|v| v.done}.sort_by{|v| v.score}.first
    end
    tour.map{|t| t.name}
  end

end

n = (ARGV[0] || 5).to_i
graph = Graph.new

dirs = [2,-2]**2 + [[0,3],[3,0],[0,-3],[-3,0]]

valid = 0...n

(valid**2).each {|x,y|
  dirs.each{|dx,dy|
    to_x, to_y = x + dx, y + dy
    graph.add_edge([x,y] , [to_x,to_y]) if valid.include?(to_x) &&
valid.include?(to_y)
  }
}
grid = Array.new(n){Array.new(n)}

# This shows how long a tour starting from each square is
if ARGV[1]
  full_count = 0
  (valid**2).each{|x,y|
    grid[y][x] = graph.warnsdorff_tour([x,y]).length
    full_count += 1 if grid[y][x] == n*n
  }
  puts "#{full_count} / #{n*n} = #{'%.2f' %
[100*full_count.to_f/(n*n)]} % of the possible starting positions give
a full tour."
else

solution = []
try = 0
([0,n-1]**2 + [0].x(1..n-2) + (1..n-2)**2).each{|sx,sy| # for
starting square, try corners first, then one edge, then inside
  try += 1
  solution = graph.warnsdorff_tour([sx,sy])
  break if solution.length == n*n
}

if solution.length != n*n
  puts "Failed to find tour."
  exit!
end
puts "Found tour in #{try} tries."

solution.each_with_index{|(x,y),i| grid[y][x] = i+1 }
end

max_len = (n * n).to_s.length

sep = ('+' + '-' * (max_len+2) ) * n + '+'
puts sep, grid.map{|row| '| ' + row.map{|n| n.to_s.center(max_len)
}.join(' | ') + ' |' }.join("\n" + sep + "\n"), sep

Suraj N. Kurapati wrote:

Here is my solution, which uses recursion like the Civilization II
map generation example in Chris Pine's "Learn to Program" book.

With a stack limit of:

  $ ulimit -s
  8192

the maximum square size my solution can handle is 67x67. However, it
can handle larger squares if you increase the stack size.

Well, I discovered that this solution doesn't always find the
correct answer, so I fixed it accordingly. Now I can see why Eric
DUMINIL said a *brute force* approach is too slow. :slight_smile:

Thanks for the fun quiz.

#!/usr/bin/ruby -w
# @param . width of square
# @param . starting row
# @param . starting column

class Square < Array
  def initialize aWidth
    super(aWidth) { Array.new aWidth }
    @mark = 0
    @size = aWidth ** 2
  end

  # Walks this square, from the given position,
  # while marking unmarked (nil) cells.
  def walk row, col
    # skip invalid positions and marked cells
      return false if
        row < 0 or row >= length or
        col < 0 or col >= length or
        self[row][col]

    # mark the current cell
      self[row][col] = @mark += 1

    # explore adjacent paths
      if @mark >= @size or
         walk(row + 3, col ) or # east
         walk(row + 2, col - 2) or # north east
         walk(row, col - 3) or # north
         walk(row - 2, col - 2) or # north west
         walk(row - 3, col ) or # west
         walk(row - 2, col + 2) or # south west
         walk(row, col + 3) or # south
         walk(row + 2, col + 2) # south east

        true
      else
        # unmark the current cell
          @mark -= 1
          self[row][col] = nil

        false
      end
  end

  # Pretty-prints this square.
  def to_s
    # pretty-print each row
      fmt = '|' << "%#{length.to_s.length * 2}d " * length << '|'

      lines = inject() do |memo, row|
        memo << fmt % row
      end

    # add a border to top & bottom
      border = '-' * lines.first.length

      lines.unshift border
      lines << border

    lines.join("\n")
  end
end

if $0 == __FILE__
  # create a square with user's parameters
    width = (ARGV.shift || 5).to_i
    square = Square.new(width)

  # walk the square from random position
    origin = Array.new(2) { (ARGV.shift || rand(width)).to_i }
    square.walk(*origin)

  # pretty-print the walked square
    puts square.to_s
end

quoth the Elliot Temple:

···

You can get stuck in 6 steps on 6x6 or larger:

  -------------------------

  -------------------------

. . . . . . |
5 . . 4 . . |
. . . . . . |
. . 1 . . 2 |
. . . . . . |
6 . . 3 . . |

-------------------------

Am I missing something here? Your vertical moves are jumping 3 squares...
From the rules:

       - jumping over 2 squares when traveling horizontally or vertically
      - jumping over 1 square when traveling diagonally.

-- Elliot Temple
Curiosity Blog – Elliot Temple

-d
--
darren kirby :: Part of the problem since 1976 :: http://badcomputer.org
"...the number of UNIX installations has grown to 10, with more expected..."
- Dennis Ritchie and Ken Thompson, June 1972

w00t!

I finally got it. At first I misunderstood the algorithm, I was counting the
number of free adjacent cells, and giving preference to the move with the
least amount. This actually made the script take longer than when I was
picking the move randomly. Finally I figured out we were scoring the least
amount of possible moves (per move)...I also refactored the code into a
class.

So here are a few timings:
$ time ruby pnp.rb 15 > /dev/null
real 0m0.196s
user 0m0.092s
sys 0m0.009s
$ time ruby pnp.rb 25 > /dev/null
real 0m0.460s
user 0m0.334s
sys 0m0.036s
$ time ruby pnp.rb 50 > /dev/null
real 0m0.801s
user 0m0.659s
sys 0m0.052s
$ time ruby pnp.rb 100 > /dev/null
real 0m5.472s
user 0m5.038s
sys 0m0.299s

Nothing to be thrilled about, that's fer shur.

And the code:

···

#####################################
#!/usr/bin/ruby
# PnP.rb :: quiz no.90

class SolveIt
  def initialize(x,y,gridsize)
    @gridsize = gridsize
    @coords = [x,y]

    @moves_offset = {
      "l" => [0,-3],
      "r" => [0,3],
      "u" => [-3,0],
      "d" => [3,0],
      "ur" => [-2,2],
      "ul" => [-2,-2],
      "dr" => [2,2],
      "dl" => [2,-2]
    }

    @matrix = Array.new(@gridsize) { Array.new(@gridsize) { "." } }
    @matrix[@coords[0]][@coords[1]] = 1
    @totalnums = @gridsize*@gridsize
    @nextint = 2
  end

  def cell_free?(x,y)
    if x >= 0 && x < @gridsize && y >= 0 && y < @gridsize &&
                  @matrix[x][y] == "."
      return true
    else
      return false
    end
  end

  def num_moves(m)
    moves = 0
    x,y = @moves_offset[m]
    @moves_offset.each do |k,v|
      moves += 1 if cell_free?(@coords[0]+x+v[0],@coords[1]+y+v[1])
    end
    moves
  end

  def check_moves_and_return_best_one
    moves = []
    @moves_offset.each do |k,v|
      moves << k if cell_free?(@coords[0]+v[0],@coords[1]+v[1])
    end
    if moves.length == 0
       return nil
    elsif moves.length ==1
      return moves[0]
    end
    score = {}
    moves.each do |m|
      score[m] = num_moves(m)
    end
    score = score.invert.sort
    return score[0][1]
  end

  def print_matrix
    @matrix.each do |row|
      row.each do |cell|
        print " %3s " % cell
      end
      print "\n"
    end
  end

  def do_it
    @totalnums.times do
      move = check_moves_and_return_best_one
      if move == nil
        break # try again
      end
      x,y = @moves_offset[move]
      @coords[0] += x
      @coords[1] += y
      @matrix[@coords[0]][@coords[1]] = @nextint
      @nextint += 1
      if @nextint == @totalnums + 1
        print_matrix
        exit
      end
    end
  end
end

while 1:
  gridsize = ARGV[0].to_i
  x, y = rand(gridsize), rand(gridsize)
  it = SolveIt.new(x,y,gridsize)
  it.do_it
end
#############################################
-d
--
darren kirby :: Part of the problem since 1976 :: http://badcomputer.org
"...the number of UNIX installations has grown to 10, with more expected..."
- Dennis Ritchie and Ken Thompson, June 1972

Just want to correct a misstatement. The worse-case behavior is actually O(e**n**2), which better explains why it gets bad so fast.

Regards, Morton

···

On Aug 15, 2006, at 4:34 PM, Morton Goldberg wrote:

But it has O(e**n) worst-case behavior. Which means it works well enough for small n, but it quickly becomes intolerable as n grows. When does it begin to suck? Depends on how patient you are. I ran out of patience at n = 7.

I bet you are going to be very surprised when I post my pathetic little toy. If it's good at anything it's cheating... :wink:

James Edward Gray II

···

On Aug 11, 2006, at 2:42 PM, Mat Schaffer wrote:

On Aug 11, 2006, at 2:39 PM, James Edward Gray II wrote:

On Aug 11, 2006, at 7:58 AM, Ruby Quiz wrote:

  - Try to fill the biggest board you can with this script, in a
    reasonable amount of time (let's say 48 hours minus scripting time)

[snip: lots of fast filled boards]

Do you by any chance have a background in artificial intelligence? That's really impressive how quickly you cranked out a solution to a problem I can't yet see a decent strategy for. Hats off to you, my friend.

<timings snipped>

Mine did 50x50 in

real 0m0.516s
user 0m0.473s
sys 0m0.013s

on it's third attempt. it fails sometimes (i guess i could have it loop and keep trying until it gets a good run). i haven't written printing out a pretty board though (using pp is no good over about 15x15, at least with the defaults)

i tried 500x500 a few times and it failed them all. 500x500 should be easy though, for a smarter algorithm. you could treat it like a bunch of smaller games with a requirement that you end near a certain edge.

-- Elliot Temple

···

On Aug 11, 2006, at 1:40 PM, Suraj N. Kurapati wrote:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

James Edward Gray II wrote:

On Aug 11, 2006, at 7:58 AM, Ruby Quiz wrote:

    - Try to fill the biggest board you can with this script, in a
      reasonable amount of time (let's say 48 hours minus scripting time)

[timings for 6x6 square snipped]

Here's what I get, although I'm sure we have different hardware. :slight_smile:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Trans wrote:

Could you explain the rules of game a little better.

This is a great answer. Let me just add one minor point...

1. Grab a pen and piece of paper.

2. Draw a 5x5 grid (or table or matrix or whatever you like
   to call it).

3. Pick a cell or slot, whichever one you like, in the grid.

4. Write the number "1" in that chosen cell.

5. Starting from the cell you just marked (with the number "
   1"), travel to a different cell by following these rules:

        a. jump over 2 cells when traveling horizontally or
           vertically

        b. jump over 1 cell when traveling diagonally

6. Once you have traveled to the new cell, mark it with the
   number "2".

7. Starting from the cell you just marked (with the number "
   2"), travel to a different cell by following the rules a.
   and b. shown above.

8. Once you have traveled to the new cell, mark it with the
   number "3".

See the pattern? Now you need to travel from the one you
    just marked (with the number "3") to a new cell (which you
    will mark with the number "4"). Continue this process until
    you have marked all cells in the grid (the last cell will be
    marked with the number "25" for a 5x5 grid).

If you cannot make a move at any point (using either of the two move rules), the game is lost.

James Edward Gray II

···

On Aug 11, 2006, at 6:21 PM, Suraj N. Kurapati wrote:

Suraj N. Kurapati wrote:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Trans wrote:
> Could you explain the rules of game a little better.

1. Grab a pen and piece of paper.

2. Draw a 5x5 grid (or table or matrix or whatever you like
   to call it).

3. Pick a cell or slot, whichever one you like, in the grid.

4. Write the number "1" in that chosen cell.

5. Starting from the cell you just marked (with the number "
   1"), travel to a different cell by following these rules:

        a. jump over 2 cells when traveling horizontally or
           vertically

        b. jump over 1 cell when traveling diagonally

6. Once you have traveled to the new cell, mark it with the
   number "2".

7. Starting from the cell you just marked (with the number "
   2"), travel to a different cell by following the rules a.
   and b. shown above.

8. Once you have traveled to the new cell, mark it with the
   number "3".

See the pattern? Now you need to travel from the one you
    just marked (with the number "3") to a new cell (which you
    will mark with the number "4"). Continue this process until
    you have marked all cells in the grid (the last cell will be
    marked with the number "25" for a 5x5 grid).

Ah. okay. Consecutive order. That's what I was missing. Thanks Suraj.

T.

'Bout 10 seconds (James time, not computer time). Did you read the quiz? :wink:

James Edward Gray II

···

On Aug 12, 2006, at 2:22 PM, Morton Goldberg wrote:

---------------------------------
> 1 30 12 2 29 13 |
> 20 33 27 15 34 8 |
> 11 3 36 31 4 23 |
> 26 16 19 7 28 14 |
> 21 32 10 22 35 9 |
> 18 6 25 17 5 24 |
---------------------------------

It took a while for the penny to drop. My question is: how did you find the _first_ 6 x 6 solution and long did _that_ take?

Great explanation indeed!

Thank you very much for making this quiz easier to understand Suraj :slight_smile:

Eric

PS:
Do you also receive my emails twice?
Do you know where the problem can be?

···

Trans wrote:
> Could you explain the rules of game a little better.

1. Grab a pen and piece of paper.

2. Draw a 5x5 grid (or table or matrix or whatever you like
   to call it).

3. Pick a cell or slot, whichever one you like, in the grid.

4. Write the number "1" in that chosen cell.

5. Starting from the cell you just marked (with the number "
   1"), travel to a different cell by following these rules:

        a. jump over 2 cells when traveling horizontally or
           vertically

        b. jump over 1 cell when traveling diagonally

6. Once you have traveled to the new cell, mark it with the
   number "2".

7. Starting from the cell you just marked (with the number "
   2"), travel to a different cell by following the rules a.
   and b. shown above.

8. Once you have traveled to the new cell, mark it with the
   number "3".

See the pattern? Now you need to travel from the one you
    just marked (with the number "3") to a new cell (which you
    will mark with the number "4"). Continue this process until
    you have marked all cells in the grid (the last cell will be
    marked with the number "25" for a 5x5 grid).

Hope that helps.

i think jumping over 2 squares means landing on third square away. that's what it looked like in the examples.

-- Elliot Temple

···

On Aug 13, 2006, at 7:11 PM, darren kirby wrote:

quoth the Elliot Temple:

You can get stuck in 6 steps on 6x6 or larger:

  -------------------------

  -------------------------
> . . . . . . |
> 5 . . 4 . . |
> . . . . . . |
> . . 1 . . 2 |
> . . . . . . |
> 6 . . 3 . . |
-------------------------

Am I missing something here? Your vertical moves are jumping 3 squares...
From the rules:

       - jumping over 2 squares when traveling horizontally or vertically
      - jumping over 1 square when traveling diagonally.

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/

$ gem install pen_and_paper --remote
Attempting remote installation of 'pen_and_paper'
ERROR: While executing gem ... (Gem::GemNotFoundException)
     Could not find pen_and_paper (> 0) in the repository

Okay, well at least it wasn't THAT sort of cheating :slight_smile: Can't wait to see it!
-Mat

···

On Aug 11, 2006, at 3:48 PM, James Edward Gray II wrote:

On Aug 11, 2006, at 2:42 PM, Mat Schaffer wrote:

On Aug 11, 2006, at 2:39 PM, James Edward Gray II wrote:

On Aug 11, 2006, at 7:58 AM, Ruby Quiz wrote:

  - Try to fill the biggest board you can with this script, in a
    reasonable amount of time (let's say 48 hours minus scripting time)

[snip: lots of fast filled boards]

Do you by any chance have a background in artificial intelligence? That's really impressive how quickly you cranked out a solution to a problem I can't yet see a decent strategy for. Hats off to you, my friend.

I bet you are going to be very surprised when I post my pathetic little toy. If it's good at anything it's cheating... :wink:

I've done a similar problem before, years ago called a Knight's Tour. You move a knight around a chess board and try to visit each square once. I've seen someone do it live, blindfolded which was pretty cool. Here's a brief English description of one technique I used.

MINOR SPOILER WARNING

One simple technique that helped get decent results was to keep track of how many still-open squares are connected to each square. Then when choosing what square to visit next, choose the square with the lowest number of open, connected squares. Corners are only connected to two others squares at the start (for the knight's tour). This makes sure if you move adjacent to a corner, you'll move to the corner next, which helps avoid being struck (it's not strictly always necessary because you could end in a corner). I can't remember if it ever had incomplete runs using just the open square scoring technique or not.

-- Elliot Temple

···

On Aug 11, 2006, at 12:48 PM, James Edward Gray II wrote:

On Aug 11, 2006, at 2:42 PM, Mat Schaffer wrote:

On Aug 11, 2006, at 2:39 PM, James Edward Gray II wrote:

On Aug 11, 2006, at 7:58 AM, Ruby Quiz wrote:

  - Try to fill the biggest board you can with this script, in a
    reasonable amount of time (let's say 48 hours minus scripting time)

[snip: lots of fast filled boards]

Do you by any chance have a background in artificial intelligence? That's really impressive how quickly you cranked out a solution to a problem I can't yet see a decent strategy for. Hats off to you, my friend.

I bet you are going to be very surprised when I post my pathetic little toy. If it's good at anything it's cheating... :wink:

Elliot Temple wrote:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

James Edward Gray II wrote:

    - Try to fill the biggest board you can with this script, in a
      reasonable amount of time (let's say 48 hours minus scripting time)

[timings for 6x6 square snipped]

Here's what I get, although I'm sure we have different hardware. :slight_smile:

<timings snipped>

Mine did 50x50 in

real 0m0.516s
user 0m0.473s
sys 0m0.013s

on it's third attempt. it fails sometimes (i guess i could have it loop and keep trying until it gets a good run). i haven't written printing out a pretty board though (using pp is no good over about 15x15, at least with the defaults)

i tried 500x500 a few times and it failed them all. 500x500 should be easy though, for a smarter algorithm. you could treat it like a bunch of smaller games with a requirement that you end near a certain edge.

-- Elliot Temple
Curiosity Blog – Elliot Temple

[justin@cheese ruby]$ time -p ruby penpaper.rb 10 > /dev/null
real 0.02
user 0.01
sys 0.00
[justin@cheese ruby]$ time -p ruby penpaper.rb 50 > /dev/null
real 1.85
user 1.85
sys 0.00
[justin@cheese ruby]$ time -p ruby penpaper.rb 100 > /dev/null
real 18.27
user 18.25
sys 0.00
[justin@cheese ruby]$ time -p ruby penpaper.rb 200 > /dev/null
real 21.32
user 21.31
sys 0.00
[justin@cheese ruby]$ time -p ruby penpaper.rb 300 > /dev/null
real 564.41
user 564.39
sys 0.01

I'm not sure I like where those times are going... :slight_smile:
My script keeps trying until it finds a solution, so keep that in mind for the timings above.

-Justin

···

On Aug 11, 2006, at 1:40 PM, Suraj N. Kurapati wrote:

On Aug 11, 2006, at 7:58 AM, Ruby Quiz wrote:

You mean your first solution was cut and paste from the quiz posting? That's really cheating!
That's what I did, too, but I was naive enough to believe you wrote a script that generated at least one solution. :slight_smile:

Regards, Morton

···

On Aug 12, 2006, at 3:33 PM, James Edward Gray II wrote:

On Aug 12, 2006, at 2:22 PM, Morton Goldberg wrote:

---------------------------------
> 1 30 12 2 29 13 |
> 20 33 27 15 34 8 |
> 11 3 36 31 4 23 |
> 26 16 19 7 28 14 |
> 21 32 10 22 35 9 |
> 18 6 25 17 5 24 |
---------------------------------

It took a while for the penny to drop. My question is: how did you find the _first_ 6 x 6 solution and [how] long did _that_ take?

'Bout 10 seconds (James time, not computer time). Did you read the quiz? :wink:

James Edward Gray II

Eric, if you use Gmail, it'll show up twice because Gmail adds your
response as well as the email you get of what you just sent back from
the list. So, in effect, you keep your response as well as getting
your response back, and Google shows it twice.

:slight_smile: Sorry to be off topic!

M.T.

oh haha. there are typos. the 4 and 5 should be one row lower. i think it's right then.

-- Elliot Temple

···

On Aug 13, 2006, at 7:44 PM, Elliot Temple wrote:

On Aug 13, 2006, at 7:11 PM, darren kirby wrote:

quoth the Elliot Temple:

You can get stuck in 6 steps on 6x6 or larger:

  -------------------------

  -------------------------
> . . . . . . |
> 5 . . 4 . . |
> . . . . . . |
> . . 1 . . 2 |
> . . . . . . |
> 6 . . 3 . . |
-------------------------

Am I missing something here? Your vertical moves are jumping 3 squares...
From the rules:

       - jumping over 2 squares when traveling horizontally or vertically
      - jumping over 1 square when traveling diagonally.

i think jumping over 2 squares means landing on third square away. that's what it looked like in the examples.