# Copyright 2015 Kevin Ryde
#
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3, or (at your option) any later
# version.
#
# This file is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with this file.  See the file COPYING.  If not, see
# <http://www.gnu.org/licenses/>.

package Graph::Maker::Hanoi;
use 5.004;
use strict;
use base 'Graph::Maker';

use vars '$VERSION';
$VERSION = 5;

# uncomment this to run the ### lines
# use Smart::Comments;

sub _default_graph_maker {
  require Graph;
  Graph->new(@_);
}

sub _vertex_name_digits {
  my (@digits) = @_;
  return join('',@digits);
}
sub _vertex_name_integer {
  my (@digits) = @_;
  my $pow = 1;
  my $ret = 0;
  foreach my $i (reverse 0 .. $#digits) {
    $ret += $digits[$i] * $pow;
    $pow *= 3;
  }
  return $ret;
}

sub init {
  my ($self, %params) = @_;
  my $level = delete($params{'level'}) || 0;

  my $graph_maker = delete($params{'graph_maker'});
  $graph_maker ||= \&_default_graph_maker;
  my $graph = $graph_maker->(%params);

  $graph->set_graph_attribute (name => "Hanoi $level");
  my $directed = $graph->is_directed;

  my $vertex_name_func = \&_vertex_name_integer;

  if ($level <= 0) {
    $graph->add_vertex(0);
  } else {
    my @t = (0) x $level;
    for (;;) {
      my $v = &$vertex_name_func(@t);
      my @t2 = @t;

      # smallest disc $t2[-1] moves to either other spindle
      foreach (1, 2) {
        $t2[-1]++;
        $t2[-1] %= 3;
        if ($directed || $t2[-1] > $t[-1]) {
          my $v2 = &$vertex_name_func(@t2);
          ### smallest disc: "$v to $v2"
          $graph->add_edge($v, $v2);
        }
      }

      # on the spindles without the smallest disc, can move the smaller of
      # their two top discs
      for (my $pos = $#t-1; $pos >= 0; $pos--) {
        if ($t[$pos] != $t[-1]) {
          @t2 = @t;
          $t2[$pos]++;
          $t2[$pos] %= 3;
          if ($t2[$pos] == $t[-1]) {
            $t2[$pos]++;
            $t2[$pos] %= 3;
          }
          if ($directed || $t2[$pos] > $t[$pos]) {
            my $v2 = &$vertex_name_func(@t2);
            ### second disc: "$v to $v2"
            $graph->add_edge($v, $v2);
          }
          last;
        }
      }

      # increment t ...
      my $pos = $#t;
      for (;;) {
        last if ++$t[$pos] < 3;
        $t[$pos] = 0;
        if (--$pos < 0) {
          return $graph;
        }
      }
    }
  }

  # done in integers
  if (0) {
    my $v_max = 3**$level - 1;
    my $vpad_max = 3**($level-1) - 1;
    ### $level
    ### $v_max
    ### $vpad_max

    foreach my $v (0 .. $v_max) {
      my $low = $v % 3;
      ### $v

      foreach my $inc (1, 2) {
        ### $low
        ### $inc
        my $other = ($low + $inc) % 3;
        {
          my $v2 = $v - $low + $other;
          if ($directed || $v2 > $v) {
            ### smallest disc: "$v to $v2"
            $graph->add_edge($v, $v2);
          }
        }

        ### $low
        ### $other
        my $pad = ($low - $inc) % 3;
        my $mod = 3;
        my $rem = $low;
        foreach (1 .. $level) {
          $mod *= 3;
          $rem = 3*$rem + $low;
          my $got = $v % $mod;
          ### $mod
          ### $rem
          ### $got
          if ($got != $rem) {
            my $v2 = $v - $got + ((2*$got - $rem) % $mod);
            if ($directed || $v2 > $v) {
              ### second smallest: "$v to $v2"
              $graph->add_edge($v, $v2);
            }
            last;
          }
        }

        # my $pad = ($low - $inc) % 3;
        # ### $other
        # ### $pad
        #
        # my $vpad = $v;
        # for (;;) {
        #   ### at: "vpad=$vpad  v2=$v2"
        #   last if $vpad >= $vpad_max || $v2 >= $vpad_max;
        #   $vpad = 3*$vpad + $pad;
        #   $v2   = 3*$v2 + $pad;
        #   if ($directed || $v2 > $vpad) {
        #     ### second smallest: "$vpad to $v2"
        #   }
        #     $graph->add_edge($vpad, $v2);
        # }
      }
    }
  }

  return $graph;
}

  Graph::Maker->add_factory_type('hanoi' => __PACKAGE__);
1;

__END__

=for stopwords Ryde

=head1 NAME

Graph::Maker::Hanoi - create Hanoi graph

=for test_synopsis my ($graph)

=head1 SYNOPSIS

 use Graph::Maker::Hanoi;
 $graph = Graph::Maker->new ('hanoi', level => 3);

=head1 DESCRIPTION

C<Graph::Maker::Hanoi> creates C<Graph.pm> graphs of configurations in the
towers of Hanoi puzzle.  This is equivalent to adjacent points of the
Sierpinski triangle, which is also the odd numbers in Pascal's triangle.

      0  level=0         0                    0      
                        / \  level=2         / \   level=3
                       1---2                1---2    
      0  level=1      /     \              /     \   
     / \             7       5            7       5  
    1---2           / \     / \          / \     / \ 
                   8---6---3---4        8---6---3---4
                                       /             \
                                     17              22
                                     / \             / \
                                   15--16          23--21
                                   /     \         /     \      
                                 12      10       2       5     
                                 / \     / \     / \     / \    
                               13--14---9---11--0---1---3---4

0
2  1
5  7
4  3  6  8
22  17
23  21  15  16
20  24  12  10
19  18  26  25  13  14  9  11

The towers of Hanoi puzzle has three spindles with discs of increasing size.
Each vertex is a configuration of discs on spindles.  Each edge is a legal
move from one configuration to another.

At each configuration there are either two or three legal moves.  The
smallest disc can be moved to either of the other two spindles.  Or if those
two other spindles are not empty then the smaller disc on them can be moved
to the other.

=cut

# Configurations are represented as integers 0 to 3^level-1.  Each ternary digit
# is a disc and its value 0,1,2 is which spindle holds that disc.  The least
# significant ternary digit is the smallest disc.  The legal moves are to
# change the least significant digit to either of its two other values.  Or
# skip the low run of the least significant digit and the lowest different
# digit (if there is one) be changed to the other value (not itself and not
# the least significant).
#
# The case of no digit different from the least significant occurs for
# 00..00, 11..11 and 22..22 which are the corners shown above.  For example
# in level=3 the vertices 0, 13 and 26 (ternary 000, 111 and 222).  So every
# vertex is degree 3 except the three corners which are degree 2.

=for Test-Pari  9+3+1 == 13

=for Test-Pari  2*9+2*3+2 == 26

=pod

=head1 FUNCTIONS

=over

=item C<$graph = Graph::Maker-E<gt>new('hanoi', key =E<gt> value, ...)>

The key/value parameters are

    level  =>  integer

Other parameters are passed to C<Graph-E<gt>new()>.

=back

=head1 SEE ALSO

L<Graph::Maker>

=head1 LICENSE

Copyright 2015 Kevin Ryde

This file is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3, or (at your option) any later
version.

This file is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
more details.

You should have received a copy of the GNU General Public License along with
This file.  If not, see L<http://www.gnu.org/licenses/>.

=cut
