File Coverage

blib/lib/NetHack/FOV.pm
Criterion Covered Total %
statement 88 93 94.6
branch 38 46 82.6
condition 15 20 75.0
subroutine 10 10 100.0
pod 1 1 100.0
total 152 170 89.4


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             package NetHack::FOV;
3              
4 1     1   25493 use warnings;
  1         3  
  1         26  
5 1     1   5 use strict;
  1         2  
  1         36  
6              
7 1     1   4 use Exporter;
  1         5  
  1         937  
8              
9             our $VERSION = 0.01;
10             our @EXPORT_OK = qw(calculate_fov);
11             our @ISA = qw(Exporter);
12              
13             sub _clear {
14 74376     74376   94129 my ($self, $x, $y) = @_;
15              
16 74376         203095 return $self->{cbi}->($x + $self->{x}, $y + $self->{y});
17             }
18              
19             sub _see {
20 26899     26899   31582 my ($self, $x, $y) = @_;
21              
22 26899         61080 return $self->{cbo}->($x + $self->{x}, $y + $self->{y});
23             }
24              
25             sub _Q_path {
26 6868     6868   8842 my ($self, $x, $y) = @_;
27              
28 6868         7451 my ($px, $py) = (0,0);
29              
30 6868         9027 my $flip = abs($x) > abs($y);
31              
32 6868 100       14277 my ($rmaj, $rmin) = $flip ? (\$px,\$py) : (\$py,\$px);
33 6868 100       12393 my ($dmaj, $dmin) = $flip ? ( $x , $y ) : ( $y , $x );
34              
35 6868         7778 my $fmin = -abs($dmaj);
36              
37 6868         10616 for (2 .. abs($dmaj)) {
38 27625         150668 $fmin += 2*abs($dmin);
39 27625 100       45905 if ($fmin >= 0) { $fmin -= 2*abs($dmaj); $$rmin += ($dmin <=> 0); }
  8443         11107  
  8443         11181  
40 27625         30745 $$rmaj += ($dmaj <=> 0);
41 27625 100       62194 if (!$self->_clear($px, $py)) {
42 4095         38799 return 0;
43             }
44             }
45              
46 2773         25868 return 1;
47             }
48              
49             sub _quadrant {
50 4377     4377   5874 my ($self, $hs, $row, $left, $right_mark) = @_;
51              
52 4377         4004 my ($right, $right_edge);
53              
54 4377 100       8867 my $rail = ($hs == 1) ? 79 - $self->{x} : $self->{x};
55             # Why does this have to be irregular
56              
57 4377         8348 while ($left <= $right_mark) {
58             #print "in quadrant, $rail $hs $row $left $right_mark\n";
59 6462         7273 $right_edge = $left;
60 6462         12563 my $left_clear = $self->_clear($hs*$left, $row);
61 6462   100     47420 while ($self->_clear($hs*$right_edge, $row) == $left_clear &&
      66        
62             ($left_clear || $right_edge <= $right_mark + 1))
63 27968         259687 { $right_edge++ }
64 6462         48186 $right_edge--;
65 6462 100       11599 if ($left_clear) { $right_edge++; }
  3845         4456  
66              
67 6462 50       10750 if ($right_edge >= $rail) {
68 0         0 $right_edge = $rail; # Yuck
69             }
70              
71             #print "in quadrant2, $hs $row $left $right_mark $right_edge\n";
72              
73 6462 100       12246 if (!$left_clear) {
74 2617 100       4502 if ($right_edge > $right_mark) {
75 1612 100       3795 $right_edge = $self->_clear($hs*$right_mark,
76             $row - ($row <=> 0)) ? $right_mark + 1 : $right_mark;
77             }
78              
79 2617         13943 for (my $i = $left; $i <= $right_edge; $i++) {
80 9348         17550 $self->_see($hs*$i, $row);
81             }
82 2617         2733 $left = $right_edge + 1;
83 2617         6616 next;
84             }
85             #print "in quadrant3, $hs $row $left $right_mark\n";
86              
87 3845 100       15040 if ($left != 0) {
88 1557         2757 for (; $left <= $right_edge; $left++) {
89 4235 100       8714 last if $self->_Q_path($hs*$left, $row);
90             }
91              
92 1557 50       2699 if ($left >= $rail) {
93             # Double yuck
94 0 0       0 if ($left == $rail) {
95 0         0 $self->_see($left*$hs, $row);
96             }
97              
98 0         0 return;
99             }
100              
101 1557 100       2879 if ($left >= $right_edge) {
102 800         789 $left = $right_edge;
103 800         1968 next;
104             }
105             }
106             #print "in quadrant4, $hs $row $left $right_mark\n";
107              
108 3045 100       4651 if ($right_mark < $right_edge) {
109 1064         2029 for ($right = $right_mark; $right <= $right_edge; $right++) {
110 2633 100       5057 last if !$self->_Q_path($hs*$right, $row);
111             }
112 1064         1481 --$right;
113             }
114 1981         2442 else { $right = $right_edge; }
115             #print "in quadrant5, $hs $row $left $right_mark\n";
116 3045 100       6120 if ($left <= $right) {
117 2969 100 100     6851 if ($left == $right && $left == 0 && !$self->_clear($hs,$row) &&
      100        
      66        
118             ($left != $rail)) {
119 66         683 $right = 1;
120             }
121              
122 2969 50       7670 if ($right > $rail) { $right = $rail }
  0         0  
123              
124 2969         6426 for (my $i = $left; $i <= $right; $i++) {
125 13259         28245 $self->_see($hs*$i,$row);
126             }
127              
128 2969         11063 $self->_quadrant($hs, $row + ($row <=> 0),$left,$right);
129 2969         6809 $left = $right + 1;
130             }
131             #print "in quadrant6, $hs $row $left $right_mark\n";
132             }
133             }
134              
135             sub _trace {
136 352     352   541 my $self = shift;
137              
138 352         521 my ($xl, $xr) = (0, 0);
139              
140 352         832 $self->_see(0,0);
141              
142             #for my $i (-2 .. 2) { print ($self->_clear($i,0) ? "1" : "0"); }
143             #print "\n";
144              
145 352         528 do { $self->_see(--$xl,0) } while $self->_clear($xl,0);
  1970         12402  
146 352         2611 do { $self->_see(++$xr,0) } while $self->_clear($xr,0);
  1970         12804  
147              
148             # Triple yuck
149 352 50       3200 $xr-- if $xr + $self->{x} == 80;
150 352 50       991 $xl++ if $xl + $self->{x} < 0;
151              
152             #print "$xl $xr\n";
153              
154 352         1158 $self->_quadrant(-1,-1,0,-$xl);
155 352         895 $self->_quadrant(+1,-1,0,$xr);
156 352         943 $self->_quadrant(-1,+1,0,-$xl);
157 352         978 $self->_quadrant(+1,+1,0,$xr);
158             }
159              
160             # not handled: swimming, phasing
161             # possibly buggy: everything
162             sub calculate_fov {
163 352     352 1 1695095 my ($startx, $starty, $cb, $cbo) = @_;
164              
165 352         594 my @visible;
166              
167 352         2157 my $self = bless { x => $startx, y => $starty, cbi => $cb, cbo => $cbo };
168              
169 26899     26899   30719 $self->{cbo} ||= sub { my ($x, $y) = @_;
170 352 50 33     3712 $visible[$x][$y] = 1 unless $x < 0 || $y < 0; };
  26899   50     183656  
171              
172 352         1022 $self->_trace();
173              
174 352         4555 return \@visible;
175             }
176              
177             1;
178              
179             =head1 NAME
180              
181             NetHack::FOV - NetHack compatible field of view
182              
183             =head1 SYNOPSIS
184              
185             use NetHack::FOV 'calculate_fov';
186              
187             my $AoA = calculate_fov($x, $y, \&transparent);
188              
189             =head1 DESCRIPTION
190              
191             This package implements field of view (the determination, for every
192             square on the map simultaneously, of whether it is visible to the
193             avatar), in a NetHack compatible way. It is expected to be primarily
194             useful to bot writers.
195              
196             =head1 FUNCTION
197              
198             NetHack::FOV defines and allows import of a single function.
199              
200             =over 4
201              
202             =item B
203              
204             STARTX and STARTY determine the location of the avatar on the integer
205             plane used by FOV::NetHack. INCALLBACK is used to determine the map's
206             local structure; it is passed two arguments, X and Y coordinates, and
207             must return true iff the specified point is transparent. OUTCALLBACK
208             is used to return the viewable map, one coordinate pair at a time as
209             for INCALLBACK. OUTCALLBACK is optional; if you omit it, calculate_fov
210             will return an array of arrays such that $ret[$x][$y] will be true
211             iff ($x,$y) is visible.
212              
213             Obviously, calculate_fov will hang if passed a map which has lines of
214             sight with infinite length. Also, if the visible part of the map
215             extends beyond the doubly non-negative quadrant, and you are using
216             the array of arrays return method, only the part which lies within
217             said quadrant will be returned. Due to unusual boundary conditions
218             of the NetHack FOV algorithm, this module will misbehave if passed
219             data outside the range of 1 to 79 inclusive in the horizontal
220             dimension; no such restriction exists vertically.
221              
222             You may be wondering why the callbacks exist at all and calculate_fov
223             doesn't just use arrays of arrays both ways. The answer is asymptotic
224             complexity. The algorithm used by calculate_fov takes time proportional
225             to the number of I tiles. If an array of arrays had to be
226             constructed for the transparency data, any user would suffer time costs
227             proportional to the number of I tiles.
228              
229             =back
230              
231             =head1 AUTHOR
232              
233             Stefan O'Rear
234              
235             =head1 COPYRIGHT
236              
237             Copyright 2008 Stefan O'Rear.
238              
239             This program is free software; you can redistribute it and/or modify it
240             under the same terms as Perl itself.
241              
242             =cut
243