File Coverage

blib/lib/Algorithm/Metric/Chessboard.pm
Criterion Covered Total %
statement 76 77 98.7
branch 18 24 75.0
condition 10 16 62.5
subroutine 14 14 100.0
pod 3 9 33.3
total 121 140 86.4


line stmt bran cond sub pod time code
1 4     4   10250 use strict;
  4         7  
  4         224  
2             package Algorithm::Metric::Chessboard;
3 4     4   24 use vars qw( $VERSION );
  4         8  
  4         250  
4             $VERSION = '0.01';
5              
6 4     4   2912 use Algorithm::Metric::Chessboard::Journey;
  4         12  
  4         273  
7 4     4   2515 use Algorithm::Metric::Chessboard::Wormhole;
  4         12  
  4         119  
8 4     4   23 use Carp "croak";
  4         7  
  4         4526  
9              
10             =head1 NAME
11              
12             Algorithm::Metric::Chessboard - Calculate distances on a square grid with optional wormholes (the 'chessboard metric').
13              
14             =head1 DESCRIPTION
15              
16             Calculates the minimum number of moves between two points in a game
17             played on a square grid, where one move is a jump from a point to a
18             horizontal, vertical or diagonal neighbour.
19              
20             With no other features, the number of moves taken to go from
21             the point C<(x1, y1)> to C<(x2, y2)> I be quite simple:
22              
23             d( (x1, y1), (x2, y2) ) = max( abs( x1 - x2 ), abs( y1 - y2) )
24              
25             However within the space are "wormholes" which allow you to travel
26             between any two distant points, so the actual number of moves may be
27             smaller than the above. Wormhole travel costs a fixed number of moves.
28              
29             =head1 SYNOPSIS
30              
31             my @wormholes = (
32             Algorithm::Metric::Chessboard::Wormhole->new( x => 5, y => 30 ),
33             Algorithm::Metric::Chessboard::Wormhole->new( x => 98, y => 99 ),
34             );
35              
36             my $grid = Algorithm::Metric::Chessboard->new(
37             x_range => [ 0, 99 ],
38             y_range => [ 0, 99 ],
39             wormholes => \@wormholes,
40             wormhole_cost => 3,
41             );
42              
43             my $wormhole = $grid->nearest_wormhole( x => 26, y => 53 );
44              
45             my $journey = $grid->shortest_journey(start => [1, 6], end => [80, 1]);
46              
47             =head1 METHODS
48              
49             =over
50              
51             =item B
52              
53             my @wormholes = (
54             Algorithm::Metric::Chessboard::Wormhole->new(
55             x => 5,
56             y => 30,
57             ),
58             Algorithm::Metric::Chessboard::Wormhole->new(
59             x => 98,
60             y => 99,
61             ),
62             );
63              
64             my $grid = Algorithm::Metric::Chessboard->new(
65             x_range => [ 0, 99 ],
66             y_range => [ 0, 99 ],
67             wormholes => \@wormholes,
68             wormhole_cost => 3,
69             );
70              
71             C is optional. C defaults to 0.
72              
73             =cut
74              
75             sub new {
76 5     5 1 673 my ($class, %args) = @_;
77 5         10 my $self = {};
78 5         15 bless $self, $class;
79 5 50       23 $self->x_range( $args{x_range} ) or croak "Bad 'x_range'";
80 5 50       22 $self->y_range( $args{y_range} ) or croak "Bad 'y_range'";
81 5         19 $self->wormholes( $args{wormholes} );
82 5         24 $self->wormhole_cost( $args{wormhole_cost} );
83 5         20 $self->calculate_wormhole_dists;
84 5         43 return $self;
85             }
86              
87             =item B
88              
89             my $wormhole = $grid->nearest_wormhole( x => 26, y => 53 );
90             print "Nearest wormhole is " . $wormhole->id . " at ("
91             . $wormhole->x . ", " . $wormhole->y . ")";
92              
93             Returns a L object.
94              
95             =cut
96              
97             sub nearest_wormhole {
98 5     5 1 20 my ($self, %args) = @_;
99 5         21 return $self->{nearest_wormhole}[$args{x}][$args{y}];
100             }
101              
102             =item B
103              
104             my $journey = $grid->shortest_journey(
105             start => [1, 6],
106             end => [80, 1],
107             );
108             my $distance = $journey->distance;
109             my @via = $journey->via;
110             print "Shortest journey is $distance move"
111             . ( $distance == 1 ? "" : "s" );
112             if ( scalar @via ) {
113             print " via " . $via[0]->id . " and " . $via[1]->id;
114             }
115              
116             Returns a L object.
117              
118             =cut
119              
120             sub shortest_journey {
121 2     2 1 402 my ($self, %args) = @_;
122 2         8 my ($start, $end) = @args{ qw( start end ) };
123 2         7 my $straight_dist = $self->straight_distance(
124             start => $start,
125             end => $end,
126             );
127 2         12 my $start_worm = $self->nearest_wormhole(
128             x => $start->[0],
129             y => $start->[1] );
130 2         6 my $end_worm = $self->nearest_wormhole(
131             x => $end->[0],
132             y => $end->[1] );
133 2 50 33     20 if ( $start_worm and $end_worm ) {
134 2         9 my $worm_dist = $self->straight_distance(
135             start => $start,
136             end => [ $start_worm->x, $start_worm->y ]
137             )
138             + $self->wormhole_cost
139             + $self->straight_distance(
140             start => $end,
141             end => [ $end_worm->x, $end_worm->y ]
142             );
143 2 50       9 if ( $worm_dist < $straight_dist ) {
144 2         21 return Algorithm::Metric::Chessboard::Journey->new(
145             start => $start,
146             end => $end,
147             via => [ $start_worm, $end_worm ],
148             distance => $worm_dist,
149             );
150             }
151             }
152              
153 0         0 return Algorithm::Metric::Chessboard::Journey->new(
154             start => $start,
155             end => $end,
156             distance => $straight_dist,
157             );
158             }
159              
160             sub calculate_wormhole_dists {
161 5     5 0 9 my $self = shift;
162 5         7 my @wormholes = @{ $self->wormholes };
  5         14  
163 5         11 my ($x_min, $x_max) = @{ $self->x_range };
  5         14  
164 5         8 my ($y_min, $y_max) = @{ $self->y_range };
  5         1107  
165 5         91 foreach my $x ( $x_min .. $x_max ) {
166 500         3993 foreach my $y ( $y_min .. $y_max ) {
167 49000         55767 my ($nearest_wormhole, $nearest_dist);
168 49000         66464 foreach my $wormhole ( @wormholes ) {
169 70000         235790 my $dist = $self->straight_distance(
170             start => [ $x, $y ],
171             end => [ $wormhole->x, $wormhole->y ],
172             );
173 70000 100 100     277633 if ( ! defined $nearest_wormhole or $dist < $nearest_dist ) {
174 59167         68744 $nearest_wormhole = $wormhole;
175 59167         106221 $nearest_dist = $dist;
176             }
177             }
178 49000         121000 $self->{nearest_wormhole}[$x][$y] = $nearest_wormhole;
179             }
180             }
181             }
182              
183             sub straight_distance {
184 70006     70006 0 150249 my ($self, %args) = @_;
185 70006         81030 my ($x1, $y1) = @{ $args{start} };
  70006         114793  
186 70006         75854 my ($x2, $y2) = @{ $args{end} };
  70006         107161  
187 70006         89425 my $x_dist = abs( $x1 - $x2 );
188 70006         86258 my $y_dist = abs( $y1 - $y2 );
189 70006 100       126640 my $dist = $x_dist < $y_dist ? $y_dist : $x_dist;
190 70006         136109 return $dist;
191             }
192              
193             sub x_range {
194 11     11 0 975 my ($self, $value) = @_;
195 11 100       34 if ( defined $value ) {
196 5 50 33     44 croak "Bad 'x_range'"
197             unless ref $value eq "ARRAY" and scalar @$value == 2;
198 5         21 $self->{x_range} = $value;
199             }
200 11         121 return $self->{x_range};
201             }
202              
203             sub y_range {
204 11     11 0 20 my ($self, $value) = @_;
205 11 100       28 if ( defined $value ) {
206 5 50 33     38 croak "Bad 'y_range'"
207             unless ref $value eq "ARRAY" and scalar @$value == 2;
208 5         14 $self->{y_range} = $value;
209             }
210 11         39 return $self->{y_range};
211             }
212              
213             sub wormholes {
214 10     10 0 17 my ($self, $value) = @_;
215 10 100       64 $self->{wormholes} = $value if $value;
216 10   100     42 return $self->{wormholes} || [];
217             }
218              
219             sub wormhole_cost {
220 7     7 0 29 my ($self, $value) = @_;
221 7 100       22 $self->{wormhole_cost} = $value if $value;
222 7   100     34 return $self->{wormhole_cost} || 0;
223             }
224              
225             =back
226              
227             =head1 AUTHOR
228              
229             Kake Pugh (kake@earth.li).
230              
231             =head1 COPYRIGHT
232              
233             Copyright (C) 2004 Kake Pugh. All Rights Reserved.
234              
235             This module is free software; you can redistribute it and/or modify it
236             under the same terms as Perl itself.
237              
238             =head1 CREDITS
239              
240             Jon Chin helped me figure out the name, Andy Armstrong and Mike
241             Stevens helped me clarify the statement of the problem.
242              
243             =head1 SEE ALSO
244              
245             Why I wrote this:
246              
247             =over 4
248              
249             =item * L
250              
251             =item * L
252              
253             =back
254              
255             =cut
256              
257             1;