# 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/>.


# Self-loops when cyclic and dim 3x3 ?


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

use vars '$VERSION';
$VERSION = 4;

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

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

# last $dim runs fastest, per Graph::Maker::Grid
sub _coordinates_to_vertex {
  my ($c, $dims) = @_;
  my $v = $c->[0];
  die if $c->[0] >= $dims->[0];
  foreach my $i (1 .. $#$dims) {
    $v *= $dims->[$i];
    $v += $c->[$i];
    die if $c->[$i] >= $dims->[$i];
  }
  return $v+1;
}

sub init {
  my ($self, %params) = @_;

  my $dims   = delete($params{'dims'}) || 0;
  my $cyclic = delete($params{'cyclic'});

  ### KnightGrid ...
  ### $dims

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

  $graph->set_graph_attribute(name => "Knight Grid ".join('x',@$dims));

  foreach my $dim (@$dims) {
    if ($dim <= 0) { return $graph; } # empty
  }

  my @c = (0) x scalar(@$dims);
  for (;;) {
    my $v = _coordinates_to_vertex(\@c, $dims);
    $graph->add_vertex($v);
    ### at: join(',',@c)."=[$v]"

    foreach my $i (0 .. $#c) {
      foreach my $j (0 .. $#c) {
        next if $i == $j;
        foreach my $isign (1,-1) {
          foreach my $jsign (1,-1) {
            foreach my $offset (0, 1) {
              my @c2 = @c;
              $c2[$i] += (1+$offset)*$isign;
              $c2[$j] += (2-$offset)*$jsign;
              if ($cyclic) {
                $c2[$i] %= $dims->[$i];
                $c2[$j] %= $dims->[$j];
              } else {
                next unless ($c2[$i] >= 0 && $c2[$i] < $dims->[$i]
                             && $c2[$j] >= 0 && $c2[$j] < $dims->[$j]);
              }
              my $v2 = _coordinates_to_vertex(\@c2, $dims);
              ### edge: join(',',@c)."=[$v] to ".join(',',@c2)."=[$v2]"
              unless ($graph->has_edge($v,$v2)) {
                $graph->add_edge($v,$v2);
              }
            }
          }
        }
      }
    }

    # increment @c coordinates
    for (my $i = 0; ; $i++) {
      if ($i > $#$dims) {
        return $graph;
      }
      if (++$c[$i] < $dims->[$i]) {
        last;
      }
      $c[$i] = 0;
    }
  }
}

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

__END__

=for stopwords Ryde

=head1 NAME

Graph::Maker::KnightGrid - create Knight grid graph

=for test_synopsis my ($graph)

=head1 SYNOPSIS

 use Graph::Maker::KnightGrid;
 $graph = Graph::Maker->new ('knight_grid', dims => [8,8]);

=head1 DESCRIPTION

C<Graph::Maker::KnightGrid> creates a C<Graph.pm> graph of a grid with edges
connecting squares as a chess Knight moves.

The C<dims> and C<cyclic> parameters are the same as C<Graph::Maker::Grid>
but the edges here are steps +/-1,+/-2.

For a 2-dimensional grid each vertex can be degree up to 8, if the grid is
big enough (each dimension E<gt>= 5).  In general for n dimensions

    max_degree = 4*n*(n-1)  = 0, 8, 24, 48, 80, ...       (A033996)

=for Test-Pari-DEFINE  max_degree(n) = 4*n*(n-1);

=for Test-Pari  max_degree(2) == 8

=for Test-Pari  vector(6,n, max_degree(n)) == 8*[0,1,3,6,10,15]

The C<cyclic> parameter makes the grid edges wrap-around.  For 2 dimensions
this is knight moves on a torus.

=head1 FUNCTIONS

=over

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

The key/value parameters are

    dims     => arrayref of dimensions
    cyclic   => boolean

C<dims> and C<cyclic> are both in the style of C<Graph::Maker::Grid>.  Other
parameters are passed to C<Graph-E<gt>new()>.

=back

=head1 SEE ALSO

L<Graph::Maker>, L<Graph::Maker::Grid>

=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
