Advertisement
Guest User

solve-8-puzzle.pl

a guest
Jul 20th, 2018
245
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Perl 4.38 KB | None | 0 0
  1. #!/usr/bin/perl -Twl
  2.  
  3. use strict;
  4.  
  5. # Solves the sliding puzzle with 8 numbered tiles using the A-star algorithm
  6. # and a Manhattan Distance heuristic.
  7. #
  8. # Usage: ./solve-8-puzzle.pl "4623 1587"    # Solve puzzle, output statistics
  9. #        ./solve-8-puzzle.pl -b "4623 1587" # Simulate BFS by disabling MD
  10. #
  11. # Takes one argument, the initial board state, as a single nine character
  12. # string. The string must contain a space, and the numbers 1-8, once each.
  13. #
  14. # To convert a board state to such a string, read the numbers off the puzzle
  15. # and write everything together. Use a space to denote the empty square.
  16.  
  17. # Installing prerequisites:
  18. #     sudo apt-get install cpanm
  19. #     sudo cpanm AI::Pathfinding::AStar
  20.  
  21. # Sample run:
  22. #
  23. #  ./solve-8-puzzle.pl "74623 158"
  24. # +-------+
  25. # | 7 4 6 |
  26. # | 2 3   |
  27. # | 1 5 8 |
  28. # +-------+
  29. # Unsolvable position.
  30. # Total positions checked: 181440
  31.  
  32. # This code is published under the EVVKTVH license:
  33. #   http://evvk.com/evvktvh.html#english
  34.  
  35. package GameOf8;
  36. use base "AI::Pathfinding::AStar";
  37.  
  38.  
  39. sub print_board
  40. {
  41.     my ($self, $board) = @_;
  42.     printf("+-------+\n".
  43.        "| %s %s %s |\n".
  44.        "| %s %s %s |\n".
  45.        "| %s %s %s |\n".
  46.        "+-------+\n",
  47.        (split//,$board));
  48. }
  49.  
  50.  
  51. sub get_neighbours
  52. {
  53.     my ($self, $board) = @_;
  54.  
  55.     my @neighbours=(); # list of neighbours to be filled and returned
  56.  
  57.     my $hole=index($board," ");
  58.     if($hole >= 0 and $hole <=5) # there's a piece under the hole
  59.     {
  60.     my $new=$board;
  61.     substr($new,$hole,1) = substr($new,$hole+3,1);
  62.     substr($new,$hole+3,1) = " ";
  63.     push @neighbours, $new;
  64.     }
  65.     if($hole >= 3 and $hole <=8) # there's a piece above the hole
  66.     {
  67.     my $new=$board;
  68.     substr($new,$hole,1) = substr($new,$hole-3,1);
  69.     substr($new,$hole-3,1) = " ";
  70.     push @neighbours, $new;
  71.     }
  72.     if($hole % 3 != 0) # there's a piece to the right of the hole
  73.     {
  74.     my $new=$board;
  75.     substr($new,$hole,1) = substr($new,$hole-1,1);
  76.     substr($new,$hole-1,1) = " ";
  77.     push @neighbours, $new;
  78.     }
  79.     if($hole % 3 != 2) # there's a piece to the left of the hole
  80.     {
  81.     my $new=$board;
  82.     substr($new,$hole,1) = substr($new,$hole+1,1);
  83.     substr($new,$hole+1,1) = " ";
  84.     push @neighbours, $new;
  85.     }
  86.    
  87.     return @neighbours;
  88. }
  89.  
  90.  
  91. sub manhattan_distance
  92. {
  93.     my ($self,$board)=(@_);
  94.  
  95.    
  96.     return 0 if $self->{simulate_bfs};
  97.     # with an always-zero heuristic, A* reverts into Dijkstra's algorithm,
  98.     # which in turn reverts into a breadth-first search (BFS) when each move
  99.     # costs the same.
  100.  
  101.    
  102.     my $md = 0;
  103.     for my $x (1,2,3)
  104.     {
  105.     $md += int(index($board,$x)/3);
  106.     }
  107.     for my $x (4,5,6)
  108.     {
  109.     $md += (int(index($board,$x)/3) != 1);
  110.     }
  111.     for my $x (7,8)
  112.     {
  113.     $md += 2 - int(index($board,$x)/3);
  114.     }
  115.     for my $x (1,4,7)
  116.     {
  117.     $md += index($board,$x) % 3;
  118.     }
  119.     for my $x (2,5,8)
  120.     {
  121.     $md += (index($board,$x) % 3 != 1);
  122.     }
  123.     for my $x (3,6)
  124.     {
  125.     $md += 2-(index($board,$x) % 3);
  126.     }
  127.     return $md;
  128. }
  129.  
  130.  
  131. sub new
  132. {
  133.     my ($class)=@_;
  134.     return bless {
  135.     callcount       => 0,  # count A-star's calls to getSurrounding.
  136.     node_visited_at => {}, # record when a particular position was reached.
  137.     simulate_bfs    => 0,  # short-circuit manhattan distance heuristic?
  138.     }, $class;
  139. }
  140.  
  141.  
  142. # Method required by AI::Pathfinding::AStar
  143. sub getSurrounding
  144. {
  145.     my ($self, $node, $target) = @_;
  146.  
  147.     $self->{node_visited_at}->{$node} = $self->{callcount}++;
  148.  
  149.     my @neighbours = $self->get_neighbours($node);
  150.     my $surroundings=[];
  151.     foreach my $neighbour (@neighbours)
  152.     {
  153.     push(@$surroundings,
  154.          [$neighbour, 1, $self->manhattan_distance($neighbour)]
  155.         );
  156.     }
  157.     return $surroundings;
  158. }
  159.  
  160.  
  161.  
  162. package main;
  163.  
  164. my $map = new GameOf8;
  165. $map->{simulate_bfs} = 1, shift if $ARGV[0] eq "-b";
  166.  
  167. my $start=pop;
  168.  
  169. die "Usage: $0 \"14256 837\"\n"
  170.     unless join('', sort split//, $start) eq " 12345678";
  171.  
  172. my $path = $map->findPath($start, "12345678 "); # do the actual solving
  173.  
  174. if(@$path)
  175. {
  176.     foreach my $node (@$path)
  177.     {
  178.     GameOf8->print_board($node);
  179.     print("Manhattan distance: ", $map->manhattan_distance($node));
  180.     print "Positions checked: ",  $map->{node_visited_at}->{$node} || "-";
  181.     }
  182.     print "Total moves: ", scalar(@$path)-1;
  183. }
  184. else
  185. {
  186.     GameOf8->print_board($start);
  187.     print("Unsolvable position.");
  188. }
  189.  
  190. print("Total positions checked: ", $map->{callcount});
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement