File Coverage

blib/lib/Game/Battleship/Grid.pm
Criterion Covered Total %
statement 72 83 86.7
branch 29 50 58.0
condition 14 24 58.3
subroutine 7 7 100.0
pod 0 1 0.0
total 122 165 73.9


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