File Coverage

blib/lib/Math/PlanePath/QuintetCurve.pm
Criterion Covered Total %
statement 104 126 82.5
branch 37 44 84.0
condition 12 15 80.0
subroutine 12 17 70.5
pod 6 6 100.0
total 171 208 82.2


line stmt bran cond sub pod time code
1             # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 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   9291 use 5.004;
  1         10  
32 1     1   7 use strict;
  1         2  
  1         25  
33              
34 1     1   5 use vars '$VERSION', '@ISA';
  1         2  
  1         67  
35             $VERSION = 129;
36              
37             # inherit: new(), rect_to_n_range(), arms_count(), n_start(),
38             # parameter_info_array(), xy_is_visited()
39 1     1   567 use Math::PlanePath::QuintetCentres;
  1         3  
  1         36  
40 1     1   551 use Math::PlanePath::Base::NSEW;
  1         2  
  1         42  
41             @ISA = ('Math::PlanePath::Base::NSEW',
42             'Math::PlanePath::QuintetCentres');
43              
44 1     1   6 use Math::PlanePath;
  1         2  
  1         33  
45             *_divrem_mutate = \&Math::PlanePath::_divrem_mutate;
46              
47             use Math::PlanePath::Base::Generic
48 1         47 'is_infinite',
49 1     1   6 'round_nearest';
  1         2  
50             use Math::PlanePath::Base::Digits
51 1         1192 'digit_split_lowtohigh',
52 1     1   5 '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 21995     21995 1 72713 my ($self, $n) = @_;
99             ### QuintetCurve n_to_xy(): $n
100              
101 21995 50       40737 if ($n < 0) {
102 0         0 return;
103             }
104 21995 50       43415 if (is_infinite($n)) {
105 0         0 return ($n,$n);
106             }
107              
108 21995         41249 my $arms = $self->{'arms'};
109 21995         29750 my $int = int($n);
110 21995         31028 $n -= $int; # fraction part
111              
112 21995         42385 my $rot = _divrem_mutate ($int,$arms);
113 21995 100       39466 if ($rot) { $int += 1; }
  4086         5757  
114              
115 21995         46092 my @digits = digit_split_lowtohigh($int,5);
116 21995         34567 my @sx;
117             my @sy;
118             {
119 21995         30084 my $sy = 0 * $int; # inherit bignum 0
  21995         31039  
120 21995         31089 my $sx = 1 + $sy; # inherit bignum 1
121 21995         36048 foreach (@digits) {
122 99066         130378 push @sx, $sx;
123 99066         136111 push @sy, $sy;
124              
125             # 2*(sx,sy) + rot+90(sx,sy)
126 99066         167560 ($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 21995         32421 my $x = 0;
144 21995         28021 my $y = 0;
145 21995         28077 my $rev = 0;
146              
147 21995         40775 while (defined (my $digit = pop @digits)) { # high to low
148 99066         131454 my $sx = pop @sx;
149 99066         132782 my $sy = pop @sy;
150             ### at: "$x,$y digit $digit side $sx,$sy"
151              
152 99066 100       166712 if ($rot & 2) {
153 36786         57182 ($sx,$sy) = (-$sx,-$sy);
154             }
155 99066 100       163901 if ($rot & 1) {
156 44802         70725 ($sx,$sy) = (-$sy,$sx);
157             }
158              
159 99066 100       147881 if ($rev) {
160 43087 100       88184 if ($digit == 0) {
    100          
    100          
    100          
161 9341         11717 $rev = 0;
162 9341         12444 $rot++;
163              
164             } elsif ($digit == 1) {
165 5468         7503 $x -= $sy;
166 5468         7306 $y += $sx;
167 5468         7363 $rot++;
168              
169             } elsif ($digit == 2) {
170 6207         8624 $x += -2*$sy;
171 6207         8347 $y += 2*$sx;
172              
173             } elsif ($digit == 3) {
174 5636         8028 $x += $sx - 2*$sy; # add 2*rot-90(side) + side
175 5636         7758 $y += $sy + 2*$sx;
176 5636         7102 $rot--;
177 5636         7584 $rev = 0;
178              
179             } else { # $digit == 4
180 16435         22395 $x += $sx - $sy; # add rot-90(side) + side
181 16435         22152 $y += $sy + $sx;
182             }
183              
184             } else {
185             # normal
186              
187 55979 100       112692 if ($digit == 0) {
    100          
    100          
    100          
188              
189             } elsif ($digit == 1) {
190 14825         19626 $x += $sx;
191 14825         20465 $y += $sy;
192 14825         19039 $rot--;
193 14825         21384 $rev = 1;
194              
195             } elsif ($digit == 2) {
196 9369         12479 $x += $sx + $sy; # add side + rot-90(side)
197 9369         13421 $y += $sy - $sx;
198              
199             } elsif ($digit == 3) {
200 7922         10992 $x += 2*$sx + $sy;
201 7922         11166 $y += 2*$sy - $sx;
202 7922         11153 $rot++;
203              
204             } else { # $digit == 4
205 11067         16209 $x += 2*$sx;
206 11067         14033 $y += 2*$sy;
207 11067         14787 $rot++;
208 11067         15082 $rev = 1;
209             }
210             }
211              
212             # lowest non-zero digit determines the direction
213 99066 100       226166 if ($digit != 0) {
214             ### frac_dir at non-zero: $rot
215             }
216             }
217              
218             ### final: "$x,$y"
219             ### $rot
220 21995         30522 $rot &= 3;
221 21995         82728 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 9617 my ($self, $x, $y) = @_;
230             ### QuintetCurve xy_to_n(): "$x, $y"
231              
232 2562         5043 $x = round_nearest($x);
233 2562         4960 $y = round_nearest($y);
234              
235 2562         3997 my ($n, $cx, $cy);
236 2562         4200 foreach my $i (0, 1, 2, 3) {
237 6088 100 66     16836 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         6745 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 514 my ($self, $level) = @_;
259 7         25 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   12823 my ($self, $n) = @_;
291             ### _UNDOCUMENTED__n_to_turn_LSR(): $n
292              
293 998         1548 $n += $self->{'arms'}-1; # division rounding up
294 998         2333 _divrem_mutate ($n, $self->{'arms'});
295 998 50 33     2523 if ($n < 1 || is_infinite($n)) { return undef; }
  0         0  
296              
297 998         1770 my $any_low_zeros;
298             my $low;
299 998         1754 while ($n) {
300 1244 100       2247 last if ($low = _divrem_mutate($n,5));
301 246         452 $any_low_zeros = 1;
302             }
303             ### $low
304             ### $any_low_zeros
305              
306 998         1432 my $non_two = 0;
307 998         1738 while (($non_two = _divrem_mutate($n,5)) == 2) {}
308             ### $non_two
309              
310 998 100 100     3005 $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         1893 return $_UNDOCUMENTED__n_to_turn_LSR[$low];
315             }
316             }
317              
318              
319             #------------------------------------------------------------------------------
320             1;
321             __END__