# Copyright 2011, 2012 Kevin Ryde

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



# math-image --path=CellularRule57 --all
#
# math-image --path=CellularRule57 --all --output=numbers --size=132x50
# math-image --path=CellularRule57,mirror=1 --all --output=numbers --size=132x50
#


package Math::PlanePath::CellularRule57;
use 5.004;
use strict;

use Math::PlanePath;
*_round_nearest = \&Math::PlanePath::_round_nearest;

use Math::PlanePath::CellularRule54 54; # v.54 for _rect_for_V()
*_rect_for_V = \&Math::PlanePath::CellularRule54::_rect_for_V;

use vars '$VERSION', '@ISA';
$VERSION = 77;
@ISA = ('Math::PlanePath');


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


use constant class_y_negative => 0;
use constant n_frac_discontinuity => .5;

use constant parameter_info_array =>
  [ { name        => 'mirror',
      type        => 'boolean',
      display     => 'Mirror',
      default     => 0,
      description => 'Mirror to "rule 99" instead.',
    },
  ];


#            left
# even  y=3     5
#         5    12
#         7    23
#         9    38
# [1,2,3,4], [5,12,23,38]
#
# N = (2 d^2 + d + 2)
#   = (2*$d**2 + $d + 2)
#   = ((2*$d + 1)*$d + 2)
# d = -1/4 + sqrt(1/2 * $n + -15/16)
#   = (-1 + 4*sqrt(1/2 * $n + -15/16)) / 4
#   = (sqrt(8*$n-15)-1)/4
# with Y=2*d+1

# row 19, d=9
# N=173 to N=181 is 9 cells rem=0..8  is d-1
# 1/3 section 3 cells rem=0,1,2  floor((d-1)/3)
# 2/3 section 6 cells
# right solid N=191 to N=200 is 10 of is rem<d
#
# row 21, d=10
# 1/3 section 4 cells rem=0,1,2,3  floor((d-1)/3)
# 2/3 section 6 cells
#
# row 23, d=11
# 1/3 section 4 cells rem=0,1,2,3  floor((d-1)/3)
# 2/3 section 7 cells
#
# row 25, d=12
# 2/3 section 8 cells
#
# row 27, d=13
# 2/3 section 8 cells
#
# row 29, d=14
# 2/3 section 9 cells    floor(2d/3)
#
# row 31, d=15
# 2/3 section 10 cells   floor(2d/3)
#
#
# row 18 d=8
# odd 1/3 section   4 cells  (d+4)/3
#
# row 20 d=9
# odd 1/3 section   4 cells
#
# row 22 d=10
# odd 1/3 section   4 cells
#
# row 23 d=11
# odd 1/3 section   5 cells


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

  my $frac;
  {
    my $int = int($n);
    $frac = $n - $int;
    $n = $int;       # BigFloat int() gives BigInt, use that
    if (2*$frac >= 1) {
      $frac -= 1;
      $n += 1;
    }
    # -0.5 <= $frac < 0.5
    ### assert: 2*$frac >= -1
    ### assert: 2*$frac < 1
  }

  if ($n <= 1) {
    if ($n == 1) {
      return (0,0);
    } else {
      return;
    }
  }

  # d is the two-row group number, y=2*d+1, where n belongs
  #
  my $d = int ((sqrt(8*$n-15)-1)/4);
  $n -= ((2*$d + 1)*$d + 2);   # remainder
  ### $d
  ### remainder: $n

  if ($self->{'mirror'}) {
    if ($n <= $d) {
      ### right solid: $n
      return ($frac + $n - 2*$d - 1,
              2*$d+1);
    }
    $n -= $d+1;

    if ($n < int(2*$d/3)) {
      ### right 2/3: $n
      return ($frac + int(3*$n/2) - $d + 1,
              2*$d+1);
    }
    $n -= int(2*$d/3);

    if ($n < int(($d+2)/3)) {
      ### left 1/3: $n
      return ($frac + 3*$n + ((2+$d)%3),
              2*$d+1);
    }
    $n -= int(($d+2)/3);

    if ($n < $d) {
      ### left solid: $n
      return ($frac + $n + $d+2,
              2*$d+1);
    }
    $n -= $d;

    if ($n < int((2*$d+5)/3)) {
      ### odd 2/3: $n
      return ($frac + int((3*$n)/2) - $d +  - 1,
              2*$d+2);
    }
    $n -= int((2*$d+5)/3);

    ### odd 1/3: $n
    return ($frac + 3*$n + ($d%3) + 1,
            2*$d+2);

  } else {
    if ($n < $d) {
      ### left solid: $n
      return ($frac + $n - 2*$d - 1,
              2*$d+1);
    }
    $n -= $d;

    if ($n < int(($d+2)/3)) {
      ### left 1/3: $n
      return ($frac + 3*$n - $d + 1,
              2*$d+1);
    }
    $n -= int(($d+2)/3);

    if ($n < int(2*$d/3)) {
      ### right 2/3: $n
      return ($frac + $n + int(($n+(-$d%3))/2) + 1,
              2*$d+1);
    }
    $n -= int(2*$d/3);

    if ($n <= $d) {
      ### right solid: $n
      return ($frac + $d + $n + 1,
              2*$d+1);
    }
    $n -= $d+1;

    if ($n < int(($d+4)/3)) {
      ### odd 1/3: $n
      return ($frac + 3*$n - $d - 1,
              2*$d+2);
    }
    $n -= int(($d+4)/3);

    ### odd 2/3: $n
    return ($frac + $n + int(($n+((1-$d)%3))/2) + 1,
            2*$d+2);
  }
}

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

  if ($y < 0
      || $x < -$y
      || $x > $y) {
    ### outside pyramid region ...
    return undef;
  }

  if ($self->{'mirror'}) {
    # mirrored, rule 99

    if ($y % 2) {
      my $d = ($y+1)/2;
      ### odd row, solids, d: $d

      if ($x < -$d) {
        return ($y+1)*$y/2 + $x + 2;
      }
      if ($x < 0) {
        ### mirror left 2 of 3 ...
        if (($x += $d+2) % 3) {
          return ($y+1)*$y/2 + $x-int($x/3) - $d;
        }
      } elsif ($x > $d) {
        return ($y+1)*$y/2 + $x - $d + 1;
      } else {
        ### mirror right 1 of 3 ...
        $x += 2-$d;
        unless ($x % 3) {
          return ($y+1)*$y/2 + $x/3 + 1;
        }
      }

    } else {
      ### even row, sparse ...
      my $d = $y/2;
      if ($x >= 0) {
        ### mirror sparse right 1 of 3 ...
        if ($x <= $d  # only to half way
            && (($x -= $d) % 3) == 0) {
          return ($y+1)*$y/2 + $x/3 + 1;
        }
      } else { # $x < 0
        ### mirror sparse left 2 of 3 ...
        if ($x >= -$d  # only to half way
            && (($x += $d+1) % 3)) {
          return ($y+1)*$y/2 + $x-int($x/3) - $d;
        }
      }
    }
  } else {
    # unmirrored, rule 57

    if ($y % 2) {
      my $d = ($y+1)/2;
      ### odd row, solids, d: $d

      if ($x <= -$d) {
        ### solid left ...
        if ($x < -$d) {  # always skip the -$d cell
          return ($y+1)*$y/2 + $x + 2;
        }
      } elsif ($x <= 0) {
        ### 1 of 3 ...
        unless (($x += $d+1) % 3) {
          return ($y+1)*$y/2 + $x/3 - $d + 1;
        }
      } elsif ($x >= $d) {
        ### solid right ...
        return ($y+1)*$y/2 + $x - $d + 1;
      } else {
        ### 2 of 3 ...
        $x += 1-$d;
        if ($x % 3) {
          return ($y+1)*$y/2 + $x-int($x/3) + 1;
        }
      }

    } else {
      ### even row, sparse ...

      my $d = $y/2;
      if ($x > 0) {
        ### right 2 of 3 ...
        if ($x <= $d  # goes to half way only
            && (($x -= $d+1) % 3)) {
          return ($y+1)*$y/2 + $x-int($x/3) + 2;
        }
      } else { # $x <= 0
        ### left 1 of 3 ...
        if (($x += $d) >= 0   # goes to half way only
            && ! ($x % 3)) {
          return ($y+1)*$y/2 + $x/3 - $d + 1;
        }
      }
    }
  }
  return undef;
}

# left edge ((2*$d + 1)*$d + 2)
# where y=2*d+1
#       d=floor((y-1)/2)
# left N = (2*floor((y-1)/2) + 1)*floor((y-1)/2) + 2
#        = (yodd + 1)*yodd/2 + 2


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

  ($x1,$y1, $x2,$y2) = _rect_for_V ($x1,$y1, $x2,$y2)
    or return (1,0); # rect outside pyramid

  my $zero = ($x1 * 0 * $y1 * $x2 * $y2);  # inherit bignum

  $y1 -= ! ($y1 % 2);
  $y2 -= ! ($y2 % 2);
  return ($zero + ($y1 < 1
                   ? 1
                   : ($y1-1)*$y1/2 + 2),
          $zero + ($y2+2)*($y2+1)/2 + 1);
}

1;
__END__

=for stopwords straight-ish PyramidRows Ryde Math-PlanePath ie hexagonals 18-gonal Xmax-Xmin Nleft Nright OEIS

=head1 NAME

Math::PlanePath::CellularRule57 -- cellular automaton 57 and 99 points

=head1 SYNOPSIS

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

=head1 DESCRIPTION

X<Wolfram, Stephen>
This is the pattern of Stephen Wolfram's "rule 57" cellular automaton

    http://mathworld.wolfram.com/ElementaryCellularAutomaton.html

arranged as rows

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

    -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1  2  3  4  5  6  7  8  9

On rows with odd Y there's a solid block at either end then 1 of 3 cells to
the left and 2 of 3 to the right of the centre.  On even Y rows there's
similar 1 of 3 and 2 of 3 middle parts, but without the solid ends.  Those 1
of 3 and 2 of 3 are successively offset so as to make lines going up towards
the centre as can be seen in the following plot.

    ***********  *  *  *  * * ** ** ** ************
                *  *  *  *  ** ** ** **
      **********  *  *  *  * ** ** ** ***********
                 *  *  *  * * ** ** **
        *********  *  *  *  ** ** ** **********
                  *  *  *  * ** ** **
          ********  *  *  * * ** ** *********
                   *  *  *  ** ** **
            *******  *  *  * ** ** ********
                    *  *  * * ** **
              ******  *  *  ** ** *******
                     *  *  * ** **
                *****  *  * * ** ******
                      *  *  ** **
                  ****  *  * ** *****
                       *  * * **
                    ***  *  ** ****
                        *  * **
                      **  * * ***
                         *  **
                        *  * **
                          * *
                            *
                           *

=head2 Mirror

The C<mirror =E<gt> 1> option gives the mirror image pattern which is "rule
99".  The point numbering shifts but the total points on each row is the
same.

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

    -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1  2  3  4  5  6  7  8  9

=head1 FUNCTIONS

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

=over 4

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

Create and return a new path object.

=item C<($x,$y) = $path-E<gt>n_to_xy ($n)>

Return the X,Y coordinates of point number C<$n> on the path.

=item C<$n = $path-E<gt>xy_to_n ($x,$y)>

Return the point number for coordinates C<$x,$y>.  C<$x> and C<$y> are each
rounded to the nearest integer, which has the effect of treating each cell
as a square of side 1.  If C<$x,$y> is outside the pyramid or on a skipped
cell the return is C<undef>.

=back

=head1 SEE ALSO

L<Math::PlanePath>,
L<Math::PlanePath::CellularRule>,
L<Math::PlanePath::CellularRule54>,
L<Math::PlanePath::CellularRule190>,
L<Math::PlanePath::PyramidRows>

http://mathworld.wolfram.com/ElementaryCellularAutomaton.html

=head1 HOME PAGE

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

=head1 LICENSE

Copyright 2011, 2012 Kevin Ryde

This file is part of Math-PlanePath.

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

=cut
