File Coverage

blib/lib/Game/Battleship/Grid.pm
Criterion Covered Total %
statement 77 89 86.5
branch 27 48 56.2
condition 13 24 54.1
subroutine 9 9 100.0
pod 1 1 100.0
total 127 171 74.2


line stmt bran cond sub pod time code
1             package Game::Battleship::Grid;
2             $Game::Battleship::Grid::VERSION = '0.06';
3             our $AUTHORITY = 'cpan:GENE';
4 2     2   10 use strict;
  2         5  
  2         51  
5 2     2   10 use warnings;
  2         5  
  2         48  
6 2     2   12 use Carp;
  2         2  
  2         134  
7 2     2   10 use Game::Battleship::Craft;
  2         2  
  2         59  
8              
9 2     2   8 use Moo;
  2         4  
  2         12  
10 2     2   595 use Types::Standard qw( ArrayRef Int );
  2         4  
  2         15  
11              
12             has dimension => (
13             is => 'ro',
14             isa => ArrayRef[Int],
15             default => sub { [ 9, 9 ] },
16             );
17              
18             has fleet => (
19             is => 'ro',
20             isa => ArrayRef,
21             );
22              
23             # Place the array reference of craft on the grid.
24             sub BUILD {
25 5     5 1 170 my $self = shift;
26              
27             # Initialize the matrix.
28 5         18 for my $i (0 .. $self->{dimension}[0]) {
29 50         82 for my $j (0 .. $self->{dimension}[1]) {
30 500         964 $self->{matrix}[$i][$j] = '.';
31             }
32             }
33              
34             # Place the fleet on the grid.
35 5         7 for my $craft (@{ $self->{fleet} }) {
  5         56  
36 15         17 my ($ok, $x0, $y0, $x1, $y1, $orient);
37              
38 15 50       31 if (defined $craft->{position}) {
39 0         0 ($x0, $y0) = ($craft->{position}[0], $craft->{position}[1]);
40              
41             # Set the craft orientation and tail coordinates.
42             ($orient, $x1, $y1) = _tail_coordinates(
43             $x0, $y0,
44 0         0 $craft->{points} - 1
45             );
46             }
47             else {
48             # XXX This looping is needlessly brutish. refactoring please
49 15         36 while (not $ok) {
50             # Grab a random coordinate that we haven't seen.
51 27         99 $x0 = int(rand($self->{dimension}[0] + 1));
52 27         70 $y0 = int(rand($self->{dimension}[1] + 1));
53              
54             # Set the craft orientation and tail coordinates.
55             ($orient, $x1, $y1) = _tail_coordinates(
56             $x0, $y0,
57 27         67 $craft->{points} - 1
58             );
59              
60             # If the craft is not placed off the grid and it does
61             # not collide with another craft, then we are ok to
62             # move on.
63             # XXX regex constraint rules here?
64 27 100 100     157 if ($x1 <= $self->{dimension}[0] &&
65             $y1 <= $self->{dimension}[1]
66             ) {
67             # For each craft (except the current one) that has
68             # a position, do the craft share a common point?
69 22         35 my $collide = 0;
70              
71 22         26 for (@{ $self->{fleet} }) {
  22         55  
72             # Ships can't be the same.
73 86 100       219 if ($craft->{name} ne $_->{name}) {
74             # Ships can't intersect.
75 71 100 100     274 if (defined $_->{position} &&
76             _segment_intersection(
77             $x0, $y0,
78             $x1, $y1,
79 41         118 @{ $_->{position}[0] },
80 41         100 @{ $_->{position}[1] }
81             )
82             ) {
83 7         10 $collide = 1;
84 7         12 last;
85             }
86             }
87             }
88              
89 22 100       76 $ok = 1 unless $collide;
90             }
91             }
92              
93             # Set the craft position.
94 15         52 $craft->{position} = [[$x0, $y0], [$x1, $y1]];
95             }
96             #warn "$craft->{name}: [$x0, $y0], [$x1, $y1], $craft->{points}\n";
97              
98             # Add the craft to the grid.
99 15         34 for my $n (0 .. $craft->{points} - 1) {
100 51 100       87 if ($orient) {
101 13         35 $self->{matrix}[$x0 + $n][$y0] = $craft->{id};
102             }
103             else {
104 38         170 $self->{matrix}[$x0][$y0 + $n] = $craft->{id};
105             }
106             }
107             }
108             }
109              
110             sub _tail_coordinates {
111             # Get the coordinates of the end of the segment based on a given
112             # span.
113 27     27   38 my ($x0, $y0, $span) = @_;
114              
115             # Set orientation to 0 (vertical) or 1 (horizontal).
116 27         45 my $orient = int rand 2;
117              
118 27         36 my ($x1, $y1) = ($x0, $y0);
119              
120 27 100       45 if ($orient) {
121 12         18 $x1 += $span;
122             }
123             else {
124 15         24 $y1 += $span;
125             }
126              
127 27         61 return $orient, $x1, $y1;
128             }
129              
130             sub _segment_intersection {
131             # 0 - Intersection doesn't exist.
132             # 1 - Intersection exists.
133             # NOTE: In Battleship, we only care about yes/no, but the
134             # original code can tell much more:
135             # 0 (was 2) - line segments are parallel
136             # 0 (was 3) - line segments are collinear but do not overlap.
137             # 4 - line segments are collinear and share an end point.
138             # 5 - line segments are collinear and overlap.
139              
140 41 50   41   90 croak "segment_intersection needs 4 points\n" unless @_ == 8;
141             my(
142 41         65 $x0, $y0, $x1, $y1, # AB segment 1
143             $x2, $y2, $x3, $y3 # CD segment 2
144             ) = @_;
145             #warn "[$x0, $y0]-[$x1, $y1], [$x2, $y2]-[$x3, $y3]\n";
146              
147 41         52 my $xba = $x1 - $x0;
148 41         54 my $yba = $y1 - $y0;
149 41         60 my $xdc = $x3 - $x2;
150 41         48 my $ydc = $y3 - $y2;
151 41         56 my $xca = $x2 - $x0;
152 41         48 my $yca = $y2 - $y0;
153              
154 41         61 my $delta = $xba * $ydc - $yba * $xdc;
155 41         51 my $t1 = $xca * $ydc - $yca * $xdc;
156 41         57 my $t2 = $xca * $yba - $yca * $xba;
157              
158 41 100       74 if ($delta != 0) {
159 24         41 my $u = $t1 / $delta;
160 24         33 my $v = $t2 / $delta;
161              
162             # Two segments intersect (including at end points).
163 24 100 100     253 return ($u <= 1 && $u >= 0 && $v <= 1 && $v >= 0) ? 1 : 0;
164             }
165             else {
166             # AB & CD are parallel.
167 17 100 66     129 return 0 if $t1 != 0 && $t2 != 0;
168             # NOTE: We just care about yes/no, so this is the old way:
169             # return 2 if $t1 != 0 && $t2 != 0;
170              
171             # When AB & CD are collinear...
172 1         2 my ($a, $b, $c, $d);
173              
174             # If AB isn't a vertical line segment, project to x-axis.
175 1 50       17 if ($x0 != $x1) {
176             # < is min, > is max
177 0 0       0 $a = $x0 < $x1 ? $x0 : $x1;
178 0 0       0 $b = $x0 > $x1 ? $x0 : $x1;
179 0 0       0 $c = $x2 < $x3 ? $x2 : $x3;
180 0 0       0 $d = $x2 > $x3 ? $x2 : $x3;
181              
182 0 0 0     0 if ($d < $a || $c > $b) {
    0 0        
183             # NOTE: We just care about yes/no. The old way returns 3:
184 0         0 return 0;#3;
185             }
186             elsif ($d == $a || $c == $b) {
187 0         0 return 4;
188             }
189             else {
190 0         0 return 5;
191             }
192             }
193             # If AB is a vertical line segment, project to y-axis.
194             else {
195             # < is min, > is max
196 1 50       4 $a = $y0 < $y1 ? $y0 : $y1;
197 1 50       4 $b = $y0 > $y1 ? $y0 : $y1;
198 1 50       4 $c = $y2 < $y3 ? $y2 : $y3;
199 1 50       3 $d = $y2 > $y3 ? $y2 : $y3;
200              
201 1 50 33     15 if ($d < $a || $c > $b) {
    50 33        
202             # NOTE: We just care about yes/no. The old way returns 3:
203 0         0 return 0;#3;
204             }
205             elsif ($d == $a || $c == $b) {
206 0         0 return 4;
207             }
208             else {
209 1         5 return 5;
210             }
211             }
212             }
213             }
214              
215             1;
216              
217             __END__