File Coverage

blib/lib/Game/Battleship/Grid.pm
Criterion Covered Total %
statement 61 83 73.4
branch 16 48 33.3
condition 8 24 33.3
subroutine 7 7 100.0
pod 1 1 100.0
total 93 163 57.0


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