#!/usr/bin/ruby -w require 'pp' # ------------------------------------------------------------------------------ # DEEP SEA FISH # # 9 piece puzzle 0 .. 8 # # 1: pacific blue marlin head # 2: atlantic tarpon head # 3: tuna head # 4: dolphinfish (mahi-mahi) head # # -1: pacific blue marlin tail # -2: atlantic tarpon tail # -3: tuna tail # -4: dolphinfish (mahi-mahi) tail # # definition of pieces top, right, bottom, left #Pieces = [[ 2, -4, 3, 1], # [-1, -4, -3, 4], # [ 3, -1, 2, -3], # [-3, 4, 2, 1], # [ 4, 2, -2, 1], # [ 4, -3, -1, -2], # [-3, 4, -2, -1], # [ 2, -1, -3, 4], # [-2, -1, -4, 3]] # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # BUTTERFLIES # # 9 piece puzzle 0 .. 8 # # 1: american copper head # 2: monarch head # 3: palamedes swallowtail head # 4: red-spotted purple head # # -1: american copper tail # -2: monarch tail # -3: palamedes swallowtail tail # -4: red-spotted purple tail # # definition of pieces top, right, bottom, left Pieces = [[ 2, 4, -1, 3], [ 3, 2, -4, -2], # winner [ 1, 4, -2, -3], [-1, -2, -3, -4], # winner [ 2, -4, -1, 3], # winner [ 1, -3, -2, 4], # winner [ 3, -3, -1, 4], [-1, -4, 2, 3], [ 1, 2, 1, -4]] # winner # ------------------------------------------------------------------------------ # define 'board' and the match rules # # +-----+-----+-----+ # | 0 | 0 | 0 | # |3 0 1|3 1 1|3 2 1| # | 2 | 2 | 2 | # +-----+-----+-----+ # | 0 | 0 | 0 | # |3 3 1|3 4 1|3 5 1| # | 2 | 2 | 2 | # +-----+-----+-----+ # | 0 | 0 | 0 | # |3 6 1|3 7 1|3 8 1| # | 2 | 2 | 2 | # +-----+-----+-----+ # # rules are defined in pairs where each pair defines a position on the board # and a side in that position. The pairs define adjacent sides that need to # be matched. So, for the piece in position n, there are pairs defined for # each comparison that must be made. # Rules = [ [[[0,1],[1,3]], # matches in square 0 [[0,2],[3,0]]], [[[1,1],[2,3]], # matches in square 1 [[1,2],[4,0]], [[1,3],[0,1]]], [[[2,2],[5,0]], # matches in square 2 [[2,3],[1,1]]], [[[3,0],[0,2]], # matches in square 3 [[3,1],[4,3]], [[3,2],[6,0]]], [[[4,0],[1,2]], # matches in square 4 [[4,1],[5,3]], [[4,2],[7,0]], [[4,3],[3,1]]], [[[5,0],[2,2]], # matches in square 5 [[5,2],[8,0]], [[5,3],[4,1]]], [[[6,0],[3,2]], # matches in square 6 [[6,1],[7,3]]], [[[7,0],[4,2]], # matches in square 7 [[7,1],[8,3]], [[7,3],[6,1]]], [[[8,0],[5,2]], # matches in square 8 [[8,3],[7,1]]] ] # # define the initial state of the board -- the 'board' is an array of number # pairs where the first number refers to an entry from the pieces array and the # second number is a counter-clockwise rotation factor relative to the initial # orientation of the pieces when they were defined. Everything will start out # in numeric order and no rotation # Board = [[0,0], [1,0], [2,0], [3,0], [4,0], [5,0], [6,0], [7,0], [8,0]] #Board = [[1,0], [5,0], [3,0], [2,0], [0,0], [6,0], [4,0], [7,0], [8,0]] #Board = [[1,0], [5,1], [3,0], [2,0], [0,0], [6,2], [4,2], [7,2], [8,3]] def match?(side1, side2) return ((side1 + side2) == 0) end def rotate(piece, n = 1) n.times do piece[3] = piece.shift end end def solved?() solved = true for i in 0..8 Rules[i].each do | rule_pair | #pp Pieces # printf("I want to look at piece %d side %d: %d\n", Board[rule_pair[0][0]][0], rule_pair[0][1], Pieces[Board[rule_pair[0][0]][0]][rule_pair[0][1]]) # printf("and compare to piece %d side %d: %d\n", Board[rule_pair[1][0]][0], rule_pair[1][1], Pieces[Board[rule_pair[1][0]][0]][rule_pair[1][1]]) solved = match?(Pieces[Board[rule_pair[0][0]][0]][rule_pair[0][1]], Pieces[Board[rule_pair[1][0]][0]][rule_pair[1][1]]) break if !solved end break if !solved end return solved end def rotate_piece_in_board(x) done = false if (x < 9) rotate(Pieces[Board[x][0]]) Board[x][1] += 1 if Board[x][1] == 4 Board[x][1] = 0 done = rotate_piece_in_board(x+1) end else done = true end return done end def next_board_permutation! # The following algorithm generates the next permutation lexicographically # after a given permutation. It changes the given permutation in-place. # # 1. Find the largest index k such that a[k] < a[k + 1]. If no such # index exists, the permutation is the last permutation. # 2. Find the largest index l such that a[k] < a[l]. Since k + 1 is # such an index, l is well defined and satisfies k < l. # 3. Swap a[k] with a[l]. # 4. Reverse the sequence from a[k + 1] up to and including the final # element a[n]. # # After step 1, one knows that all of the elements strictly after position k # form a weakly decreasing sequence, so no permutation of these elements will # make it advance in lexicographic order; to advance one must increase a[k]. # Step 2 finds the smallest value a[l] to replace a[k] by, and swapping them # in step 3 leaves the sequence after position k in weakly decreasing order. # Reversing this sequence in step 4 then produces its lexicographically minimal # permutation, and the lexicographic successor of the initial state for the # whole sequence. n = Board.length k = (n - 2) # step 1 while (k >= 0) do break if (Board[k][0] < Board[k+1][0]) k -= 1 end return false if k < 0 # step 2 l = (n - 1) while (l > k) do break if (Board[k][0] < Board[l][0]) l -= 1 end # step 3 tmp = Board[k] Board[k] = Board[l] Board[l] = tmp # step 4 k += 1 segment = Board.slice(k...n).reverse! i = 0 while k < n do Board[k] = segment[i] k += 1; i += 1 end return true end #------------------------------------------------------------------------------- # brute_force algorithm by alan partis #++ # Attempts all possible solutions until the right one is found. # # There are something on the order of 96 billion permutations to examine # in a brute-force solution for a puzzle like this. My computer is taking # roughly a minute to check half a million, or about 30 million per hour. # At that rate, it will take well over half a year to check them all, though # it is reasonable to expect a solution to be found in half that time. #------------------------------------------------------------------------------- def brute_force begin loop do 262144.times do if solved? puts "puzzle is solved with board looking like this:" pp Board return end done = rotate_piece_in_board(0) break if done end break if !next_board_permutation! pp Board end rescue Exception => e puts e end end #------------------------------------------------------------------------------- # the global pool and solution spaces for the human approach Pool = [[0,0], [1,0], [2,0], [3,0], [4,0], [5,0], [6,0], [7,0], [8,0]] Solution = [] #------------------------------------------------------------------------------- # fit? by alan partis #++ # Does the 'work_cell' piece fit into the solution? # # return number of rotations to find a fit # return -1 if no fit found #------------------------------------------------------------------------------- def fit?(work_cell) rotations = Solution[work_cell][1] return rotations if work_cell == 0 fit = false while !fit && (rotations < 4) fit = true Rules[work_cell].each do | rule_pair | # only examine rules where the matching cell is at, or below, # the work_cell location if (rule_pair[1][0] <= work_cell) # identify the work cell piece and side work_piece = Solution[work_cell][0] work_side = rule_pair[0][1] # calculate which Piece and side to try to match solution_cell = rule_pair[1][0] test_piece = Solution[solution_cell][0] test_side = rule_pair[1][1] if !match?(Pieces[work_piece][work_side], Pieces[test_piece][test_side]) fit = false end end end if !fit rotate(Pieces[Solution[work_cell][0]]) rotations += 1 end end return -1 if rotations == 4 return rotations end #------------------------------------------------------------------------------- # human algorithm by alan partis #++ # Looks for a solution much like a human might approach the problem. # # We start by choosing one piece from the pool of pieces and placing it in # the first cell. Then we choose another piece and see if it will work somehow # in the next cell. If it does, then we try another piece in the next cell # and so on. If a piece can't fit, then we try a different one in the previous # cell. If no solutions can be found all the way back to the first cell, we use # a different starting piece and try again. # # The calculations for this approach might be slightly longer in this algorithm # versus the brute force method, but we will avoid examining billions and billions # of possible solutions that we already know can't work. # # It turns out that this solution executes in a matter of a few seconds at most # while the brute force approach will likely take several months. # # returns whether we've solved the puzzle, or ran out of options #------------------------------------------------------------------------------- def human(work_cell) solved = false rotate_first = false while !solved # if we're being asked to find a fit for _this_ cell, then we # found fits for all the previous cells and we've found our # solution! if work_cell > 8 solved = true break end if rotate_first rotate_first = false # rotate our piece and retry. If we've tried all sides, pop it off # and try the next piece. If there are no more pieces, fail. rotate(Pieces[Solution[work_cell][0]]) Solution[work_cell][1] = (Solution[work_cell][1] + 1) % 4 if (Solution[work_cell][1] == 0) Pool[Solution[work_cell][0]] = Solution[work_cell] i = Solution[work_cell][0] + 1 while (i < 9) && (Pool[i] == nil) i += 1 end if (i < 9) Solution[work_cell] = Pool[i] Pool[i] = nil else # we've run out of pieces in the pool to work with at this level # so fail back to the previous cell Solution.pop work_cell -= 1 rotate_first = true next end end end # make sure we've got a piece in the solution to work with if Solution[work_cell] == nil #get lowest available piece from pool i = 0 while (i < 9) && (Pool[i] == nil) i += 1 end Solution[work_cell] = Pool[i] Pool[i] = nil end # does the work piece fit here? # calling fit? has the unexpected side-effect of also rotating # the given Piece's data. Get that number and record it in the # solution rotations = fit?(work_cell) if (rotations >= 0) # yes, the work piece fits, note the rotations and # move to the next cell Solution[work_cell][1] = rotations work_cell += 1 else # nope, the work piece doesn't fit, reset the rotations, pop the # cell from the Solution and get the next higher available piece # from the pool. If no higher piece is available, ... Solution[work_cell][1] = 0 Pool[Solution[work_cell][0]] = Solution[work_cell] i = Solution[work_cell][0] + 1 while (i < 9) && (Pool[i] == nil) i += 1 end if (i < 9) Solution[work_cell] = Pool[i] Pool[i] = nil else # we've run out of pieces in the pool to work with at this level # so fail back to the previous cell Solution.pop work_cell -= 1 rotate_first = true end end end return solved end #------------------------------------------------------------------------------- # main program entry point by alan partis #++ # Get things set up and run the appointed algorithm to find the solution. #------------------------------------------------------------------------------- begin # if no algorithm specified on the command line, default to 'human' algorithm = ARGV[0] || 'human' start_time = Time.now case algorithm when 'brute_force' then brute_force when 'human' then solved = human(0) if solved print "solution: "; pp Solution else print "Nice try! solution: "; pp Solution end end stop_time = Time.now printf("run time: %0.1f seconds\n", (stop_time - start_time)) end
Copyright © Thundernet Development Group Inc., 2003. All rights reserved. |