File Coverage

blib/lib/Math/PlanePath/QuintetCurve.pm
Criterion Covered Total %
statement 104 126 82.5
branch 37 44 84.0
condition 11 12 91.6
subroutine 12 17 70.5
pod 6 6 100.0
total 170 205 82.9


line stmt bran cond sub pod time code
1             # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Kevin Ryde
2              
3             # This file is part of Math-PlanePath.
4             #
5             # Math-PlanePath is free software; you can redistribute it and/or modify
6             # it under the terms of the GNU General Public License as published by the
7             # Free Software Foundation; either version 3, or (at your option) any later
8             # version.
9             #
10             # Math-PlanePath is distributed in the hope that it will be useful, but
11             # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12             # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13             # for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Math-PlanePath. If not, see .
17              
18              
19             # Boundary of unit squares:
20             # 2*(4*3^n+1) cf A199108 = 4*3^n+1
21             #
22             # QuintetCurve unit squares boundary
23             # 12,28,76,220,652
24             # match 12,28,76,220,652
25             # [HALF]
26             # A079003 a(n) = 4*3^(n-2)+2
27              
28              
29              
30             package Math::PlanePath::QuintetCurve;
31 1     1   8915 use 5.004;
  1         11  
32 1     1   6 use strict;
  1         2  
  1         40  
33              
34 1     1   7 use vars '$VERSION', '@ISA';
  1         2  
  1         66  
35             $VERSION = 127;
36              
37             # inherit: new(), rect_to_n_range(), arms_count(), n_start(),
38             # parameter_info_array(), xy_is_visited()
39 1     1   476 use Math::PlanePath::QuintetCentres;
  1         4  
  1         36  
40 1     1   502 use Math::PlanePath::Base::NSEW;
  1         3  
  1         42  
41             @ISA = ('Math::PlanePath::Base::NSEW',
42             'Math::PlanePath::QuintetCentres');
43              
44 1     1   55 use Math::PlanePath;
  1         3  
  1         80  
45             *_divrem_mutate = \&Math::PlanePath::_divrem_mutate;
46              
47             use Math::PlanePath::Base::Generic
48 1         54 'is_infinite',
49 1     1   9 'round_nearest';
  1         2  
50             use Math::PlanePath::Base::Digits
51 1         1130 'digit_split_lowtohigh',
52 1     1   6 'round_up_pow';
  1         2  
53              
54             # uncomment this to run the ### lines
55             # use Smart::Comments;
56              
57              
58             {
59             my @x_negative_at_n = (undef, 513, 9, 2, 2);
60             sub x_negative_at_n {
61 0     0 1 0 my ($self) = @_;
62 0         0 return $x_negative_at_n[$self->{'arms'}];
63             }
64             }
65             {
66             my @y_negative_at_n = (undef, 2, 4, 6, 3);
67             sub y_negative_at_n {
68 0     0 1 0 my ($self) = @_;
69 0         0 return $y_negative_at_n[$self->{'arms'}];
70             }
71             }
72             {
73             my @_UNDOCUMENTED__dxdy_list_at_n = (undef, 8, 5, 5, 4);
74             sub _UNDOCUMENTED__dxdy_list_at_n {
75 0     0   0 my ($self) = @_;
76 0         0 return $_UNDOCUMENTED__dxdy_list_at_n[$self->{'arms'}];
77             }
78             }
79              
80             # N=4 first straight, then for other arms 18,27,36
81             # must override base Math::PlanePath::QuintetCentres
82             sub _UNDOCUMENTED__turn_any_straight_at_n {
83 0     0   0 my ($self) = @_;
84             # arms=1 4 only first arm has origin 0
85             # arms=2 7
86             # arms=3 10
87             # arms=4 13
88 0         0 return 3*$self->arms_count + 1;
89             }
90              
91              
92             #------------------------------------------------------------------------------
93             my @dir4_to_dx = (1,0,-1,0);
94             my @dir4_to_dy = (0,1,0,-1);
95             my @digit_reverse = (0,1,0,0,1,0);
96              
97             sub n_to_xy {
98 24120     24120 1 78871 my ($self, $n) = @_;
99             ### QuintetCurve n_to_xy(): $n
100              
101 24120 50       46275 if ($n < 0) {
102 0         0 return;
103             }
104 24120 50       47984 if (is_infinite($n)) {
105 0         0 return ($n,$n);
106             }
107              
108 24120         47462 my $arms = $self->{'arms'};
109 24120         33323 my $int = int($n);
110 24120         32774 $n -= $int; # fraction part
111              
112 24120         45836 my $rot = _divrem_mutate ($int,$arms);
113 24120 100       45653 if ($rot) { $int += 1; }
  4086         5663  
114              
115 24120         51185 my @digits = digit_split_lowtohigh($int,5);
116 24120         38835 my @sx;
117             my @sy;
118             {
119 24120         33880 my $sy = 0 * $int; # inherit bignum 0
  24120         35891  
120 24120         33669 my $sx = 1 + $sy; # inherit bignum 1
121 24120         39090 foreach (@digits) {
122 113687         151043 push @sx, $sx;
123 113687         158235 push @sy, $sy;
124              
125             # 2*(sx,sy) + rot+90(sx,sy)
126 113687         187851 ($sx,$sy) = (2*$sx - $sy,
127             2*$sy + $sx);
128             }
129             # ### @digits
130             # my $rev = 0;
131             # for (my $i = $#digits; $i >= 0; $i--) { # high to low
132             # ### digit: $digits[$i]
133             # if ($rev) {
134             # ### reverse: "$digits[$i] to ".(5 - $digits[$i])
135             # $digits[$i] = (5 - $digits[$i]) % 5;
136             # }
137             # # $rev ^= $digit_reverse[$digits[$i]];
138             # ### now rev: $rev
139             }
140             # ### reversed n: @digits
141              
142              
143 24120         34742 my $x = 0;
144 24120         32521 my $y = 0;
145 24120         32722 my $rev = 0;
146              
147 24120         47004 while (defined (my $digit = pop @digits)) { # high to low
148 113687         151417 my $sx = pop @sx;
149 113687         155497 my $sy = pop @sy;
150             ### at: "$x,$y digit $digit side $sx,$sy"
151              
152 113687 100       194980 if ($rot & 2) {
153 41760         67035 ($sx,$sy) = (-$sx,-$sy);
154             }
155 113687 100       188508 if ($rot & 1) {
156 52157         81811 ($sx,$sy) = (-$sy,$sx);
157             }
158              
159 113687 100       172449 if ($rev) {
160 49799 100       104427 if ($digit == 0) {
    100          
    100          
    100          
161 10358         14904 $rev = 0;
162 10358         13722 $rot++;
163              
164             } elsif ($digit == 1) {
165 6177         8518 $x -= $sy;
166 6177         8313 $y += $sx;
167 6177         8522 $rot++;
168              
169             } elsif ($digit == 2) {
170 6425         8984 $x += -2*$sy;
171 6425         8799 $y += 2*$sx;
172              
173             } elsif ($digit == 3) {
174 6318         9450 $x += $sx - 2*$sy; # add 2*rot-90(side) + side
175 6318         8727 $y += $sy + 2*$sx;
176 6318         8130 $rot--;
177 6318         8466 $rev = 0;
178              
179             } else { # $digit == 4
180 20521         28000 $x += $sx - $sy; # add rot-90(side) + side
181 20521         27967 $y += $sy + $sx;
182             }
183              
184             } else {
185             # normal
186              
187 63888 100       132159 if ($digit == 0) {
    100          
    100          
    100          
188              
189             } elsif ($digit == 1) {
190 16415         21888 $x += $sx;
191 16415         21363 $y += $sy;
192 16415         21401 $rot--;
193 16415         22630 $rev = 1;
194              
195             } elsif ($digit == 2) {
196 9455         13300 $x += $sx + $sy; # add side + rot-90(side)
197 9455         13352 $y += $sy - $sx;
198              
199             } elsif ($digit == 3) {
200 9411         13260 $x += 2*$sx + $sy;
201 9411         12588 $y += 2*$sy - $sx;
202 9411         13199 $rot++;
203              
204             } else { # $digit == 4
205 12470         17769 $x += 2*$sx;
206 12470         17271 $y += 2*$sy;
207 12470         16218 $rot++;
208 12470         16684 $rev = 1;
209             }
210             }
211              
212             # lowest non-zero digit determines the direction
213 113687 100       261834 if ($digit != 0) {
214             ### frac_dir at non-zero: $rot
215             }
216             }
217              
218             ### final: "$x,$y"
219             ### $rot
220 24120         33611 $rot &= 3;
221 24120         92717 return ($n * $dir4_to_dx[$rot] + $x,
222             $n * $dir4_to_dy[$rot] + $y);
223             }
224              
225             # up upl left
226             my @attempt_x = (0, 0, -1, -1);
227             my @attempt_y = (0, 1, 1, 0);
228             sub xy_to_n {
229 2562     2562 1 9648 my ($self, $x, $y) = @_;
230             ### QuintetCurve xy_to_n(): "$x, $y"
231              
232 2562         5007 $x = round_nearest($x);
233 2562         4983 $y = round_nearest($y);
234              
235 2562         4082 my ($n, $cx, $cy);
236 2562         4357 foreach my $i (0, 1, 2, 3) {
237 6110 100 66     17072 if (defined ($n = $self->SUPER::xy_to_n($x + $attempt_x[$i],
      100        
      100        
238             $y + $attempt_y[$i]))
239             && (($cx,$cy) = $self->n_to_xy($n))
240             && $x == $cx
241             && $y == $cy) {
242 2562         6460 return $n;
243             }
244             }
245 0         0 return undef;
246             }
247              
248             #------------------------------------------------------------------------------
249             # levels
250              
251             # arms=1 arms=2 arms=3 arms=4
252             # level 0 0..1 = 2 0..2 = 2+1=3 0..3 = 2+1+1=4 0..4 = 2+1+1+1=5
253             # level 1 0..5 = 6 0..10 = 6+5=11 0..15 = 6+5+5=16 0..20 = 6+5+5+5=21
254             # level 2 0..25 = 26 0..50 = 26+25=51 0..75 = 26+25+25=76 0..100 = 26+25+25+25=101
255             # 5^k 2*5^k 3*5^k 4*5^k
256             #
257             sub level_to_n_range {
258 7     7 1 534 my ($self, $level) = @_;
259 7         24 return (0, 5**$level * $self->{'arms'});
260             }
261             sub n_to_level {
262 0     0 1 0 my ($self, $n) = @_;
263 0 0       0 if ($n < 0) { return undef; }
  0         0  
264 0 0       0 if (is_infinite($n)) { return $n; }
  0         0  
265 0         0 $n = round_nearest($n);
266 0         0 $n += $self->{'arms'}-1; # division rounding up
267 0         0 _divrem_mutate ($n, $self->{'arms'});
268 0         0 my ($pow, $exp) = round_up_pow ($n, 5);
269 0         0 return $exp;
270             }
271              
272              
273             #------------------------------------------------------------------------------
274              
275             # R,L,L,S
276             #
277             # forward reverse
278             # 0 forward 0 forward
279             # 1 reverse 1 reverse
280             # 2 forward 2 reverse
281             # 3 forward 3 forward
282             # 4 reverse 4 reverse
283             {
284             # 1 2 3 4
285             my @_UNDOCUMENTED__n_to_turn_LSR = (-1, 1, 1, 0, # forward no low zeros
286             -1, 1, 0, 0, # forward low zeros
287             0,-1,-1, 1, # reverse
288             0, 0,-1, 1);
289             sub _UNDOCUMENTED__n_to_turn_LSR {
290 998     998   13036 my ($self, $n) = @_;
291             ### _UNDOCUMENTED__n_to_turn_LSR(): $n
292              
293 998         1623 $n += $self->{'arms'}-1; # division rounding up
294 998         2467 _divrem_mutate ($n, $self->{'arms'});
295 998 50       1747 if ($n < 1) { return undef; }
  0         0  
296              
297 998         1368 my $any_low_zeros;
298             my $low;
299 998         1734 while ($n) {
300 1244 100       2532 last if ($low = _divrem_mutate($n,5));
301 246         449 $any_low_zeros = 1;
302             }
303             ### $low
304             ### $any_low_zeros
305              
306 998         1450 my $non_two = 0;
307 998         1792 while (($non_two = _divrem_mutate($n,5)) == 2) {}
308             ### $non_two
309              
310 998 100 100     3177 $low = $low - 1
    100          
311             + ($any_low_zeros ? 4 : 0) # low zeros
312             + ($non_two == 1 || $non_two == 4 ? 8 : 0); # reverse
313             ### lookup: $low
314 998         1943 return $_UNDOCUMENTED__n_to_turn_LSR[$low];
315             }
316             }
317              
318              
319             #------------------------------------------------------------------------------
320             1;
321             __END__