# Copyright 2012, 2013 Kevin Ryde

# This file is part of Math-PlanePath-Toothpick.
#
# Math-PlanePath-Toothpick 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.
#
# Math-PlanePath-Toothpick 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 Math-PlanePath-Toothpick.  If not, see <http://www.gnu.org/licenses/>.


# A151727 parts=4 added endless row, A151728 div4, A151729 div8
# A151747 v1 added 3/4
# cf A170881 (3*n+1)*2^(n-2)+1 first column of A151747
#

package Math::PlanePath::SurroundOneEight;
use 5.004;
use strict;
use Carp;
#use List::Util 'max';
*max = \&Math::PlanePath::_max;

use vars '$VERSION', '@ISA';
$VERSION = 2;
use Math::PlanePath;
@ISA = ('Math::PlanePath');
*_divrem = \&Math::PlanePath::_divrem;

use Math::PlanePath::Base::Generic
  'is_infinite',
  'round_nearest';
use Math::PlanePath::Base::Digits
  'round_down_pow';

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


use constant n_start => 0;
use constant parameter_info_array =>
  [ { name            => 'parts',
      share_key       => 'parts_surroundoneeight',
      display         => 'Parts',
      type            => 'enum',
      default         => 4,
      choices         => ['4','1','octant','3mid','3side',
                          # 'side'
                         ],
      description     => 'Which parts of the plane to fill.',
    },
  ];
use constant class_x_negative => 1;
use constant class_y_negative => 1;

{
  my %x_negative = (4       => 1,
                    1       => 0,
                    octant  => 0,
                    '3mid'  => 1,
                    '3side' => 1,
                    side    => 0,
                   );
  sub x_negative {
    my ($self) = @_;
    return $x_negative{$self->{'parts'}};
  }
}
{
  my %x_minimum = (4       => undef,
                   1       => 0,
                   octant  => undef,
                   '3side' => undef,
                   '3mid'  => undef,
                   side    => 0,
                  );
  sub x_minimum {
    my ($self) = @_;
    return $x_minimum{$self->{'parts'}};
  }
}
{
  my %y_negative = (4       => 1,
                    1       => 0,
                    octant  => 0,
                    '3mid'  => 1,
                    '3side' => 1,
                    side    => 0,
                   );
  sub y_negative {
    my ($self) = @_;
    return $y_negative{$self->{'parts'}};
  }
}
{
  my %y_minimum = (4       => undef,
                   1       => 0,
                    octant => 0,
                   '3mid'  => undef,
                   '3side' => undef,
                   side    => 1,
                  );
  sub y_minimum {
    my ($self) = @_;
    return $y_minimum{$self->{'parts'}};
  }
}
{
  my %tree_num_children_maximum = (4       => 8,
                                   1       => 5,
                                   octant  => 3,
                                   '3mid'  => 5,
                                   '3side' => 3,
                                   side    => 3,
                                  );
  sub tree_num_children_maximum {
    my ($self) = @_;
    return $tree_num_children_maximum{$self->{'parts'}};
  }
}
# sub rsquared_minimum {
#   my ($self) = @_;
#   return ($self->{'parts'} <= 2   ? 1   # X=0,Y=1
#           : 0);                         # origin X=0,Y=0
# }

#------------------------------------------------------------------------------

sub new {
  my $self = shift->SUPER::new(@_);
  $self->{'parts'} ||= '4';
  return $self;
}


#------------------------------------------------------------------------------
# n_to_xy()

# use Smart::Comments;

my %initial_n_to_xy = (4       => [ [0,0], [1,0], [1,1], [0,1],
                                   [-1,1], [-1,0], [-1,-1], [0,-1], [1,-1] ],
                       1       => [ [0,0], [1,0], [1,1], [0,1] ],
                       octant  => [ [0,0], [1,0], [1,1] ],
                       '3mid'  => [ [0,0], [1,-1], [1,0], [1,1],
                                    [0,1], [-1,1] ],

                      # for 3side table up to N=8 because cell X=1,Y=2 at N=7
                       # is overlapped by two upper octants
                       '3side' => [ [0,0], [1,-1], [1,0], [1,1],
                                    [1,-2], [2,-2], [2,2], [1,2], [0,2] ],

                       side    => [ [0,1], [1,1] ]);

#                           0    1      2    3
my @octant_small_n_to_v = ([0], [0,1], [2], [1,2,3]);
# my @mirror_small_n_to_v = ([0], [1,0], [2], [3,2,1]);

# my @octant_mid_n_to_h = ([0], [ 1,1,1]);
my @octant_mid_n_to_v = ([0], [-1,0,1]);

sub n_to_xy {
  my ($self, $n) = @_;
  ### SurroundOneEight n_to_xy(): $n

  if ($n < 0) { return; }
  if (is_infinite($n)) { return ($n,$n); }

  {
    my $int = int($n);
    ### $int
    ### $n
    if ($n != $int) {
      my ($x1,$y1) = $self->n_to_xy($int);
      my ($x2,$y2) = $self->n_to_xy($int+1);
      my $frac = $n - $int;  # inherit possible BigFloat
      my $dx = $x2-$x1;
      my $dy = $y2-$y1;
      return ($frac*$dx + $x1, $frac*$dy + $y1);
    }
    $n = $int;       # BigFloat int() gives BigInt, use that
  }
  my $zero = $n*0;

  my $parts = $self->{'parts'};
  {
    my $initial = $initial_n_to_xy{$parts};
    if ($n <= $#$initial) {
      ### initial_n_to_xy{}: $initial->[$n]
      return @{$initial->[$n]};
    }
  }

  (my $depth, $n) = _n0_to_depth_and_rem($self, $n);
  ### $depth
  ### remainder n: $n
  ### cf this depth n: $self->tree_depth_to_n($depth)
  ### cf next depth n: $self->tree_depth_to_n($depth+1)

  # $hdx,$hdy is the dx,dy offsets which is "horizontal".  Initially this is
  # hdx=1,hdy=0 so horizontal along the X axis, but subsequent blocks rotate
  # around or mirror to point other directions.
  #
  # $vdx,$vdy is similar dx,dy which is "vertical".  Initially vdx=0,vdy=1
  # so vertical along the Y axis.
  #
  # $mirror is true if in a "mirror image" such as the 0<=X<=Y part of the
  # pattern.  The difference is that when plain points are numbered
  # anti-clockwise "upwards" towards the diagonal, but when mirrored instead
  # clockwise "down" from the diagonal.
  #
  # When $mirror is true the octant generated is still reckoned as 0<=Y<=X,
  # but the $hdx,$hdy and $vdx,$vdy are suitably mangled so that this
  # logical first octant ends up in whatever target is desired.  For example
  # the 0<=X<=Y second octant of the pattern starts with hdx=0,hdy=1 and
  # vdx=1,vdy=0, so the "horizontal" is upwards and the "vertical" is to the
  # right.
  #
  # $log2_extras is true if the extra cell at the log2 positions
  # X=3,7,15,31,etc and Y=1 should be included in the pattern.  Initially
  # true, but later in the "lower" block there are no such extra cells.
  #
  # $top_no_extra_pow is a 2^k power if the top of the diagonal at
  # X=pow-1,Y=pow-1 should not be included in the pattern.  Or 0 if this
  # diagonal cell should be included.  Initially true, but later going
  # "lower" followed by "upper" it's the end of the diagonal is not wanted.
  # The first such is at X=8,Y=2 which should not be in the "upper"
  # (mirrored) diagonal coming from X=11,Y=5.  In general if $log2_extras is
  # false then $top_no_extra_pow excludes that log2 cell when going to the
  # "upper" block.
  #
  my $x = 0;
  my $y = 0;
  my $hdx = 1;
  my $hdy = 0;
  my $vdx = 0;
  my $vdy = 1;
  my $mirror = 0;        # plain
  my $log2_extras = 1;   # include cells X=3,7,15,31;Y=1 etc
  my $top_no_extra_pow = 0;

  if ($parts eq 'octant') {
    ### parts=octant ...

  } elsif ($parts eq '1' || $parts eq '2' || $parts eq '4') {
    my $add = _depth_to_octant_added([$depth],[1],$zero);
    ### octant add: $add

    if ($parts eq '4') {
      # Half-plane is 4 octants, less 2 for duplicate diagonal.
      my $hadd = 4*$add-2;
      if ($n >= $hadd) {
        ### initial rotate 180 ...
        $n -= $hadd;
        $hdx = -1;
        $vdy = -1;
      }
    }
    if ($parts eq '2' || $parts eq '4') {
      # Each quadrant is 2 octants, less 1 for duplicate diagonal.
      my $qadd = 2*$add-1;
      if ($n >= $qadd) {
        ### initial rotate +90 ...
        $n -= $qadd;
        ($hdx,$hdy) = (-$hdy,$hdx);
        ($vdx,$vdy) = (-$vdy,$vdx);
      }
    }
    if ($n >= $add) {
      ### initial mirror ...
      $mirror = 1;
      ($hdx,$hdy, $vdx,$vdy)     # mirror by transpose
        = ($vdx,$vdy, $hdx,$hdy);
      $n -= $add;
      $n += 1; # excluding diagonal
    }

  } elsif ($parts eq '3mid') {
    my $add = _depth_to_octant_added([$depth+1],[1],$zero)
      - (_is_pow2($depth+2) ? 2 : 1);
    ### lower of side 1, excluding diagonal: "depth=".($depth+1)." add=".$add
    if ($n < $add) {
      ### lower of side 1 ...
      $hdx = 0;
      $hdy = -1;
      $vdx = 1;
      $vdy = 0;
      $log2_extras = 0;
      $depth += 1;
      $x -= 1;
      $y += 1;
    } else {
      $n -= $add;
      ### past side 1 lower, not past diagonal: "n=$n"

      $add = _depth_to_octant_added([$depth],[1],$zero);
      if ($n < $add) {
        ### upper of side 1 ...
        $vdy = -1;
        $mirror = 1;
      } else {
        $n -= $add;

        if ($n < $add) {
          ### lower of centre ...
        } else {
          $n -= $add;
          $n += 1;  # past diagonal

          if ($n < $add) {
            ### upper of centre ...
            $hdx = 0;
            $hdy = 1;
            $vdx = 1;
            $vdy = 0;
            $mirror = 1;
          } else {
            $n -= $add;

            if ($n < $add) {
              ### upper of side 3 ...
              $hdx = 0;
              $hdy = 1;
              $vdx = -1;
              $vdy = 0;
            } else {
              $n -= $add;
              $n += 1;  # past diagonal

              ### lower of side 3 ...
              $hdx = -1;
              $depth += 1;
              $x += 1;
              $y -= 1;
              $log2_extras = 0;
              $mirror =1;
            }
          }
        }
      }
    }

  } elsif ($parts eq '3side') {
    my $add = (_depth_to_octant_added([$depth+1],[1],$zero)
               - (_is_pow2($depth+2) ? 2 : 1));
    ### lower of side 1, excluding diagonal: "depth=".($depth+1)." add=".$add
    if ($n < $add) {
      ### lower of side 1 ...
      $hdx = 0;
      $hdy = -1;
      $vdx = 1;
      $vdy = 0;
      $log2_extras = 0;
      $depth += 1;
      $x -= 1; $y += 1;
    } else {
      $n -= $add;

      $add = _depth_to_octant_added([$depth],[1],$zero);
      ### plain add, including diagonal: "add=$add  cf n=$n"
      if ($n < $add) {
        ### upper of side 1 ...
        $vdy = -1;
        $mirror = 1;
      } else {
        $n -= $add;
        ### not upper of side 1, leaving n: $n

        if ($n < $add) {
          ### lower of centre, including diagonal ...
        } else {
          $n -= $add;
          $n += 1;  # past diagonal
          ### not lower of centre, and past diagonal to n: $n

          my $add = _depth_to_octant_added([$depth-1],[1],$zero);
          ### upper of centre, excluding diagonal: "depth=".($depth-1)." add-1=".$add
          if ($n < $add) {
            ### upper of centre ...
            $hdx = 0; $hdy = 1; $vdx = 1; $vdy = 0;
            $x += 1; $y += 1;
            $mirror = 1;
            $depth -= 1;
          } else {
            $n -= $add;
            ### not upper of centre, to n: $n

            if ($n < $add) {
              ### upper of side 3 ...
              $hdx = 0; $hdy = 1; $vdx = -1; $vdy = 0; # rotate -90
              $x += 1; $y += 1;
              $depth -= 1;
            } else {
              $n -= $add;
              $n += 1;  # past diagonal
              ### not upper of side 3, and past diagonal to n: $n

              ### lower of side 3 ...
              $hdx = -1;
              $x += 2;
              $log2_extras = 0;
              $mirror =1;
            }
          }
        }
      }
    }
  }

  ### adjusted to octant style: "depth=$depth remainder n=$n"

  my ($pow,$exp) = round_down_pow ($depth+1, 2);
  ### initial exp: $exp
  ### initial pow: $pow

  for ( ; $exp >= 0; $pow/=2, $exp--) {
    ### at: "pow=$pow exp=$exp depth=$depth n=$n mirror=$mirror log2extras=$log2_extras topnopow=$top_no_extra_pow  xy=$x,$y  h=$hdx,$hdy v=$vdx,$vdy"
    ### assert: $depth >= 1
    ### assert: $mirror == 0 || $mirror == 1

    if ($depth < $pow) {
      ### block 0 ...
      $top_no_extra_pow = 0;
      next;
    }

    if ($depth <= 3) {
      if ($mirror) {
        ### mirror small depth ...
        if ($depth == $top_no_extra_pow-1) {
          $n += 1;
          ### inc n for top_no_extra_pow: "to n=$n"
        }
        ### assert: $n <= $#{$octant_small_n_to_v[$depth]}
        $n = -1-$n;  # perl negative index to read array in reverse
      } else {
        ### small depth ...
        if (! $log2_extras && $depth == 3) {
          $n += 1;
          ### inc n for no log2_extras: "to n=$n"
        }
        ### assert: $n <= $#{$octant_small_n_to_v[$depth]}
      }
      my $v = $octant_small_n_to_v[$depth][$n];
      ### hv: "h=$depth, v=$v"
      $x += $depth*$hdx + $v*$vdx;     # $depth is "$h" horizontal position
      $y += $depth*$hdy + $v*$vdy;
      last;
    }

    $x += $pow * ($hdx + $vdx);   # $pow along diagonal
    $y += $pow * ($hdy + $vdy);
    $depth -= $pow;
    ### diagonal to: "depth=$depth  xy=$x,$y"

    if ($depth <= 1) {
      ### mid two levels ...
      if ($mirror) {
        ### negative perl array index to reverse for mirror state ...
        $n = -1-$n;
      }
      my $v = $octant_mid_n_to_v[$depth][$n];
      ### hv: "h=$depth v=$v"
      $x += $depth*$hdx + $v*$vdx;   # $depth is "$h" horizontal position
      $y += $depth*$hdy + $v*$vdy;
      last;
    }

    if ($mirror == 0) { # plain

      # See if $n within lower.
      # Not at depth+1==pow since lower has already finished then.
      #
      if ($depth+1 < $pow) {
        my $add = _depth_to_octant_added([$depth+1],[1],$zero);
        if (_is_pow2($depth+2)) {
          ### add lower decreased for remaining depth+2 a power-of-2 ...
          $add -= 1;
        }
        $add -= 1;
        ### add in lower, excluding diagonal: $add
        if ($n < $add) {
          ### lower, rotate +90 ...
          $top_no_extra_pow = 0;
          $log2_extras = 0;
          $depth += 1;
          ### assert: $depth < $pow
          ($hdx,$hdy, $vdx,$vdy)    # rotate 90 in direction v toward h
            = (-$vdx,-$vdy, $hdx,$hdy);
          $x -= $hdx + $vdx;
          $y -= $hdy + $vdy;
          next;
        }
        $n -= $add;
      } else {
        ### skip lower at depth==pow-1 ...
      }

      # See if $n within upper.
      #
      my $add = _depth_to_octant_added([$depth],[1],$zero);
      if (! $log2_extras && $depth+1 == $pow) {
        ### add upper decreased for no log2_extras at depth=pow-1 ...
        $add -= 1;
      }
      ### add in upper, including diagonal: $add
      if ($n < $add) {
        ### upper, mirror ...
        $mirror = 1;
        $vdx = -$vdx;  # flip vertically
        $vdy = -$vdy;
        $top_no_extra_pow = ($log2_extras ? 0 : $pow);
        $log2_extras = 1;
        next;
      }
      $n -= $add;
      ### assert: $n < $add

      # Otherwise $n is within extend.
      #
      ### extend ...
      $top_no_extra_pow /= 2;
      $log2_extras = 1;

    } else {
      # $mirror == 1, mirrored

      # See if $n within extend.
      #
      my $eadd = my $add = _depth_to_octant_added([$depth],[1],$zero);
      $top_no_extra_pow /= 2;  # since after $depth+=$pow
      if ($depth == $top_no_extra_pow - 1) {
        ### add extend decreased for no top extra ...
        $eadd -= 1;
      }
      ### add in extend: $eadd
      if ($n < $eadd) {
        ### extend ...
        $log2_extras = 1;
        next;
      }
      $n -= $eadd;

      # See if $n within upper.
      #
      ### add in upper, including diagonal: "$add cf n=$n"
      if ($n < $add) {
        ### upper, unmirror ...
        $top_no_extra_pow = ($log2_extras ? 0 : $pow);
        $log2_extras = 1;
        $mirror = 0;
        $vdx = -$vdx;  # flip vertically
        $vdy = -$vdy;
        next;
      }
      $n -= $add;

      # Otherwise $n is within lower.
      #
      $n += 1; # past diagonal
      ### lower, rotate: "n=$n"
      ### assert: $n < _depth_to_octant_added([$depth+1],[1],$zero)
      $top_no_extra_pow = 0;
      $log2_extras = 0;
      $depth += 1;
      ### assert: $depth < $pow
      ($hdx,$hdy, $vdx,$vdy)    # rotate 90 in direction v toward h
        = (-$vdx,-$vdy, $hdx,$hdy);
      $x -= $hdx + $vdx;
      $y -= $vdx + $vdy;
    }
  }

  ### n_to_xy() return: "$x,$y  (depth=$depth n=$n)"
  return ($x,$y);
}

no Smart::Comments;

# use Smart::Comments;

# ($depth, $nrem) = _n0_to_depth_and_rem($self,$n)
#
# _n0_to_depth_and_rem() finds the tree $depth level containing $n and
# returns that $depth and the offset of $n into that level, being
# $n - $self->tree_depth_to_n($depth).
#
# The current approach is a binary search for the bits of depth which have
# tree_depth_to_n($depth) <= $n.
#
# Ndepth grows as roughly depth*depth, so this is about log4(N) many bsearch
# compares.  Maybe for modest N a table of depth->N could be used for the
# search (and for tree_depth_to_n()).  It would cover up to about sqrt(N),
# so for large N would still need some searching code.
#
# quadrant(2^k) = (4*4^k + 6*k + 14) / 9
# N*9/4 = 4^k + 6/4*k + 14/4
# parts=1      N*9 to round up to next power
# parts=octant N*18
# parts=4      N*9/4 = N*3 as estimate
# parts=3      N*9/4 = N*3 too
#
my %parts_to_depth_multiplier = (4       => 3,
                                 1       => 9,
                                 octant  => 18,
                                 '3mid' => 3,
                                 '3side' => 3,
                                 side    => 9,
                                );
sub _n0_to_depth_and_rem {
  my ($self, $n) = @_;
  ### _n0_to_depth_and_rem(): "n=$n   parts=$self->{'parts'}"

  my ($pow,$exp) = round_down_pow
    ($n * $parts_to_depth_multiplier{$self->{'parts'}},
     4);
  if (is_infinite($exp)) {
    return ($exp,0);
  }
  ### $pow
  ### $exp

  my $depth = 0;
  my $n_depth = 0;
  $pow = 2 ** $exp;  # pow=2^exp down to 1, inclusive

  while ($exp-- >= 0) {
    my $try_depth = $depth + $pow;
    my $try_n_depth = $self->tree_depth_to_n($try_depth);

    ### $depth
    ### $pow
    ### $try_depth
    ### $try_n_depth

    if ($try_n_depth <= $n) {
      ### use this tried depth ...
      $depth = $try_depth;
      $n_depth = $try_n_depth;
    }
    $pow /= 2;
  }

  ### _n0_to_depth_and_rem() final ...
  ### $depth
  ### remainder: $n - $n_depth

  return ($depth, $n - $n_depth);
}

no Smart::Comments;

#------------------------------------------------------------------------------
# xy_to_n()

my @yxoct_to_n = ([     0, 1 ],   # Y=0
                  [ undef, 2 ]);  # Y=1
my @yx1_to_n = ([ 0, 1 ],   # Y=0
                [ 3, 2 ]);  # Y=1
my @yx3_to_n = ([     0, 2, undef ],   # Y=0   X=0,1,-1
                [     4, 3,     5 ],   # Y=1
                [ undef, 1, undef ]);  # Y=-1
my @yx4_to_n = ([ 0, 1, 5 ],   # Y=0   X=0,1,-1
                [ 3, 2, 4 ],   # Y=1
                [ 7, 8, 6 ]);  # Y=-1
my @yx3mid_to_n = ([     0, 2, undef ],   # Y=0   X=0,1,-1
                   [     4, 3, 5     ],   # Y=1
                   [ undef, 1, undef ]);  # Y=-1
my @yx3side_to_n = ([     0, 2, undef ],   # Y=0   X=0,1,-1
                    [ undef, 3, 5     ],   # Y=1
                    [     8, 7, 16    ],   # Y=2
                    [ undef, 4, undef ],   # Y=-2
                    [ undef, 1, undef ]);  # Y=-1

# N values relative to tree_depth_to_n() start of the depth level
my @yx_to_n = ([ [     0,     0,          ],  # plain
                 [ undef,     1, undef, 0 ],
                 [ undef, undef,     0, 1 ],
                 [ undef, undef, undef, 2 ] ],
               [ [     0,     1,          ],  # mirror
                 [ undef,     0, undef, 2 ],
                 [ undef, undef,     0, 1 ],
                 [ undef, undef, undef, 0 ] ]);

#use Smart::Comments;

sub xy_to_n {
  my ($self, $x, $y) = @_;
  ### SurroundOneEight xy_to_n(): "$x, $y"

  if (0) {
    require Math::PlanePath::SurroundOneEightByCells;
    my $cells = ($self->{'cells'} ||= Math::PlanePath::SurroundOneEightByCells->new (parts => $self->{'parts'}));
    return $cells->xy_to_n($x,$y);
  }

  $x = round_nearest ($x);
  $y = round_nearest ($y);
  if (is_infinite($x)) {
    return $x;
  }
  if (is_infinite($y)) {
    return $y;
  }

  my ($pow,$exp) = round_down_pow (max(abs($x),abs($y))+2, 2);
  ### initial pow: "exp=$exp  pow=$pow"
  ### from abs(x): abs($x)
  ### from abs(y): abs($y)
  ### from max: max(abs($x),abs($y))

  if (is_infinite($exp)) {
    return $exp;
  }

  my $zero = $x * 0 * $y;
  my @add_offset;
  my @add_mult;
  my @add_log2_extras;
  my @add_top_no_extra_pow;
  my @add_is_block1;
  my @centre_offset;
  my @centre_mult;
  my $mirror = 0;
  my $log2_extras = 1;
  my $top_extra = 1;
  my $top_no_extra_pow = 0;
  my $depth = 0;
  my $n = $zero;

  my $parts = $self->{'parts'};
  if ($parts eq 'octant') {
    ### parts==octant ...
    if ($y < 0 || $y > $x) {
      return undef;
    }
    if ($x <= 1 && $y <= 1) {
      return $yxoct_to_n[$y][$x];
    }

  } elsif ($parts eq '1' || $parts eq '4') {
    my $mult = 0;
    if ($parts eq '1') {
      ### parts==1 ...
      if ($x <= 1 && $y <= 1) {
        return $yx1_to_n[$y][$x];
      }
      if ($x < 0 || $y < 0) {
        return undef;
      }
    } else {
      ### parts==4 ...
      if (abs($x) <= 1 && abs($y) <= 1) {
        return $yx4_to_n[$y][$x];
      }
      if ($y < 0) {
        ### quad 3 or 4, rotate 180 ...
        $mult = 4;  # past first,second quads
        $n -= 2;    # unduplicate diagonals
        $x = -$x;  # rotate 180
        $y = -$y;
      }
      if ($x < 0) {
        ### quad 2 (or 4), rotate 90 ...
        $mult += 2;
        $n -= 1;  # unduplicate diagonal
        ($x,$y) = ($y,-$x);  # rotate -90
      }
    }

    ### now in first quadrant: "x=$x y=$y"
    if ($y > $x) {
      ### second octant, transpose and mirror ...
      ($x,$y) = ($y,$x);
      $mult++;
      $n -= 1;  # unduplicate diagonal
      $mirror = 1;
    }
    if ($mult) {
      push @add_offset,           0;
      push @add_mult,             $mult;
      push @add_top_no_extra_pow, 0;
      push @add_log2_extras,      1;
      push @add_is_block1,        1;
    }

  } elsif ($parts eq '3mid') {
    ### parts==3mid ...
    if (abs($x) <= 1 && abs($y) <= 1) {
      ### 3mid small: $yx3mid_to_n[$y][$x]
      return $yx3mid_to_n[$y][$x];
    }
    if ($y < 0) {
      if ($x < 0) {
        ### third quadrant, no such point ...
        return undef;
      }
      $y = -$y;
      if ($y >= $x) {
        ### block 0 lower ...
        $log2_extras = 0;
        ($x,$y) = ($y+1,$x+1);
        $depth = -1;
      } else {
        ### block 1 upper ...
        $mirror = 1;

        ### past block 0 lower, excluding diagonal ...
        push @add_offset,          -1;
        push @add_mult,             1;
        push @add_top_no_extra_pow, 0;
        push @add_log2_extras,      0;
        push @add_is_block1,        1;
        $n -= 1;  # excluding diagonal
      }
    } else {
      if ($x >= 0) {
        if ($y <= $x) {
          ### block 2 first octant ...

          ### past block 0 lower, excluding diagonal ...
          push @add_offset,          -1;
          push @add_mult,             1;
          push @add_top_no_extra_pow, 0;
          push @add_log2_extras,      0;
          push @add_is_block1,        1;
          $n -= 1;  # excluding diagonal

          ### past block 1 ...
          push @add_offset,           0;
          push @add_mult,             1;
          push @add_top_no_extra_pow, 0;
          push @add_log2_extras,      1;
          push @add_is_block1,        1;

        } else {
          ### block 3 second octant ...
          ($x,$y) = ($y,$x);
          $mirror = 1;

          ### past block 0 lower, excluding diagonal ...
          push @add_offset,          -1;
          push @add_mult,             1;
          push @add_top_no_extra_pow, 0;
          push @add_log2_extras,      0;
          push @add_is_block1,        1;
          $n -= 1;  # excluding diagonal

          ### past blocks 1,2, excluding leading diagonal ...
          push @add_offset,           0;
          push @add_mult,             2;
          push @add_top_no_extra_pow, 0;
          push @add_log2_extras,      1;
          push @add_is_block1,        1;
          $n -= 1;  # excluding leading diagonal
        }
      } else {
        ### second quadrant ...
        $x = -$x;
        if ($y >= $x) {
          ### block 4 third octant ...
          ($x,$y) = ($y,$x);

          ### past block 0 lower, excluding diagonal ...
          push @add_offset,          -1;
          push @add_mult,             1;
          push @add_top_no_extra_pow, 0;
          push @add_log2_extras,      0;
          push @add_is_block1,        1;
          $n -= 1;  # excluding diagonal

          ### past blocks 1,2,3 excluding leading diagonal ...
          push @add_offset,           0;
          push @add_mult,             3;
          push @add_top_no_extra_pow, 0;
          push @add_log2_extras,      1;
          push @add_is_block1,        1;
          $n -= 1;  # excluding leading diagonal

        } else {
          ### block 5 fourth octant ...
          $x += 1; $y += 1;
          $mirror = 1;
          $depth = -1;
          $log2_extras = 0;

          ### past block 0 lower, excluding diagonal ...
          push @add_offset,          -1;
          push @add_mult,             1;
          push @add_top_no_extra_pow, 0;
          push @add_log2_extras,      0;
          push @add_is_block1,        1;
          $n -= 1;  # excluding diagonal

          push @add_offset,           0;
          push @add_mult,             4;
          push @add_top_no_extra_pow, 0;
          push @add_log2_extras,      1;
          push @add_is_block1,        1;
          $n -= 2;  # unduplicate two diagonals
        }
      }
    }

  } elsif ($parts eq '3side') {
    ### parts==3side ...
    if (abs($x) <= 1 && abs($y) <= 2) {
      ### 3side small: $yx3side_to_n[$y][$x]
      return $yx3side_to_n[$y][$x];
    }
    if ($y < 0) {
      if ($x < 0) {
        ### third quadrant, no such point ...
        return undef;
      }
      $y = -$y;
      if ($y >= $x) {
        ### block 0 lower ...
        $log2_extras = 0;
        ($x,$y) = ($y+1,$x+1);
        $depth = -1;
      } else {
        ### block 1 upper ...
        $mirror = 1;

        ### past block 0 lower, excluding diagonal ...
        push @add_offset,          -1;
        push @add_mult,             1;
        push @add_top_no_extra_pow, 0;
        push @add_log2_extras,      0;
        push @add_is_block1,        1;
        $n -= 1;  # excluding diagonal
      }
    } else {
      if ($x > 0) {
        if ($y <= $x) {
          ### block 2 first octant ...

          ### past block 0 lower, excluding diagonal ...
          push @add_offset,          -1;
          push @add_mult,             1;
          push @add_top_no_extra_pow, 0;
          push @add_log2_extras,      0;
          push @add_is_block1,        1;
          $n -= 1;  # excluding diagonal

          ### past block 1 ...
          push @add_offset,           0;
          push @add_mult,             1;
          push @add_top_no_extra_pow, 0;
          push @add_log2_extras,      1;
          push @add_is_block1,        1;

        } else {
          ### block 3 second octant ...
          ($x,$y) = ($y-1,$x-1);
          $depth = 1;
          $mirror = 1;

          ### past block 0 lower, excluding diagonal ...
          push @add_offset,          -1;
          push @add_mult,             1;
          push @add_top_no_extra_pow, 0;
          push @add_log2_extras,      0;
          push @add_is_block1,        1;
          $n -= 1;  # excluding diagonal

          ### past block 1,2, excluding leading diagonal ...
          push @add_offset,           0;
          push @add_mult,             2;
          push @add_top_no_extra_pow, 0;
          push @add_log2_extras,      1;
          push @add_is_block1,        1;
          $n -= 1;  # excluding leading diagonal
        }
      } else {
        ### second quadrant ...
        $x = 2-$x;
        ### X mirror to: "x=$x y=$y"

        if ($y >= $x) {
          ### block 4 third octant ...
          ($x,$y) = ($y-1,$x-1);
          ### transpose to: "x=$x y=$y"
          $depth = 1;

          ### past block 0 lower, excluding diagonal ...
          push @add_offset,          -1;
          push @add_mult,             1;
          push @add_top_no_extra_pow, 0;
          push @add_log2_extras,      0;
          push @add_is_block1,        1;
          $n -= 1;  # excluding diagonal

          ### past block 1,2, excluding leading diagonal ...
          push @add_offset,           0;
          push @add_mult,             2;
          push @add_top_no_extra_pow, 0;
          push @add_log2_extras,      1;
          push @add_is_block1,        1;
          $n -= 1;  # excluding leading diagonal

          ### past block 3 ...
          push @add_offset,           1;
          push @add_mult,             1;
          push @add_top_no_extra_pow, 0;
          push @add_log2_extras,      1;
          push @add_is_block1,        1;

        } else {
          ### block 5 fourth octant ...
          $mirror = 1;
          $log2_extras = 0;

          ### past block 0 lower, excluding diagonal ...
          push @add_offset,          -1;
          push @add_mult,             1;
          push @add_top_no_extra_pow, 0;
          push @add_log2_extras,      0;
          push @add_is_block1,        1;
          $n -= 1;  # excluding diagonal

          ### past block 1,2, excluding leading diagonal ...
          push @add_offset,           0;
          push @add_mult,             2;
          push @add_top_no_extra_pow, 0;
          push @add_log2_extras,      1;
          push @add_is_block1,        1;
          $n -= 1;  # unduplicate leading diagonal

          ### past block 3,4 ...
          push @add_offset,           1;
          push @add_mult,             2;
          push @add_top_no_extra_pow, 0;
          push @add_log2_extras,      1;
          push @add_is_block1,        1;
          $n -= 1;  # excluding block4 diagonal
        }
      }
    }

  } elsif ($parts eq '2') {
    ### parts==2 ...
    # if ($x == 0) {
    #   if ($y == 1) { return 0; }
    # }
    # if ($y == 1) {
    #   if ($x == 1) { return 1; }
    #   if ($x == -1) { return 2; }
    # }
    # if ($x < 0) {
    #   ### initial mirror second quadrant ...
    #   $x = -$x;
    #   $mirror = 1;
    #   push @add_offset, -1;
    #   push @add_mult, 1;
    # }
  }

  if ($x == 0 || $y == 0) {
    ### nothing on axes after origin ...
    return undef;
  }

  for (;;) {
    ### at: "x=$x,y=$y  n=$n  pow=$pow depth=$depth mirror=$mirror log2_extras=$log2_extras top_extra=$top_extra top_no_extra_pow=$top_no_extra_pow"
    ### assert: $x >= 0
    ### assert: $x < 2 * $pow
    ### assert: $y >= 0
    ### assert: $y <= $x

    if ($x <= 3) {
      ### loop small XY ...
      ### $top_no_extra_pow

      if ($x == 3) {
        if (! $log2_extras) {
          if ($y == 1) {
            ### no log2_extras ...
            return undef;
          }
          if (! $mirror) {
            ### no log2_extras, N decrement, (not mirrored) ...
            $n -= 1;
          }
        }
        if ($top_no_extra_pow == 4) {
          if ($y == 3) {
            ### no top extra, so no such point ...
            return undef;
          }
          ### top_no_extra_pow, N decrement by mirror: $mirror
          $n -= $mirror;
        }
      }

      my $nyx = $yx_to_n[$mirror][$y][$x];
      ### $nyx
      if (! defined $nyx) {
        ### no such point ...
        return undef;
      }
      $n += $nyx;
      $depth += $x;
      last;
    }

    if ($x == $pow) {
      if ($y == $pow) {
        ### mid X=pow,Y=pow, stop ...
        $depth += $pow;
        last;
      }
      ### X=pow no such point ...
      return undef;
    } elsif ($x == $pow+1) {
      if ($y == $pow-1) {
        ### mid X=pow+1,Y=pow-1, stop ...
        $depth += $pow+1;
        $n += ($mirror ? 2 : 0);
        last;
      }
      if ($y == $pow) {
        ### mid X=pow+1,Y=pow, stop ...
        $depth += $pow+1;
        $n += 1;
        last;
      }
      if ($y == $pow+1) {
        ### mid X=pow+1,Y=pow+1, stop ...
        $depth += $pow+1;
        $n += ($mirror ? 0 : 2);
        last;
      }
    }

    if ($x < $pow) {
      ### base block ...
      $top_no_extra_pow = 0;

    } else {
      $x -= $pow;
      $depth += $pow;
      if ($y < $pow) {
        $y = $pow-$y;
        ### Y flip to: $y

        if ($y > $x) {
          ### block lower, excluding diagonal ...
          ($x,$y) = ($y+1,$x+1);
          ### rotate to: "x=$x y=$y"
          ### assert: $y >= 0
          unless ($y && $x < $pow) {
            ### Y=0 or X>=pow, no such point ...
            return undef;
          }
          $top_no_extra_pow = 0;
          $log2_extras = 0;
          $depth -= 1;
          if ($mirror) {
            ### offset past extend,upper, undup diagonal, (mirrored) ...
            push @add_offset,           $depth+1;
            push @add_mult,             2;
            push @add_top_no_extra_pow, $top_no_extra_pow/2;
            push @add_log2_extras,      1;
            push @add_is_block1,        0;
            $n -= 1;  # duplicated diagonal upper,lower
          }

        } else {
          ### block upper ...
          if ($mirror) {
            ### offset past extend (mirrored) ...
            push @add_offset,           $depth;
            push @add_mult,             1;
            push @add_top_no_extra_pow, $top_no_extra_pow/2;
            push @add_log2_extras,      1;
            push @add_is_block1,        0;
          } else {
            if ($x < $pow-1) {
              ### offset past lower, unduplicate diagonal, (not mirrored) ...
              push @add_offset, $depth-1;
              push @add_mult, 1;
              push @add_top_no_extra_pow, 0;
              push @add_log2_extras, 0;
              push @add_is_block1, 1;
              $n -= 1;  # duplicated diagonal upper,lower
            }
          }
          $top_no_extra_pow = ($log2_extras ? 0 : $pow);
          $log2_extras = 1;
          $mirror ^= 1;
        }
      } else {
        ### extend, same ...
        unless ($x) {
          ### on X=0, past block3, no such point ...
          return undef;
        }
        if ($mirror) {
          ### no offset past lower at X=pow-1 ...
        } else {
          if ($x < $pow-1) {
            ### offset past lower (not mirrored) ...
            push @add_offset,           $depth-1;
            push @add_mult,             1;
            push @add_top_no_extra_pow, 0;
            push @add_log2_extras,      0;
            push @add_is_block1,        1;
            $n -= 1;  # duplicated diagonal
          }
          ### offset past upper (not mirrored) ...
          push @add_offset,           $depth;
          push @add_mult,             1;
          push @add_top_no_extra_pow, ($log2_extras ? 0 : $pow);
          push @add_log2_extras,      1;
          push @add_is_block1,        0;
          # if (! $log2_extras) {
          #   ### no log2_extras so N decrement ...
          #   $n -= 1;
          # }
        }
        $y -= $pow;
        $log2_extras = 1;
        $top_extra = 1;
        $top_no_extra_pow /= 2;
      }
    }

    if (--$exp < 0) {
      ### final xy: "$x,$y"
      if ($x == 1 && $y == 1) {
      } elsif ($x == 1 && $y == 2) {
        $depth += 1;
      } else {
        ### not in final position ...
        return undef;
      }
      last;
    }
    $pow /= 2;
  }


  ### final depth: $depth
  ### $n
  ### depth_to_n: $self->tree_depth_to_n($depth)
  ### add_offset: join(',',@add_offset)
  ### add_mult:   join(',',@add_mult)
  ### assert: scalar(@add_offset) == scalar(@add_mult)
  ### assert: scalar(@add_offset) == scalar(@add_is_block1)
  ### assert: scalar(@add_offset) == scalar(@add_log2_extras)
  ### assert: scalar(@add_offset) == scalar(@add_top_no_extra_pow)

  $n += $self->tree_depth_to_n($depth);

  if (@add_offset) {
    foreach my $i (0 .. $#add_offset) {
      my $d = $add_offset[$i] = $depth - $add_offset[$i];

      # if ($add_is_block1[$i] && $d == $add_top_no_extra_pow[$i]) {
      #   ### add exclude block1 ended pow2: "d=$d"
      #   $add_mult[$i] = 0;
      # }
      if ($d+1 == $add_top_no_extra_pow[$i]) {
        ### no top_extra, decrement applied: "d=$d"
        $n -= 1;
      }
      if (! $add_log2_extras[$i] && $d >= 3 &&  _is_pow2($d+1)) {
        ### no log2_extras, decrement applied: "depth d=$d"
        $n -= 1;
      }

      ### add: "depth=$add_offset[$i] is "._depth_to_octant_added([$add_offset[$i]],[1],$zero)." x $add_mult[$i]   is_block1=$add_is_block1[$i] log2_extras=$add_log2_extras[$i] top_no_extra_pow=$add_top_no_extra_pow[$i]"
    }

    ### total add: _depth_to_octant_added ([@add_offset], [@add_mult], $zero)
    $n += _depth_to_octant_added (\@add_offset, \@add_mult, $zero);
  }

  ### xy_to_n() return n: $n
  return $n;
}

no Smart::Comments;


#------------------------------------------------------------------------------
# rect_to_n_range()

# not exact
sub rect_to_n_range {
  my ($self, $x1,$y1, $x2,$y2) = @_;
  ### SurroundOneEight rect_to_n_range(): "$x1,$y1  $x2,$y2"

  $x1 = round_nearest ($x1);
  $y1 = round_nearest ($y1);
  $x2 = round_nearest ($x2);
  $y2 = round_nearest ($y2);
  ($x1,$x2) = ($x2,$x1) if $x1 > $x2;
  ($y1,$y2) = ($y2,$y1) if $y1 > $y2;
  my $parts = $self->{'parts'};

  my $extra = ($parts eq '3side' ? 1 : 0);
  my ($pow,$exp) = round_down_pow (max(1,
                                       abs($x1),
                                       abs($x2)+$extra,
                                       abs($y1),
                                       abs($y2)+$extra),
                                   2);

  if ($parts eq '1') {
    # (total(2^k)+3)/4 = ((16*4^k + 24*k - 7)/9 + 3)/4
    #                  = (16*4^k + 24*k - 7 + 27)/9/4
    #                  = (16*4^k + 24*k + 20)/9/4
    #                  = (4*4^k + 6*k + 5)/9
    # applied to k=exp+1 2*pow=2^k
    #                  = (4* 2*pow * 2*pow + 6*(exp+1) + 5)/9
    #                  = (16*pow*pow + 6*exp + 11)/9
    return (0, (16*$pow*$pow + 6*$exp + 11) / 9);
  }

  # $parts eq '4'
  # total(2^k) = (16*4^k + 24*k - 7)/9
  # applied to k=exp+1 2*pow=2^k
  #            = (16 * 2*pow * 2*pow + 24*(exp+1) - 7) / 9
  #            = (64*pow*pow + 24*exp + 24-7) / 9
  #            = (64*pow*pow + 24*exp + 17) / 9
  return (0, (64*$pow*$pow + 24*$exp + 17) / 9);
}

sub tree_n_to_depth {
  my ($self, $n) = @_;
  ### tree_n_to_depth(): "$n"

  if ($n < 0) {
    return undef;
  }
  my ($depth) = _n0_to_depth_and_rem($self, int($n));
  ### n0 depth: $depth
  return $depth;
}

my @surround8_dx = (1, 1, 0, -1, -1, -1,  0,  1);
my @surround8_dy = (0, 1, 1,  1,  0, -1, -1, -1);

sub tree_n_children {
  my ($self, $n) = @_;
  ### tree_n_children(): $n

  my ($x,$y) = $self->n_to_xy($n)
    or return undef;
  ### $x
  ### $y

  my $depth = $self->tree_n_to_depth($n) + 1;
  return
    sort {$a<=>$b}
      grep { $self->tree_n_to_depth($_) == $depth }
        map { $self->xy_to_n_list($x + $surround8_dx[$_],
                                  $y + $surround8_dy[$_]) }
          0 .. $#surround8_dx;
}
sub tree_n_parent {
  my ($self, $n) = @_;

  if ($n < 0) {
    return undef;
  }
  my ($x,$y) = $self->n_to_xy($n)
    or return undef;
  my $parent_depth = $self->tree_n_to_depth($n) - 1;

  foreach my $i (0 .. $#surround8_dx) {
    my $pn = $self->xy_to_n($x + $surround8_dx[$i],
                            $y + $surround8_dy[$i]);
    if (defined $pn && $self->tree_n_to_depth($pn) == $parent_depth) {
      return $pn;
    }
  }
  return undef;
}

#
#    1        1  1
#    2        9  1001
#    4       33  100001
#    8      121  1111001
#   16      465  111010001
#   32     1833  11100101001
#   64     7297  1110010000001
#  128    29145  111000111011001
#  256   116529  11100011100110001
#  512   466057  1110001110010001001
# 1024  1864161  111000111000111100001
#
# before 1  2 3  4 5  6  7  8  9 10 11 12 13  14  15   16
# side = 0, 1,3, 6,9,14,21, 27,30,35,43,52,63,80,100, 112
#                              3,5,8,9,11,17,20,12
#
# side(5)  = side(4) + side(2) + 2*side(1) + 2
#          = 6 + 1 + 2*0 + 2 = 9
# side(9)  = side(8) + side(1) + 2
# side(10) = side(8) + side(3) + 2*side(2) + 3 = 27 + 3 + 2*1 + 3 = 35
# side(11) = side(8) + side(4) + 2*side(3) + log2(4/4) + 3 = 27+6+2*3+1+3 = 42
#
# side(2^k) = 4*side(2^(k-1)) -1   block 1 missing one in corner
#                             + k-2  block 2 extra lower
#                             + 3    centre A,B,C
#           = 4*side(2^(k-1)) + k
#   = k + (k-1)*4^1 + (k-2)*4^2 + ... + 2*4^(k-1) + 4^k
# eg. k=3  3+2*4+1*16 = 27
#          = 1 + 1+4 + 1+4+16 = 1 + 5 + 21
#    sum 1+4+...+4^(k-1) = (4^k-1)/3
# side(2^k) = (4^k-1)/3 + (4^(k-1)-1)/3 + ... + (4^1-1)/3
#           = (4^k - 1 + 4^(k-1) - 1 + ... + 4^1 - 1)/3    # k terms 4^k to 4^1
#           = (4^k + 4^(k-1) + ... + 4^1 - k)/3
#           = (4^k + 4^(k-1) + ... + 4^1 + 4^0 - 1 - k)/3
#           = ((4^(k+1)-1)/3 - 1 - k)/3
#           = (4^(k+1)-1 - 3*k - 3)/9
#           = (4*4^k - 3*k - 4)/9
#
# side(2^1=2) = 1
# side(2^2=4) = 1 + 1-1 + 1+0 + 1 + 3 = 6 = 4*1 + 2 = 4^1 + 2
# side(2^3=8) = 6 + 6-1 + 6+1 + 6 + 3 = 27 = 4*6 + 3 = 4^2 + 4*2+3
# side(2^4=16) = 27+27-1 +27+2 +27 + 3 = 112 = 4*27 + 4 = 4^3 + 16*2+4*3+4
#
#
#
#  centre(2^k) = 2*side(2^(k-1)) + 2*centre(2^(k-1))
#  centre(1) = 1
#  centre(2) = 4
#  centre(4) = 2*side(2) + 2*centre(2)
#            = 2*side(2) + 2*4
#            = 2*1 + 2*4 = 10
#  centre(8) = 2*side(4) + 2*centre(4)  = 2*6+2*10 = 32
#            = 2*side(4) + 2*(2*side(2) + 2*4)
#            = 2*side(4) + 4*side(2) + 4*4
#            = 2*6 + 4*1 + 4*4 = 32
#  centre(16) = 2*side(4) + 2*centre(4) = 2*6+2*10 = 32
#            = 2*side(8) + 4**side(4) + 8*side(2) + 8
#            = 2*27 + 4*6 + 8*1 + 8 = 94
#
# 4parts = 4*centre - 7
# 4parts(4) = 4*10-7 = 33
# 4parts(8) = 4*32-7 = 121
#
# 3side total 0,1, 4, 9,17
#              +1 +3 +5 +8
#
# centre(2^k)
#   = 2*side(2^(k-1)) + 2*centre(2^(k-1))
#   = 2*side(2^(k-1) + 2^2*side(2^(k-1) + ... + 2^(k-1)*side(2^1) + 2^(k-1)*4
#   k-1 many terms, and constant at end
# side(2^k) = (4*4^k - 3*k - 4)/9
#
# constant part
# 2 + 4 + ... + 2^(k-1)
#   = 2^k - 2
# eg. k=2 2
# eg. k=3 2 + 4 = 6
# eg. k=4 2 + 4 + 8 = 14
#
# linear part
# 2*(k-1) + 4*(k-2) + ... + 2^(k-1)*(1) + 2^k*(0)
#   = 2^(k-1)-1 + 2^(k-2)-1 + ... + 2-1
#   = 2*2^k - 2*k - 2
# eg. k=2 2*1 = 2
# eg. k=3 2*2 + 4*1 = 8
# eg. k=4 2*3 + 4*2 + 8*1 =  22
# eg. k=5 2*4 + 4*3 + 8*2 + 16*1 = 52
#
# exponential part
# 2*4^(k-1) + 4*4^(k-2) + 8*4^(k-3) + ... + 2^(k-1)*4^1
#   = 2^(2k-2+1) + 2^(2k-4+2) + 2^(2k-6+3) + ... + 2^(k+1)
#   = 2^(2k-1) + 2^(2k-2) + 2^(2k-3) + ... + 2^(k+1)
#   = 2^(k+1) * [ 2^(k-2) + 2^(k-3) + 2^(k-4) + ... + 2^(0) ]
#   = 2^(k+1) * (2^(k-1) - 1)
#   = 2^k * (2^k - 2)
# eg. k=2 2*4^1 = 8
# eg. k=3 2*4^2 + 4*4^1 = 48
# eg. k=4 2*4^3 + 4*4^2 + 8*4^1 = 224
# eg. k=5 2*4^4 + 4*4^3 + 8*4^2 + 16*4^1 = 960
#
# centre(2^k) = (4*(2^k * (2^k - 2)) - 3*(2*2^k-2*k-2) - 4*(2^k-2)) / 9 + 2*2^k
# eg. k=2  sidepart = 2*1 = 1  plus
# eg. k=3  sidepart = 2*6 + 4*1 = 16
# eg. k=4  sidepart = 2*27 + 4*6 + 8*1 = 86
#   = (4*(2^k * (2^k - 2)) - 3*(2*2^k-2*k-2) - 4*(2^k-2)) / 9 + 2*2^k
#   = (4*2^k*(2^k - 2) - 6*2^k + 3*2*k + 6 - 4*2^k + 8 + 18*2^k) / 9
#   = (4*2^k*2^k - 8*2^k - 6*2^k + 3*2*k - 4*2^k + 18*2^k + 14) / 9
#   = (4*2^k*2^k + 6*k + 14) / 9
#   = (4*depth^2 + 6*k + 14) / 9
#
# centre(2^k) = (4*4^k + 6*k + 14) / 9
# side(2^k)   = (4*4^k - 3*k - 4) / 9
# diff = (9k+18)/9 = k+2
# double centre(2^(k+1)) - 4*centre(2^k)
#   = (4*4^(k+1) + 6*(k+1) + 14 - 4*(4*4^k + 6*k + 14)) / 9
#   = (4*4*4^k + 6*k + 6 + 14 - 4*4*4^k - 4*6*k - 4*14) / 9
#   = (6*k - 4*6*k + 6 + 14 - 4*14) / 9
#   = (-18*k - 36) / 9
#   = -2*k - 4
# smaller than 4* on each doubling
# 6k+14 term only adds extra 6, doesn't go 4*(6k+14)
#
# side(pow+rem) = side(pow) + side(rem+1)   -1 if rem+1=pow
#                           + side(rem)
#                           + side(rem) + log2(rem+1) + 2
# except rem==1 is side(pow)+3
# eg side(5) = side(4) + 3
#            = 6       + 3 = 9
# eg side(6) = side(4) + side(3) + 2*side(2) + log2(3)+2
#            = 6       + 3       + 2*1         +1   + 2 = 14
#
# centre(pow+rem) = centre(pow) + centre(rem) + 2*side(rem)
#                 = 2*side(pow/2) + 4*side(pow/4) + ...
#                   + centre(rem) + 2*side(rem)

# d = p1+p2+p3+p4
# C(d) = C(p1) + 2*S(p2+p3+p4) + C(p2+p3+p4)
#      = C(p1) + 2*S(p2+p3+p4) + C(p2) + 2*S(p3+p4) + C(p3+p4)
#      = C(p1) + C(p2) + 2*S(p2+p3+p4) + 2*S(p3+p4) + C(p3) + C(p4) + 2*S(p4)
#      = C(p1) + C(p2) + C(p3) + C(p4) + 2*S(p2+p3+p4) + 2*S(p3+p4) + 2*S(p4)
# eg. C(4+1) = C(4) + C(1) + 2*S(1)
#            =  10  +  1   + 2*0 = 11
# eg. C(4+1) = C(4) + C(2) + 2*S(2)
#            =  10  +   4  + 2*1  = 18
# eg. C(8+1) = C(8) + C(1) + 2*S(1)
#            =  32  +   1  + 2*0 = 35
# eg. C(8+2) = C(8) + C(2) + 2*S(2)
#            =  32  +   4  + 2*1 = 38
# eg. C(8+4) = C(8) + C(4) + 2*S(4)
#            =  32  +  10  + 2*6 = 54
# eg. C(8+4+1) = C(8) + C(4) + C(1) + 2*S(4+1) + 2*S(1)
#              =  32  +  10  +   1  + 2*9 + 2*0 = 61
# eg. C(8+4+2) = C(8) + C(4) + C(2) + 2*S(4+2) + 2*S(2)
#              =  32  +  10  +   4  + 2*14     + 2*1 = 76
#
# A151735
# before     1 2 3   4  5  6  7   8  9 10 11 12 13  14  15   16
# centre = 0,1,4,5, 10,11,16,21, 32,33,38,43,54,61  76  95  118
#
# before 1  2 3  4 5  6  7  8  9 10 11 12 13  14  15   16
# side = 0, 1,3, 6,9,14,21, 27,30,35,43,52,63,80,100, 112
#
# A151725 total cells 0,1,9,13, 33,37,57,77, 121,125,145,165,209,237,297,373,
#
#
# 15 |    15 15 15    15 15 15    15 15 15    15 15 15
# 14 |       14          14          14          14 15
# 13 |       14 13 13 13 14          14 13 13 13    15
# 12 |       14    12                      12 13
# 11 |             12 11 11 11    11 11 11    13    15
# 10 |       14    12    10          10 11    14 14 15
#  9 |       14 13 13    10  9  9  9    11          15
#  8 |                          8  9
#  7 |     7  7  7     7  7  7     9    11          15
#  6 |        6           6  7    10 10 11    14 14 15           19          18
#  5 |        6  5  5  5     7          11    13    15           20 15 14 13
#  4 |              4  5          13 12 12 12 13                       10 12
#  3 |     3  3  3     5     7    13          13    15         9  8  7    11
#  2 |        2  3     6  6  7    14 14    14 14 14 15            4  6    16 17
#  1 |  1  1     3           7                      15      3  2     5
#  0 |  0  1                                                0  1
#    +----------------------------------------------
#       0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
#
#       same mirror 1->9          same 1->9
#                                extra log(d) in Y=8 row
#
# 16 |                                                 16
# 15 |    15 15 15    15 15 15    15 15 15    15 15 15 16     k=4 depth=16
# 14 |       14          14          14          14    16
# 13 |       14 13 13 13 14          14 13 13 13 14
# 12 |       14    12                      12    14
# 11 |             12 11 11 11    11 11 11 12
# 10 |       14    12    10          10    12    14
#  9 |       14 13 13    10  9  9e 9d10    13 13 14
#  8 |                          8c   10          14
#  7 |     7  7  7     7  7  7  8b
#  6 |        6           6     8a   10          14      rotate -90  1->8
#  5 |        6  5  5  5  6     9  9 10    13 13 14      miss one in corner
#  4 |              4     6          10    12    14
#  3 |     3  3  3  4          12 11 11 11 12
#  2 |        2     4     6    12          12    14
#  1 |  1  1  2     5  5  6    13 13    13 13 13 14
#  0 |  0  .            ****                    ****
#    +---------------------------------------------------
#       0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
#
# Octant
#
# 16 |
# 15 |                                              15
# 14 |                                           14 15
# 13 |                                        13    15
# 12 |                                     12 13
# 11 |                                  11    13    15
# 10 |                               10 11    14 14 15
#  9 |                             9    11          15
#  8 |                          8  9
#  7 |                       7     9    11          15
#  6 |                    6  7    10 10 11    14 14 15
#  5 |                 5     7          11    13    15
#  4 |              4  5          13 12 12 12 13
#  3 |           3     5     7    13          13    15
#  2 |        2  3     6  6  7    14 14    14 14 14 15
#  1 |     1     3           7                      15
#  0 |  0  1
#    +---------------------------------------------------
#       0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
#
# oct(pow+rem) = oct(pow)
#                + oct(rem)    # extend
#                + oct(rem)    # upper
#                + oct(rem+1)  # lower
#                - rem         # undouble spine
#                + 2*floor(log2(rem+1))    # upper+extend log2_extras
#
# side(rem) = oct(rem) + oct(rem+1)
#             - rem                   # no double spine
#             + floor(log2(rem+1))    # upper log2_extras
#
# pow=2^k
# oct(2*pow) = 4*oct(pow) + 2*(k-2) - (pow-2)
# oct(2^0=1) = 0
# oct(2^1=2) = 1
# oct(2^2=4) = 4  = 4*1 - 0
# oct(2^3=8) = 16 = 4*4 - 0
# oct(2^4=16) = 16+7+4+7+3+4+5+4+3+3+3+2+1 = 62 = 4*16 - 2

# 3side
#
#  **** *** *** *** *** *** *** ***
#  * *   *   *   *   *   *   *   *
# ** *****   *****   *****   *****
#    * *       * *   * *       * *
# **   **** ****       **** ****
#  * * * *   * * *   * * *   * * *
# ** *** ***** ***   *** ***** ***
#    *   * *               * *   *      side                        side
# **       *888 888 888 888*                                        depth+1
#  * *   * * 7   7   7   7 * *   *            upper    |  upper
#    *** *** 76667   76667 *** ***            depth-1  |  depth-1
#  * * * *   7 5       5 7   * * *              \      |
# **   *****   5444 4445   *****                 \     |       /
#  * * *   * 7 5 3   3 5 7 *   * *         lower  \    |      /   lower
# ** **** ** 766 32223 667 ** ****         depth   \   |     /    depth
#                  1 3   7       *        ---------------------------
#                 01                                   |     \    upper
#                  1 3   7       *                     |      \   depth
#                  223 667 ** ****                     |       \
#                    3 5 7 *   * *                     | lower  \
#                  54445   *****                       | depth+1     side
#                  5   5 7   * * *
#                  66 6667 *** ***
#                        7 * *   *
#                  dcc 9888*
#                  d b 9   * *   *
#                    baaa **** ***
#                  e b       * * *
#                  dcccd   *****
#                  d   d   *   * *
#                  ee eee *** ****
#                                *

my @oct_to_n = (0, 1);

my %tree_depth_to_n = (4       => [ 0, 1 ],
                       1       => [ 0, 1 ],
                       octant  => [ 0, 1 ],
                       '3mid'  => [ 0, 1 ],
                       '3side' => [ 0, 1, 4 ],
                       side    => [ 0, 1 ]);
my %tree_depth_to_n_extra_depth_pow = (4       => 0,
                                       1       => 0,
                                       octant  => 0,
                                       '3mid'  => 1,
                                       '3side' => 1,
                                       side    => 1);

# use Smart::Comments;

sub tree_depth_to_n {
  my ($self, $depth) = @_;
  ### tree_depth_to_n(): "$depth  parts=$self->{'parts'}"

  $depth = int($depth);
  my $parts = $self->{'parts'};
  {
    my $initial = $tree_depth_to_n{$parts};
    if ($depth <= $#$initial) {
      ### table %tree_depth_to_n{}: $initial->[$depth]
      return $initial->[$depth];
    }
  }
  # if ($depth <= 1) {
  #   return $depth;
  # }

  my ($pow,$exp) = round_down_pow
    ($depth + $tree_depth_to_n_extra_depth_pow{$parts},
     2);
  if (is_infinite($exp)) {
    return $exp;
  }
  ### $pow
  ### $exp

  my $zero = $depth * 0;  # inherit bignum
  my $n = $zero;

  # @side is a list of depth values.
  # @mult is the multiple of T[depth] desired for that @side entry.
  #
  # @side is mostly high to low and growing by one more value at each
  # $exp level, but sometimes it's a bit more and some values not high to
  # low and possibly duplicated.
  #
  my @pending;
  my @mult;

  if ($parts eq '4') {
    push @pending, $depth;
    push @mult, 8;
    $n -= 4*$depth + 7;

  } elsif ($parts eq '1') {
    push @pending, $depth;
    push @mult, 2;
    $n -= $depth;

  } elsif ($parts eq 'octant') {
    push @pending, $depth;
    push @mult, 1;

  } elsif ($parts eq '3mid') {
    push @pending, $depth+1, $depth;
    push @mult, 2, 4;
    # Duplicated diagonals, and no log2_extras on two outermost octants.
    # Each log2 at depth=2^k-2, so another log2 decrease when depth=2^k-1.
    # $exp == _log2_floor($depth+1) so at $depth==2*$pow-1 one less.
    $n -= 3*$depth + 2*$exp + 6;

  } elsif ($parts eq '3side') {
    push @pending, $depth+1, $depth, $depth-1;
    push @mult, 1, 3, 2;
    # Duplicated diagonals, and no log2_extras on two outermost octants.
    # For plain depth each log2 at depth=2^k-2, so another log2 decrease
    # when depth=2^k-1.
    # For depth+1 block each log2 at depth=2^k-2, so another log2 decrease
    # when depth=2^k-2.
    # $exp == _log2_floor($depth+1) so at $depth==2*$pow-1 one less.
    $n -= 3*$depth + 2*$exp + ($depth == $pow-1 ? 3 : 4);

  } elsif ($parts eq 'side') {
    push @pending, $depth+1, $depth;
    push @mult, 1, 1;
    # $exp == _log2_floor($depth+1)
    $n -= $depth - $exp;
  }

  while ($exp >= 0 && @pending) {
    ### at: "pow=$pow exp=$exp  n=$n"
    ### assert: $pow == 2 ** $exp
    ### pending: join(',',@pending)
    ### mult: join(',',@mult)

    my @new_pending;
    my @new_mult;
    my $oct_pow;
    foreach my $depth (@pending) {
      my $mult = shift @mult;
      ### assert: $depth >= 0

      if ($depth <= 1) {
        ### small depth: "depth=$depth mult=$mult * $oct_to_n[$depth]"
        $n += $mult * $depth;  # oct=0 at depth=0, oct=1 at depth=1
        next;
      }
      my $rem = $depth - $pow;
      if ($rem < 0) {
        push @new_pending, $depth;
        push @new_mult, $mult;
        next;
      }

      ### $depth
      ### $mult
      ### $rem
      ### assert: $rem >= 0 && $rem < $pow

      my $powmult = $mult;
      if ($rem <= 1) {
        if ($rem == 0) {
          ### rem=0, oct(pow) only ...
        } else { # $rem == 1
          ### rem=1, oct(pow)+1 ...
          $n += $mult;
        }
      } else {
        ### formula ...
        # oct(pow+rem) = oct(pow)
        #                + oct(rem+1)
        #                + 2*oct(rem)
        #                - floor(log2(rem+1))
        #                - rem - 3

        my $rem1 = $rem + 1;
        {
          my ($lpow,$lexp) = round_down_pow ($rem1, 2);
          $n -= ($lexp + $rem + 3)*$mult;
          ### sub also: ($lexp + $rem + 3). " *mult=$mult"
        }
        if ($rem1 == $pow) {
          ### rem+1 == pow, increase powmult ...
          $powmult *= 2;    # oct(pow)+oct(rem+1) is 2*oct(pow)
        } elsif (@new_pending && $new_pending[-1] == $rem1) {
          ### merge into previously pushed new_pending ...
          # print "rem+1=$rem1 ",join(',',@new_pending),"\n";
          $new_mult[-1] += $mult;
        } else {
          ### push: "depth=$rem1 mult=$mult"
          push @new_pending, $rem1;
          push @new_mult, $mult;
        }

        ### push: "depth=$rem mult=".2*$mult
        push @new_pending, $rem;
        push @new_mult, 2*$mult;
      }

      # oct(pow) = (2*pow*pow + 3*exp + 7)/9 + pow/2
      #
      $oct_pow ||= ((4*$pow+9)*$pow + 6*$exp + 14)/18;
      $n += $oct_pow * $powmult;
      ### oct(pow): "pow=$pow is $oct_pow * powmult=$powmult"
    }
    @pending = @new_pending;
    @mult = @new_mult;

    $exp--;
    $pow /= 2;
  }

  ### return: $n
  return $n;
}

#use Smart::Comments;

# _depth_to_octant_added() returns the number of cells added at a given
# $depth level in parts=octant.
# Ie. tree_depth_to_n(depth+1) - tree_depth_to_n(depth).
#
# @$depth_aref is a list of depth values.
# @$mult_aref is the multiple of oct(depth) desired for each @depth_aref.
#
# On input @$depth_aref must have $depth_aref->[0] as the highest depth.
#
# Within the code the depth list is mostly high to low and grown by one
# extra depth value at each $exp level.  But sometimes it grows a bit more
# than that and sometimes the values are not high to low, and sometimes
# there's duplication.
#
my @_depth_to_octant_added = (1, 2, 1);  # depth=0to2 small values

sub _depth_to_octant_added {
  my ($depth_aref, $mult_aref, $zero) = @_;
  ### _depth_to_octant_added(): join(',',@$depth_aref)
  ### mult_aref: join(',',@$mult_aref)
  ### assert: scalar(@$depth_aref) == scalar(@$mult_aref)

  # $depth_aref->[0] must be the biggest depth, to make the $pow finding easy
  ### assert: scalar(@$depth_aref) >= 1
  ### assert: max(@$depth_aref) == $depth_aref->[0]

  my ($pow,$exp) = round_down_pow ($depth_aref->[0], 2);
  if (is_infinite($exp)) {
    return $exp;
  }
  ### $pow
  ### $exp

  my $added = $zero;

  # running $pow down to 2 (inclusive)
  while ($exp >= 0 && @$depth_aref) {
    ### at: "pow=$pow exp=$exp"
    ### assert: $pow == 2 ** $exp

    ### depth: join(',',@$depth_aref)
    ### mult: join(',',@$mult_aref)
    my @new_depth;
    my @new_mult;
    foreach my $depth (@$depth_aref) {
      my $mult = shift @$mult_aref;
      ### assert: $depth >= 0

      if ($depth <= $#_depth_to_octant_added) {
        ### small depth: "depth=$depth mult=$mult * $_depth_to_octant_added[$depth]"
        $added += $mult * $_depth_to_octant_added[$depth];
        next;
      }
      if ($depth < $pow) {
        push @new_depth, $depth;
        push @new_mult, $mult;
        next;
      }

      my $rem = $depth - $pow;

      ### $depth
      ### $mult
      ### $rem
      ### assert: $rem >= 0 && $rem < $pow

      if ($rem <= 1) {
        if ($rem == 0) {
          ### rem=0, grow 1 ...
          $added += $mult;
        } else {
          ### rem=1, grow 3 ...
          $added += 3 * $mult;
        }
      } else {
        my $rem1 = $rem + 1;
        if ($rem1 == $pow) {
          ### rem+1=pow, no lower part, 3/2 of pow ...
          $added += ($pow/2) * (3*$mult);
        } else {
          ### formula ...
          # oadd(pow+rem) = oadd(rem+1) + 2*oadd(rem)
          #                 + (is_pow2($rem+2) ? -2 : -1)

          # upper/lower diagonal overlap, and no log2_extras in lower
          $added -= (_is_pow2($rem+2) ? 2*$mult : $mult);

          if (@new_depth && $new_depth[-1] == $rem1) {
            ### merge into previously pushed new_depth ...
            # print "rem=$rem ",join(',',@new_depth),"\n";
            $new_mult[-1] += $mult;
          } else {
            ### push: "rem+1  depth=$rem1 mult=$mult"
            push @new_depth, $rem1;
            push @new_mult, $mult;
          }

          ### push: "rem    depth=$rem mult=".2*$mult
          push @new_depth, $rem;
          push @new_mult, 2*$mult;
        }
      }
    }
    $depth_aref = \@new_depth;
    $mult_aref = \@new_mult;

    $exp--;
    $pow /= 2;
  }

  ### return: $added
  return $added;
}

no Smart::Comments;

sub _is_pow2 {
  my ($n) = @_;
  my ($pow,$exp) = round_down_pow ($n, 2);
  return ($n == $pow);
}
sub _log2_floor {
  my ($n) = @_;
  if ($n < 2) { return 0; }
  my ($pow,$exp) = round_down_pow ($n, 2);
  return $exp;
}

# sub _is_pow2_lt {
#   my ($n, $limit) = @_;
#   my ($pow,$exp) = round_down_pow ($n, 2);
#   return ($n == $pow && $exp < $limit);
# }

1;
__END__

=for stopwords eg Ryde Math-PlanePath-Toothpick Nstart Nend

=head1 NAME

Math::PlanePath::SurroundOneEight -- automaton growing to cells with one of eight neighbours

=head1 SYNOPSIS

 use Math::PlanePath::SurroundOneEight;
 my $path = Math::PlanePath::SurroundOneEight->new;
 my ($x, $y) = $path->n_to_xy (123);

=head1 DESCRIPTION

I<In progress ...>

X<Applegate, David>X<Pol, Omar E.>X<Sloane, Neil>This a cellular automaton
growing into cells which have just 1 of 8 neighbours already "on", as per

=over

David Applegate, Omar E. Pol, N.J.A. Sloane, "The Toothpick Sequence and
Other Sequences from Cellular Automata", Congressus Numerantium, volume 206
(2010), pages 157-191, part 14 "Square Grid with Eight Neighbours".

http://www.research.att.com/~njas/doc/tooth.pdf

=back

Points are numbered by growth levels and anti-clockwise on each cell within
the level.

=cut

# math-image --path=SurroundOneEight --output=numbers --all --size=75x16

=pod

                                                                121    8
     93  92  91      90  89  88      87  86  85      84  83  82        7
     94  64              63              60              59  81        6
     95      44  43  42  62              61  41  40  39      80        5
             45  34                              33  38                4
     96      46      20  19  18      17  16  15      37      79        3
     97  65  66      21  10               9  14      57  58  78        2
     98              22       4   3   2      13              77        1
                              5   0   1                           <- Y=0
     99              23       6   7   8      32             120       -1
    100  68  67      24  11              12  31      76  75 119       -2
    101      47      25  26  27      28  29  30      56     118       -3
             48  35                              36  55               -4
    102      49  50  51  71              72  52  53  54     117       -5
    103  69              70              73              74 116       -6
    104 105 106     107 108 109     110 111 112     113 114 115       -7

                                  ^
     -7  -6  -5  -4  -3  -2  -1  X=0  1   2   3   4   5   6   7


The start is N=0 at the origin X=0,Y=0.  Then each cell around it has just
one neighbour (that first N=0 cell) and so all are turned on.  The rule is
applied in a single atomic step, so the fact the new cells are adjacent
doesn't matter.  When counting neighbours any other prospective new cells at
a growth level don't count.

At the next level only the diagonal cells X=+/-2,Y=+/-2 have a single
neighbour, and so on.

                                     10           9
                                       \         /
                     4  3  2             4  3  2
                      \ | /               \ | /
         0           5--0--1             5--0--1
                      / | \               / | \
                     6  7  8             6  7  8
                                       /         \
                                     11           12

=head2 Level Ranges

The pattern always extends along the X=+/-Y diagonals, and extends to the
sides in power-of-2 blocks.  So for example in the part shown above N=33 at
X=4,Y=4 is the only cell growing out of the 4x4 block X=0to3,Y=0to3 at the
origin, and likewise N=34,35,36 in the other quadrants.  Then N=121 at
X=8,Y=8 is the only growth out of the 8x8 block, etc.

In general the first N at depth=2^k for kE<gt>=0 is

    Ndepth(2^k) = (16*4^k + 24*k - 7) / 9
                = (16*depth*depth + 24*k - 7) / 9

    eg. k=3 Ndepth=121

Because points are numbered from N=0 this is how many cells are "on" in the
pattern before this depth level.  The cells are within -2^k E<lt> X,Y E<lt>
2^k and so the fraction of the plane covered is

    density = Ndepth(2^k) / (2*2^k - 1)^2
            = (16*4^k + 24*k - 7) / 9 / (2*2^k-1)^2
            -> 4/9 = 0.444...    as k -> infinity

This density is approached from above, ie. density(k) decreases towards 4/9
as k increases.  The first k=0 is the single origin point which is 1/1, and
k=2 is 9/9 of the 3x3 at the origin.  Then for example k=2 7x7 square has
33/49=0.673, then k=3 121/225=0.5377, etc.

=head2 One Quadrant

Option C<parts =E<gt> 1> confines the pattern to the first quadrant.  This
is a single copy of the repeating part in each of the four quadrants of the
full pattern.

=cut

# math-image --path=SurroundOneEight,parts=1 --all --output=numbers --size=75x16

=pod

    parts => 1

     15 |    117 116 115     114 113 112     111 110 109     108 107 106
     14 |         90              89              86              85 105
     13 |         91  73  72  71  88              87  70  69  68     104
     12 |         92      58                              57  67
     11 |                 59  53  52  51      50  49  48      66     103
     10 |         93      60      41              40  47      83  84 102
      9 |         94  74  75      42  37  36  35      46             101
      8 |                                 32  34
      7 |     31  30  29      28  27  26      33      45             100
      6 |         19              18  25      38  39  44      82  81  99
      5 |         20  15  14  13      24              43      65      98
      4 |                 10  12              61  54  55  56  64
      3 |      9   8   7      11      23      62              63      97
      2 |          4   6      16  17  22      76  77      78  79  80  96
      1 |  3   2       5              21                              95
    Y=0 |  0   1
        +----------------------------------------------------------------
         X=0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15

=head2 One Octant

Option C<parts =E<gt> 'octant'> confines the pattern to the first octant
0E<lt>=YE<lt>=X.

=cut

# math-image --path=SurroundOneEight,parts=octant --all --output=numbers --size=75x16

=pod

    parts => "octant"

     15 |                                              66
     14 |                                           54 65
     13 |                                        44    64
     12 |                                     36 43
     11 |                                  32    42    63
     10 |                               26 31    52 53 62
      9 |                            23    30          61
      8 |                         20 22
      7 |                      19    21    29          60
      6 |                   13 18    24 25 28    51 50 59
      5 |                10    17          27    41    58
      4 |              7  9          37 33 34 35 40
      3 |           6     8    16    38          39    57
      2 |        3  5    11 12 15    45 46    47 48 49 56
      1 |     2     4          14                      55
    Y=0 |  0  1
        +-------------------------------------------------
         X=0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15

The full pattern is symmetric on each size of the four diagonals.  This
octant is one of those eight symmetric parts (inclusive of the diagonal,
which would be shared if two octants are combined to make a quadrant).

The octant is self similar in blocks

                              --|
                            --  |
                          --    |
                        --      |
                      -- extend |
                    --          |
                  --------------|
                --| --   upper  |
              --  |   --  flip  |
            --    |     --      |
          --      | lower --    |
        --  base  | depth+1 --  |
      --          | no pow2s  --|
    ------------------------------

The "extend" is a direct copy of the "base" block.  The "upper" is the same
as the "extend" but flipped vertically (and so numbered the opposite way
too).

The "lower" block is the base pattern rotated by +90 degrees, and without
the pow2 cells at X=3,7,15,etc,Y=1.  These absent cells are easier to see in
a bigger picture of the pattern.

The lower block is also one depth level ahead, so its N=45,46,47,48 is at
depth=14 whereas the corresponding N=62,63,64,65 in the "extend" is at
depth=15.  Ie. the lower block ends one depth level sooner than the upper
and extend.

The diagonal between the lower and upper looks as if it's a stair-step, but
that's not so.  It's the same sort of diagonal as the X=Y leading diagonal
of the whole octant.  Because the lower block is one depth level ahead of
the upper the two have branches off the diagonal offset by 1 position.  For
example the N=34,33,37 branch in the lower above corresponds to N=40,41,51
in the upper.

This offset on the upper/lower diagonal becomes easier to see in a bigger
picture, and by chopping off one level of leaf nodes.  It might look at
first as if the square side part comprising upper and lower is entirely
different from the central symmetric square (per L</One Quadrant> above),
but that's not so, it's just the branching off the diagonal offset by 1 in
the lower part.

=head2 Three Mid

Option C<parts =E<gt> "3mid"> is the "second corner sequence" of the
toothpick paper above.  This is the part of the full pattern starting at a
point X=2^k,Y=2^k in that pattern, but the three parts there extended
indefinitely.

=cut

# math-image --path=SurroundOneEight,parts=3mid --all --output=numbers --size=75x15

=pod

    parts => "3mid"

    85 84 83    82 81 80    79 78 77    76 75 74         7
       58          57          54          53 73         6
       59 41 40 39 56          55 38 37 36    72         5
       60    26                      25 35               4
             27 21 20 19    18 17 16    34    71         3
       61    28     9           8 15    51 52 70         2
       62 42 43    10  5  4  3    14          69         1
                          0  2                      <- Y=0
                             1    13          68        -1
                             6  7 12    50 49 67        -2
                                  11    33    66        -3
                            29 22 23 24 32              -4
                            30          31    65        -5
                            44 45    46 47 48 64        -6
                                              63        -7

                          ^
    -7 -5 -6 -4 -3 -2 -1 X=0 1  2  3  4  5  6  7

The first quadrant XE<gt>=0,YE<gt>=0 is the same as in the full pattern, but
the other quadrants are a "side" portion of the pattern.

This pattern can be generated from the automaton rule by starting from N=0
at X=0,Y=0 and then treating all points with XE<lt>0,YE<lt>0 as occupied.

                       ..       .. .. ..
                        9           8 ..
                       10  5  4  3    ..
                              0  2
              -------------+     1
    X<0,Y<0       *  *  *  |     6  7 ..
    considered    *  *  *  |
    all "on"      *  *  *  |

=head2 Three Side

Option C<parts =E<gt> "3side"> is the "first corner sequence" of the
toothpick paper above.  This is the part of the full pattern starting at a
point X=2^k+1,Y=3*2^k-1, mirrored horizontally, and the three parts there
extended indefinitely.

=cut

# math-image --path=SurroundOneEight,parts=3side --expression='i<=99?i:0' --output=numbers --size=120x25

=pod

    parts => "3side"

    .. 89 88 87    86 85 84    83 82 81    80 79 78 ..           8
          70          69          66          65                 7
          71 51 50 49 68          67 48 47 46 64                 6
          72    34                      33    63                 5
                35 25 24 23    22 21 20 32                       4
          73    36    15          14    31    62                 3
          74 52 53    16  8  7  6 13    44 45 61                 2
                             3    12          60                 1
                          0  2                              <- Y=0
                             1    11          59                -1
                             4  5 10    43 42 58                -2
                                   9    30    57                -3
                            26 17 18 19 29                      -4
                            27          28    56                -5
                            37 38    39 40 41 55                -6
                                              54                -7
                                        .. 75 76 77 ..          -8
                          ^
       -7 -6 -4 -3 -2 -1 X=0 1  2  3  4  5  6  7  8

The Y negative quadrant is rotated +90 degrees and is one depth level ahead
of the other two, so its run 54,55,56 corresponds to 78,79,80 in the first
quadrant.

This pattern can be generated from the automaton rule by starting from N=0
at X=0,Y=0 and then treating all points with XE<lt>0,YE<lt>=0 as occupied.
(Notice 3mid above is YE<lt>0 whereas here YE<lt>=0.)

                           .. 8  7  6 ..
                                 3
              -------------+  0  2
    X<0,Y<=0      *  *  *  |     1    11
    considered    *  *  *  |     4  5 10
    all "on"      *  *  *  |           9
                  *  *  *  |          ..

=head2 Three Growth

The interest in the two "corner" sequences 3mid and 3side is that a base
3mid can be doubled in size by adding one "3mid" and two "3side"s.

    +-------------+-------------+
    |             |             |       3mid doubled in size
    | new 3side   |  new 3mid   |       by adding two new 3sides
    |             |             |       and one new 3mid.
    |      +-------------+      |
    |      |             |      |
    |      |  start 3mid |      |
    |      |             |      |
    +------+------+      |------+
                  |      |      |
                  |      |      |
                  |      |      |
                  +------+      |
                  |             |
                  |   new 3side |
                  |             |
                  +-------------+

=head1 FUNCTIONS

See L<Math::PlanePath/FUNCTIONS> for behaviour common to all path classes.

=over 4

=item C<$path = Math::PlanePath::SurroundOneEight-E<gt>new ()>

=item C<$path = Math::PlanePath::SurroundOneEight-E<gt>new (parts =E<gt> $str)>

Create and return a new path object.  The C<parts> option can be

    "4"           full pattern
    "1"           single quadrant
    "octant"      single octant wedge
    "3mid"        three quadrants, middle symmetric style
    "3side"       three quadrants, side style

=back

=head2 Tree Methods

=over

=item C<@n_children = $path-E<gt>tree_n_children($n)>

Return the children of C<$n>, or an empty list if C<$n> has no children
(including when C<$n E<lt> 1>, ie. before the start of the path).  The way
points are numbered means the children are always consecutive N values.

The children are the new cells turned "on" adjacent to C<$n> in the next
depth level.  The possible number of children varies with the parts,

    parts        possible num children
    -----        ---------------------
      4          0, 1, 2, 3, 5, 8
      1          0, 1, 2, 3, 5
    octant       0, 1, 2, 3
    3mid         0, 1, 2, 3, 5
    3side        0,    2, 3

For parts=4 there's 8 children at the initial N=0 and after that at most 5.

1 child only occurs at the X=2^k,Y=2^k central diagonal of the full pattern
and similar parts 1, octant and 3mid.  But in 3side there's no such corner
and never a single child, only 2 or 3 children.

5 children occurs around the 1-child at X=2^k,Y=2^k of parts 4, 1, 3mid.  In
an octant there's only 3 children around that point, being confined to on
and below the X=Y diagonal.

=back

=head1 OEIS

This cellular automaton is in Sloane's Online Encyclopedia of Integer
Sequences as

    http://oeis.org/A151725    (etc)

    parts=4 (the default)
      A151725   total cells "V" (tree_depth_to_n())
      A151726   added cells "v"

    parts=1
      A151735   total cells (tree_depth_to_n())
      A151737   added cells

    parts=3mid
      A170880   total cells "V2" (tree_depth_to_n())
      A151728   added cells "v2"

    parts=3side
      A170879   total cells "V1" (tree_depth_to_n())
      A151747   added cells "v1"

=head1 SEE ALSO

L<Math::PlanePath>,
L<Math::PlanePath::ToothpickTree>,
L<Math::PlanePath::UlamWarburton>

=head1 HOME PAGE

http://user42.tuxfamily.org/math-planepath/index.html

=head1 LICENSE

Copyright 2012, 2013 Kevin Ryde

This file is part of Math-PlanePath-Toothpick.

Math-PlanePath-Toothpick 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.

Math-PlanePath-Toothpick 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
Math-PlanePath-Toothpick.  If not, see <http://www.gnu.org/licenses/>.

=cut
