Dear Monks

Recently I stumbled on an old puzzle: You have a 6*6-grid and various blocks of the sizes 2*1 or 3*1. You can move the blocks either horizontal or vertical, but only in the direction "of their orientation". (meaning: a block that is aligned horizontal can only be moved to the left or to the right, but never "sideways" to the top or bottom). The grid has an exit - and your goal is to move a specially marked block out of the grid.

Implementations of the puzzle can be found here (electronic, called blue block) or here (classic hardware solution, called rush hour).

After being stuck with a scenario for more than 15 minutes, I decided to "outsource" solution-finding to a program (see below).

Some advice I'd like to ask of you

Thanks for your feedback! Rata

#!/usr/bin/perl -w use strict; use warnings; use Data::Dumper; # for debugging use Time::HiRes qw (time); # for profiling my @solution = (); # the solution as moves my @solpics = (); # the solution as "pictures" my %poslist = (); # list of all positions that have be +en reached already - to prevent loops my $MAXMOVES = 36; # upper limit for the number of mov +es (btw: the first move is move #0!) my $LINES = 8; my $COLUMNS = 8; my $EXIT = 34; my $MAXCHAR; # highest "name" of the blocks my %blocksize; # the size of each block my %blockdir; # the orientation of all blocks (h or + v) my $pos = "********\n". # The playfield "*..EEE.*\n". # * "A" needs to be moved out "*....C.*\n". # * all other blocks are numbered +from B...Z - with no gaps in the names "*AA.BC..\n". # * all blocks need to be 2 or 3 b +oxes long "*...BDF*\n". # * free positions are marked with + "." (including the exit) "*...BDF*\n". # * the borders are marked with "* +" "*....D.*\n". # * the exit is hardcoded (positio +n 34, see above) "********\n"; setpos(); init(); # init the meta-data of the field my $movecount = 0; # count the number of steps my $starttime = time(); # for profiling my $result = solve ($pos); # recursively solve the puzzle my $endtime = time(); if ($result == 1) { print ($pos,"can be solved with the following ",scalar(@solution) +, " moves: ", join(" ",@solution), "\n\n"); for (my $i = 0; $i < scalar(@solution); $i++) { print $solution[$i +],"\n",$solpics[$i]; } print ($pos,"can be solved with the following ",scalar(@solution) +, " moves: ", join(" ",@solution), "\n\n"); } else { print ($pos,"cannot be solved in ",$MAXMOVES+1," moves\n"); } print "$movecount steps have been executed to find that solution\n"; print $endtime-$starttime," seconds have been needed.\n"; exit 0; #Debuging #print( Data::Dumper->Dumpxs( [ \ %blocksize ], [ qw{ *blocksize } ] ) +); #print( Data::Dumper->Dumpxs( [ \ %blockdir ], [ qw{ *blockdir } ] ) +); #============================== recursive solution =================== +===================================================================== +== sub solve { my $pos = $_[0]; return 1 if (substr($pos, $EXIT, 1) eq "A"); # a solutio +n has been found :-) Horray! # return 0 if (scalar(@solution) > $MAXMOVES) ; # too many s +teps <--- comment this in (###1###) my @moves = getMoves($pos); return 0 if (scalar(@moves) == 0) ; # no moves = +> not finished # print $pos, join(",",@moves), " [$movecount steps][",scalar(keys( +%poslist))," positions checked][",scalar(@solution)," steps in curren +t solution]\n"; foreach my $m (@moves) { $movecount++; # increas +e the count of moves ... my $newpos = move ($pos, $m); # do the +next move next if (exists($poslist{$newpos})); # that pos +ition was already there .. skip it to prevent going in loops push (@solution, $m); # store t +he new move push (@solpics, $newpos); $poslist{$pos} = 1; my $res = solve ($newpos); # evalua +te the new position return $res if ($res == 1); # return i +f a soultion has been found pop (@solution); # no solut +ion has been found with this move pop (@solpics); # => re +move it and try the next one # delete $poslist{$pos}; # + <--- comment this in (###1###) } return 0; # we didn't f +ind anything... } #============================== get a list of all valid moves ======== +===================================================================== +============= sub getMoves { my $pos = $_[0]; my @moves = (); foreach my $ch ('A' .. $MAXCHAR) { my $p = index($pos, $ch); # left + top corner my $s = $blocksize{$ch}; # size my $d = $blockdir {$ch}; # direction if ($d eq "h") # horizontal handling { my $i = 1; while(1) { last unless (substr($pos, $p-$i, 1) eq "."); # +look left for free elements push (@moves, "$ch:L:$i"); $i++; } $i = 1; while(1) { last unless (substr($pos, $p+$s+$i-1, 1) eq "."); + # look right for free elements push (@moves, "$ch:R:$i"); $i++; } } else # vertical handling { my $i = 1; while(1) { last unless (substr($pos, $p-$i*($COLUMNS+1), 1) eq ". +"); # look up for free elements push (@moves, "$ch:U:$i"); $i++; } $i = 1; while(1) { last unless (substr($pos, $p+($s+$i-1)*($COLUMNS+1), 1 +) eq "."); # look down for free elements push (@moves, "$ch:D:$i"); $i++; } } } return @moves; } #============================== move a block ========================= +================================================================= + sub move { my $oldpos = $_[0]; my $m = $_[1]; $m =~ /(.):(.):(\d)/; my $ch = $1; my $dir = $2; my $num = $3; my $newpos; my @s = split (//, $oldpos); my $p = index($oldpos, $ch); if ($dir eq "L") { for (my $i = 0; $i < $blocksize{$ch}; $i++) { $s[$p+$i] + = "."; } # delete old string for (my $i = 0; $i < $blocksize{$ch}; $i++) { $s[$p+$i-$nu +m] = $ch; } # write new position } elsif ($dir eq "R") { for (my $i = 0; $i < $blocksize{$ch}; $i++) { $s[$p+$i] + = "."; } # delete old string for (my $i = 0; $i < $blocksize{$ch}; $i++) { $s[$p+$i+$nu +m] = $ch; } # write new position } elsif ($dir eq "U") { for (my $i = 0; $i < $blocksize{$ch}; $i++) { $s[$p+$i*($C +OLUMNS+1)] = "."; } # delete old string for (my $i = 0; $i < $blocksize{$ch}; $i++) { $s[$p+($i-$n +um)*($COLUMNS+1)] = $ch; } # write new position } elsif ($dir eq "D") { for (my $i = 0; $i < $blocksize{$ch}; $i++) { $s[$p+$i*($C +OLUMNS+1)] = "."; } # delete old string for (my $i = 0; $i < $blocksize{$ch}; $i++) { $s[$p+($i+$n +um)*($COLUMNS+1)] = $ch; } # write new position } $newpos = join("",@s); return $newpos; } #============================== init the main fields ================= +===================================================================== +== sub init { foreach my $i (reverse("A" .. "Z")) { $MAXCHAR = $i; # overcome impli +cit localization of foreach-loop last if (index ($pos, $i) != -1); } foreach my $ch ("A"..$MAXCHAR) # determine orie +ntation and size of the blocks { my $p = index ($pos, $ch); if (substr($pos, $p+1, 1) eq $ch) { my $i = 1; while (substr($pos, $p+$i, 1) eq $ch) { $i++; } $blocksize{$ch} = $i; $blockdir{$ch} = "h"; } else { my $i = 1; while (substr($pos, $p+$i*($COLUMNS+1), 1) eq $ch) { $i++; + } $blocksize{$ch} = $i; $blockdir{$ch} = "v"; } } } #============================== another starting position ============ +===================================================================== +========= sub setpos { $pos ="********\n". # A more complicated starting p +osition "*BBB.CC*\n". "*..DEEF*\n". "*AAD..F.\n". "*H.MMLG*\n". "*HJJKLG*\n". "*IIIK.G*\n". "********\n"; }

In reply to Puzzle solver (rush hour) by Ratazong

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.