line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# -*- Perl -*- |
2
|
|
|
|
|
|
|
# |
3
|
|
|
|
|
|
|
# Dijkstra Map path finding. run perldoc(1) on this file for additional |
4
|
|
|
|
|
|
|
# documentation |
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
package Game::DijkstraMap; |
7
|
|
|
|
|
|
|
|
8
|
2
|
|
|
2
|
|
186450
|
use 5.24.0; |
|
2
|
|
|
|
|
16
|
|
9
|
2
|
|
|
2
|
|
11
|
use warnings; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
65
|
|
10
|
|
|
|
|
|
|
|
11
|
2
|
|
|
2
|
|
13
|
use Carp qw(croak); |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
118
|
|
12
|
2
|
|
|
2
|
|
13
|
use List::Util 1.26 qw(shuffle sum0); |
|
2
|
|
|
|
|
33
|
|
|
2
|
|
|
|
|
167
|
|
13
|
2
|
|
|
2
|
|
1039
|
use Moo; |
|
2
|
|
|
|
|
22387
|
|
|
2
|
|
|
|
|
9
|
|
14
|
2
|
|
|
2
|
|
3885
|
use namespace::clean; |
|
2
|
|
|
|
|
22604
|
|
|
2
|
|
|
|
|
13
|
|
15
|
2
|
|
|
2
|
|
599
|
use Scalar::Util qw(looks_like_number); |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
132
|
|
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
our $VERSION = '1.01'; |
18
|
|
|
|
|
|
|
|
19
|
2
|
|
|
2
|
|
15
|
use constant SQRT2 => sqrt(2); |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
7443
|
|
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
has bad_cost => ( is => 'rw', default => sub { -2147483648 } ); |
22
|
|
|
|
|
|
|
has min_cost => ( is => 'rw', default => sub { 0 } ); |
23
|
|
|
|
|
|
|
has max_cost => ( is => 'rw', default => sub { 2147483647 } ); |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
has costfn => ( |
26
|
|
|
|
|
|
|
is => 'rw', |
27
|
|
|
|
|
|
|
default => sub { |
28
|
|
|
|
|
|
|
return sub { |
29
|
|
|
|
|
|
|
my ( $self, $c ) = @_; |
30
|
|
|
|
|
|
|
if ( $c eq '#' ) { return $self->bad_cost } |
31
|
|
|
|
|
|
|
if ( $c eq 'x' ) { return $self->min_cost } |
32
|
|
|
|
|
|
|
return $self->max_cost; |
33
|
|
|
|
|
|
|
}; |
34
|
|
|
|
|
|
|
} |
35
|
|
|
|
|
|
|
); |
36
|
|
|
|
|
|
|
has dimap => ( is => 'rw' ); |
37
|
|
|
|
|
|
|
has iters => ( is => 'rwp', default => sub { 0 } ); |
38
|
|
|
|
|
|
|
has next_m => ( is => 'rw', default => sub { 'next' } ); |
39
|
|
|
|
|
|
|
has normfn => ( is => 'rw', default => sub { \&norm_4way } ); |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
sub BUILD { |
42
|
9
|
|
|
9
|
0
|
47
|
my ( $self, $param ) = @_; |
43
|
|
|
|
|
|
|
croak "cannot have both map and str2map arguments" |
44
|
9
|
50
|
66
|
|
|
46
|
if exists $param->{'map'} and exists $param->{'str2map'}; |
45
|
|
|
|
|
|
|
$self->map( $param->{'map'} ) |
46
|
8
|
50
|
|
|
|
16
|
if exists $param->{'map'}; |
47
|
|
|
|
|
|
|
$self->map( $self->str2map( $param->{'str2map'} ) ) |
48
|
8
|
100
|
|
|
|
34
|
if exists $param->{'str2map'}; |
49
|
|
|
|
|
|
|
} |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
sub adjacent_values { |
52
|
5
|
|
|
5
|
1
|
4765
|
my ( $dimap, $r, $c, $maxrow, $maxcol ) = @_; |
53
|
5
|
|
|
|
|
8
|
my @values; |
54
|
5
|
|
|
|
|
12
|
for my $i ( -1, 1 ) { |
55
|
10
|
|
|
|
|
16
|
my $x = $c + $i; |
56
|
10
|
100
|
100
|
|
|
43
|
push @values, $dimap->[$r][$x] if $x >= 0 and $x <= $maxcol; |
57
|
10
|
|
|
|
|
19
|
for my $j ( -1 .. 1 ) { |
58
|
30
|
|
|
|
|
34
|
$x = $r + $i; |
59
|
30
|
|
|
|
|
38
|
my $y = $c + $j; |
60
|
30
|
100
|
100
|
|
|
122
|
push @values, $dimap->[$x][$y] |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
61
|
|
|
|
|
|
|
if $x >= 0 |
62
|
|
|
|
|
|
|
and $x <= $maxrow |
63
|
|
|
|
|
|
|
and $y >= 0 |
64
|
|
|
|
|
|
|
and $y <= $maxcol; |
65
|
|
|
|
|
|
|
} |
66
|
|
|
|
|
|
|
} |
67
|
5
|
|
|
|
|
20
|
return @values; |
68
|
|
|
|
|
|
|
} |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
sub adjacent_values_diag { |
71
|
50
|
|
|
50
|
1
|
2019
|
my ( $dimap, $r, $c, $maxrow, $maxcol ) = @_; |
72
|
50
|
|
|
|
|
61
|
my @values; |
73
|
50
|
100
|
100
|
|
|
149
|
push @values, $dimap->[ $r - 1 ][ $c - 1 ] if $r > 0 and $c > 0; |
74
|
50
|
100
|
100
|
|
|
135
|
push @values, $dimap->[ $r - 1 ][ $c + 1 ] if $r > 0 and $c < $maxcol; |
75
|
50
|
100
|
100
|
|
|
137
|
push @values, $dimap->[ $r + 1 ][ $c - 1 ] if $r < $maxrow and $c > 0; |
76
|
50
|
100
|
100
|
|
|
146
|
push @values, $dimap->[ $r + 1 ][ $c + 1 ] if $r < $maxrow and $c < $maxcol; |
77
|
50
|
|
|
|
|
190
|
return @values; |
78
|
|
|
|
|
|
|
} |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
sub adjacent_values_sq { |
81
|
230
|
|
|
230
|
1
|
2209
|
my ( $dimap, $r, $c, $maxrow, $maxcol ) = @_; |
82
|
230
|
|
|
|
|
275
|
my @values; |
83
|
230
|
100
|
|
|
|
529
|
push @values, $dimap->[$r][ $c - 1 ] if $c > 0; |
84
|
230
|
100
|
|
|
|
435
|
push @values, $dimap->[$r][ $c + 1 ] if $c < $maxcol; |
85
|
230
|
100
|
|
|
|
404
|
push @values, $dimap->[ $r - 1 ][$c] if $r > 0; |
86
|
230
|
100
|
|
|
|
396
|
push @values, $dimap->[ $r + 1 ][$c] if $r < $maxrow; |
87
|
230
|
|
|
|
|
517
|
return @values; |
88
|
|
|
|
|
|
|
} |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
sub dimap_with { |
91
|
2
|
|
|
2
|
1
|
50
|
my ( $self, $param ) = @_; |
92
|
2
|
|
|
|
|
7
|
my $dimap = $self->dimap; |
93
|
2
|
100
|
|
|
|
16
|
croak "cannot make new dimap from unset map" if !defined $dimap; |
94
|
1
|
|
|
|
|
1
|
my $new_dimap; |
95
|
1
|
|
|
|
|
4
|
my $badcost = $self->bad_cost; |
96
|
1
|
|
|
|
|
3
|
my $cols = $dimap->[0]->$#*; |
97
|
1
|
|
|
|
|
4
|
for my $r ( 0 .. $dimap->$#* ) { |
98
|
3
|
|
|
|
|
8
|
COL: for my $c ( 0 .. $cols ) { |
99
|
9
|
|
|
|
|
11
|
my $value = $dimap->[$r][$c]; |
100
|
9
|
50
|
|
|
|
19
|
if ( $value == $badcost ) { |
101
|
0
|
|
|
|
|
0
|
$new_dimap->[$r][$c] = $badcost; |
102
|
0
|
|
|
|
|
0
|
next COL; |
103
|
|
|
|
|
|
|
} |
104
|
9
|
|
50
|
|
|
15
|
$value *= $param->{my_weight} // 1; |
105
|
9
|
|
|
|
|
29
|
my @here = map $_->values( [ $r, $c ] )->[0], $param->{objs}->@*; |
106
|
9
|
|
|
|
|
19
|
for my $h ( 0 .. $#here ) { |
107
|
17
|
100
|
|
|
|
44
|
if ( $here[$h] == $badcost ) { |
108
|
1
|
|
|
|
|
2
|
$new_dimap->[$r][$c] = $badcost; |
109
|
1
|
|
|
|
|
4
|
next COL; |
110
|
|
|
|
|
|
|
} |
111
|
16
|
|
50
|
|
|
39
|
$value += $here[$h] * ( $param->{weights}->[$h] // 0 ); |
112
|
|
|
|
|
|
|
} |
113
|
8
|
|
|
|
|
21
|
$new_dimap->[$r][$c] = $value; |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
} |
116
|
1
|
|
|
|
|
17
|
return $new_dimap; |
117
|
|
|
|
|
|
|
} |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
sub map { |
120
|
8
|
|
|
8
|
1
|
8883
|
my ( $self, $map ) = @_; |
121
|
8
|
|
|
|
|
18
|
my $dimap = []; |
122
|
8
|
50
|
66
|
|
|
76
|
croak "no valid map supplied" |
|
|
|
66
|
|
|
|
|
|
|
|
66
|
|
|
|
|
123
|
|
|
|
|
|
|
if !defined $map |
124
|
|
|
|
|
|
|
or ref $map ne 'ARRAY' |
125
|
|
|
|
|
|
|
or !defined $map->[0] |
126
|
|
|
|
|
|
|
or ref $map->[0] ne 'ARRAY'; |
127
|
7
|
|
|
|
|
15
|
my $cols = $map->[0]->@*; |
128
|
7
|
|
|
|
|
23
|
for my $r ( 0 .. $map->$#* ) { |
129
|
21
|
50
|
|
|
|
45
|
croak "unexpected column count at row $r" if $map->[$r]->@* != $cols; |
130
|
21
|
|
|
|
|
36
|
for my $c ( 0 .. $cols - 1 ) { |
131
|
81
|
|
|
|
|
146
|
$dimap->[$r][$c] = $self->costfn->( $self, $map->[$r][$c] ); |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
} |
134
|
|
|
|
|
|
|
$self->_set_iters( |
135
|
7
|
|
|
|
|
22
|
$self->normfn->( $dimap, $self->min_cost, $self->max_cost ) ); |
136
|
7
|
|
|
|
|
27
|
$self->dimap($dimap); |
137
|
7
|
|
|
|
|
88
|
return $self; |
138
|
|
|
|
|
|
|
} |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
sub next { |
141
|
16
|
|
|
16
|
1
|
7199
|
my ( $self, $r, $c, $value ) = @_; |
142
|
16
|
|
|
|
|
33
|
my $dimap = $self->dimap; |
143
|
16
|
100
|
|
|
|
62
|
croak "cannot pathfind on unset map" if !defined $dimap; |
144
|
13
|
|
|
|
|
21
|
my $maxrow = $dimap->$#*; |
145
|
13
|
|
|
|
|
23
|
my $maxcol = $dimap->[0]->$#*; |
146
|
13
|
50
|
33
|
|
|
56
|
croak "row $r out of bounds" if $r > $maxrow or $r < 0; |
147
|
13
|
50
|
33
|
|
|
44
|
croak "col $c out of bounds" if $c > $maxcol or $c < 0; |
148
|
13
|
|
|
|
|
24
|
my @adj; |
149
|
13
|
|
100
|
|
|
45
|
$value //= $dimap->[$r][$c]; |
150
|
13
|
100
|
|
|
|
33
|
return \@adj if $value <= $self->min_cost; |
151
|
|
|
|
|
|
|
|
152
|
12
|
|
|
|
|
23
|
for my $i ( -1, 1 ) { |
153
|
24
|
|
|
|
|
38
|
my $x = $c + $i; |
154
|
24
|
100
|
66
|
|
|
89
|
push @adj, [ [ $r, $x ], $dimap->[$r][$x] ] if $x >= 0 and $x <= $maxcol; |
155
|
24
|
|
|
|
|
45
|
for my $j ( -1 .. 1 ) { |
156
|
72
|
|
|
|
|
93
|
$x = $r + $i; |
157
|
72
|
|
|
|
|
92
|
my $y = $c + $j; |
158
|
72
|
100
|
66
|
|
|
334
|
push @adj, [ [ $x, $y ], $dimap->[$x][$y] ] |
|
|
|
100
|
|
|
|
|
|
|
|
66
|
|
|
|
|
159
|
|
|
|
|
|
|
if $x >= 0 |
160
|
|
|
|
|
|
|
and $x <= $maxrow |
161
|
|
|
|
|
|
|
and $y >= 0 |
162
|
|
|
|
|
|
|
and $y <= $maxcol; |
163
|
|
|
|
|
|
|
} |
164
|
|
|
|
|
|
|
} |
165
|
12
|
|
|
|
|
26
|
my $badcost = $self->bad_cost; |
166
|
12
|
100
|
|
|
|
21
|
return [ grep { $_->[1] < $value and $_->[1] != $badcost } @adj ]; |
|
76
|
|
|
|
|
329
|
|
167
|
|
|
|
|
|
|
} |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
sub next_best { |
170
|
15
|
|
|
15
|
1
|
704
|
my ( $self, $r, $c ) = @_; |
171
|
15
|
|
|
|
|
35
|
my $method = $self->next_m; |
172
|
|
|
|
|
|
|
my @ret = |
173
|
15
|
|
|
|
|
37
|
sort { $a->[1] <=> $b->[1] } shuffle $self->$method( $r, $c )->@*; |
|
2
|
|
|
|
|
7
|
|
174
|
13
|
|
|
|
|
128
|
return $ret[0]->[0]; |
175
|
|
|
|
|
|
|
} |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
# next() but only in square directions or "orthogonal" (but diagonals |
178
|
|
|
|
|
|
|
# are orthogonal to one another) or in the "cardinal directions" (NSEW) |
179
|
|
|
|
|
|
|
# but that term also seems unsatisfactory. "4-way" is also used for this |
180
|
|
|
|
|
|
|
# with the assumption of cardinal directions |
181
|
|
|
|
|
|
|
sub next_sq { |
182
|
7
|
|
|
7
|
1
|
762
|
my ( $self, $r, $c, $value ) = @_; |
183
|
7
|
|
|
|
|
15
|
my $dimap = $self->dimap; |
184
|
7
|
100
|
|
|
|
22
|
croak "cannot pathfind on unset map" if !defined $dimap; |
185
|
6
|
|
|
|
|
10
|
my $maxrow = $dimap->$#*; |
186
|
6
|
|
|
|
|
12
|
my $maxcol = $dimap->[0]->$#*; |
187
|
6
|
50
|
33
|
|
|
23
|
croak "row $r out of bounds" if $r > $maxrow or $r < 0; |
188
|
6
|
50
|
33
|
|
|
18
|
croak "col $c out of bounds" if $c > $maxcol or $c < 0; |
189
|
6
|
|
|
|
|
9
|
my @adj; |
190
|
6
|
|
66
|
|
|
22
|
$value //= $dimap->[$r][$c]; |
191
|
6
|
100
|
|
|
|
16
|
return \@adj if $value <= $self->min_cost; |
192
|
|
|
|
|
|
|
|
193
|
5
|
50
|
|
|
|
10
|
if ( $c > 0 ) { |
194
|
5
|
|
|
|
|
15
|
push @adj, [ [ $r, $c - 1 ], $dimap->[$r][ $c - 1 ] ]; |
195
|
|
|
|
|
|
|
} |
196
|
5
|
50
|
|
|
|
12
|
if ( $c < $maxcol ) { |
197
|
5
|
|
|
|
|
13
|
push @adj, [ [ $r, $c + 1 ], $dimap->[$r][ $c + 1 ] ]; |
198
|
|
|
|
|
|
|
} |
199
|
5
|
50
|
|
|
|
9
|
if ( $r > 0 ) { |
200
|
5
|
|
|
|
|
12
|
push @adj, [ [ $r - 1, $c ], $dimap->[ $r - 1 ][$c] ]; |
201
|
|
|
|
|
|
|
} |
202
|
5
|
50
|
|
|
|
10
|
if ( $r < $maxrow ) { |
203
|
5
|
|
|
|
|
11
|
push @adj, [ [ $r + 1, $c ], $dimap->[ $r + 1 ][$c] ]; |
204
|
|
|
|
|
|
|
} |
205
|
|
|
|
|
|
|
|
206
|
5
|
|
|
|
|
12
|
my $badcost = $self->bad_cost; |
207
|
5
|
100
|
|
|
|
8
|
return [ grep { $_->[1] < $value and $_->[1] != $badcost } @adj ]; |
|
20
|
|
|
|
|
75
|
|
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
sub next_with { |
211
|
1
|
|
|
1
|
1
|
5
|
my ( $self, $r, $c, $param ) = @_; |
212
|
1
|
|
|
|
|
3
|
my $dimap = $self->dimap; |
213
|
1
|
50
|
|
|
|
5
|
croak "cannot pathfind on unset map" if !defined $dimap; |
214
|
|
|
|
|
|
|
|
215
|
1
|
|
|
|
|
3
|
my $badcost = $self->bad_cost; |
216
|
|
|
|
|
|
|
|
217
|
1
|
|
|
|
|
3
|
my $curcost = $dimap->[$r][$c]; |
218
|
1
|
50
|
|
|
|
4
|
return undef if $curcost <= $self->min_cost; |
219
|
1
|
|
50
|
|
|
4
|
$curcost *= $param->{my_weight} // 1; |
220
|
1
|
|
|
|
|
5
|
my @here = map $_->values( [ $r, $c ] )->[0], $param->{objs}->@*; |
221
|
1
|
|
|
|
|
4
|
for my $h ( 0 .. $#here ) { |
222
|
|
|
|
|
|
|
# this may cause problems if something is standing on a cell |
223
|
|
|
|
|
|
|
# they can no longer move into but where it is still legal for |
224
|
|
|
|
|
|
|
# them to leave that cell |
225
|
2
|
50
|
|
|
|
16
|
return undef if $here[$h] == $badcost; |
226
|
2
|
|
50
|
|
|
8
|
$curcost += $here[$h] * ( $param->{weights}->[$h] // 0 ); |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
|
229
|
1
|
|
|
|
|
5
|
my $method = $self->next_m; |
230
|
1
|
|
|
|
|
6
|
my $coords = $self->$method( $r, $c, $self->max_cost ); |
231
|
1
|
50
|
|
|
|
4
|
return undef unless $coords->@*; |
232
|
1
|
|
|
|
|
5
|
my @costs = map $_->values( map $_->[0], $coords->@* ), $param->{objs}->@*; |
233
|
1
|
|
|
|
|
2
|
my @ret; |
234
|
1
|
|
|
|
|
11
|
COORD: for my $p ( 0 .. $coords->$#* ) { |
235
|
3
|
|
|
|
|
5
|
my @weights; |
236
|
3
|
|
|
|
|
5
|
for my $k ( 0 .. $#costs ) { |
237
|
5
|
100
|
|
|
|
14
|
next COORD if $costs[$k][$p] == $badcost; |
238
|
4
|
|
50
|
|
|
11
|
push @weights, $costs[$k][$p] * ( $param->{weights}->[$k] // 0 ); |
239
|
|
|
|
|
|
|
} |
240
|
2
|
|
50
|
|
|
11
|
my $newcost = sum0 $coords->[$p][1] * ( $param->{my_weight} // 1 ), @weights; |
241
|
2
|
100
|
|
|
|
8
|
push @ret, [ $coords->[$p][0], $newcost ] if $newcost < $curcost; |
242
|
|
|
|
|
|
|
} |
243
|
|
|
|
|
|
|
|
244
|
1
|
50
|
|
|
|
3
|
return undef unless @ret; |
245
|
1
|
|
|
|
|
4
|
@ret = sort { $a->[1] <=> $b->[1] } shuffle @ret; |
|
0
|
|
|
|
|
0
|
|
246
|
1
|
|
|
|
|
7
|
return $ret[0]->[0]; |
247
|
|
|
|
|
|
|
} |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
# 4-way "square" normalization as seen in the Brogue article (was called |
250
|
|
|
|
|
|
|
# normalize_costs and used to be a method). one could possibly also |
251
|
|
|
|
|
|
|
# normalize only in the diagonal directions... |
252
|
|
|
|
|
|
|
sub norm_4way { |
253
|
7
|
|
|
7
|
1
|
16
|
my ( $dimap, $mincost, $maxcost, $avfn ) = @_; |
254
|
7
|
|
100
|
|
|
32
|
$avfn //= \&adjacent_values_sq; |
255
|
7
|
|
|
|
|
12
|
my $iters = 0; |
256
|
7
|
|
|
|
|
12
|
my $maxrow = $dimap->$#*; |
257
|
7
|
|
|
|
|
11
|
my $maxcol = $dimap->[0]->$#*; |
258
|
7
|
|
|
|
|
9
|
my $stable; |
259
|
7
|
|
|
|
|
11
|
while (1) { |
260
|
21
|
|
|
|
|
35
|
$stable = 1; |
261
|
21
|
|
|
|
|
30
|
$iters++; |
262
|
21
|
|
|
|
|
34
|
for my $r ( 0 .. $maxrow ) { |
263
|
77
|
|
|
|
|
125
|
for my $c ( 0 .. $maxcol ) { |
264
|
403
|
|
|
|
|
529
|
my $value = $dimap->[$r][$c]; |
265
|
403
|
100
|
|
|
|
640
|
next if $value <= $mincost; |
266
|
182
|
|
|
|
|
217
|
my $best = $maxcost; |
267
|
182
|
|
|
|
|
263
|
for my $nv ( $avfn->( $dimap, $r, $c, $maxrow, $maxcol ) ) { |
268
|
532
|
100
|
100
|
|
|
1284
|
$best = $nv if $nv < $best and $nv >= $mincost; |
269
|
532
|
100
|
|
|
|
946
|
last if $best == $mincost; |
270
|
|
|
|
|
|
|
} |
271
|
182
|
100
|
|
|
|
340
|
if ( $value > $best + 2 ) { |
272
|
34
|
|
|
|
|
53
|
$dimap->[$r][$c] = $best + 1; |
273
|
34
|
|
|
|
|
43
|
$stable = 0; |
274
|
|
|
|
|
|
|
} |
275
|
|
|
|
|
|
|
} |
276
|
|
|
|
|
|
|
} |
277
|
21
|
100
|
|
|
|
39
|
last if $stable; |
278
|
|
|
|
|
|
|
} |
279
|
7
|
|
|
|
|
48
|
return $iters; |
280
|
|
|
|
|
|
|
} |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
# 8-way normalization could either be done with small integers where |
283
|
|
|
|
|
|
|
# diagonals cost the same as square motion (this is non-Euclidean though |
284
|
|
|
|
|
|
|
# traditional in roguelikes) ... |
285
|
|
|
|
|
|
|
sub norm_8way { |
286
|
1
|
|
|
1
|
1
|
4
|
push @_, \&adjacent_values; |
287
|
1
|
|
|
|
|
12
|
&norm_4way; # perldoc perlsub explains this calling form |
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
# ... or one could instead use floating point values to better |
291
|
|
|
|
|
|
|
# approximate diagonals costing sqrt(2) but this is more complicated, |
292
|
|
|
|
|
|
|
# which is perhaps why many roguelikes use 4-way or non-Euclidean 8-way |
293
|
|
|
|
|
|
|
sub norm_8way_euclid { |
294
|
2
|
|
|
2
|
1
|
5
|
my ( $dimap, $mincost, $maxcost ) = @_; |
295
|
2
|
|
|
|
|
5
|
my $iters = 0; |
296
|
2
|
|
|
|
|
3
|
my $maxrow = $dimap->$#*; |
297
|
2
|
|
|
|
|
4
|
my $maxcol = $dimap->[0]->$#*; |
298
|
2
|
|
|
|
|
4
|
my $stable; |
299
|
2
|
|
|
|
|
3
|
while (1) { |
300
|
5
|
|
|
|
|
8
|
$stable = 1; |
301
|
5
|
|
|
|
|
6
|
$iters++; |
302
|
5
|
|
|
|
|
11
|
for my $r ( 0 .. $maxrow ) { |
303
|
16
|
|
|
|
|
25
|
for my $c ( 0 .. $maxcol ) { |
304
|
56
|
|
|
|
|
80
|
my $value = $dimap->[$r][$c]; |
305
|
56
|
100
|
|
|
|
101
|
next if $value <= $mincost; |
306
|
47
|
|
|
|
|
76
|
my $best = [ $maxcost, 0 ]; |
307
|
47
|
|
|
|
|
78
|
for my $nr ( |
308
|
|
|
|
|
|
|
map( [ $_, 1 ], adjacent_values_sq( $dimap, $r, $c, $maxrow, $maxcol ) ), |
309
|
|
|
|
|
|
|
map( [ $_, SQRT2 ], adjacent_values_diag( $dimap, $r, $c, $maxrow, $maxcol ) ) |
310
|
|
|
|
|
|
|
) { |
311
|
186
|
100
|
100
|
|
|
443
|
$best = $nr if $nr->[0] < $best->[0] and $nr->[0] >= $mincost; |
312
|
186
|
100
|
|
|
|
333
|
last if $best->[0] == $mincost; |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
# TODO should this be + 2 like the others or is + SQRT2 |
315
|
|
|
|
|
|
|
# a better check? |
316
|
47
|
100
|
|
|
|
182
|
if ( $value > $best->[0] + SQRT2 ) { |
317
|
17
|
|
|
|
|
29
|
$dimap->[$r][$c] = $best->[0] + $best->[1]; |
318
|
17
|
|
|
|
|
33
|
$stable = 0; |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
} |
321
|
|
|
|
|
|
|
} |
322
|
5
|
100
|
|
|
|
9
|
last if $stable; |
323
|
|
|
|
|
|
|
} |
324
|
2
|
|
|
|
|
7
|
return $iters; |
325
|
|
|
|
|
|
|
} |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
sub path_best { |
328
|
3
|
|
|
3
|
1
|
715
|
my ( $self, $r, $c, $method ) = @_; |
329
|
3
|
|
|
|
|
6
|
my @path; |
330
|
3
|
|
|
|
|
9
|
while ( my $next = $self->next_best( $r, $c, $method ) ) { |
331
|
7
|
|
|
|
|
13
|
push @path, $next; |
332
|
7
|
|
|
|
|
17
|
( $r, $c ) = @$next; |
333
|
|
|
|
|
|
|
} |
334
|
2
|
|
|
|
|
24
|
return \@path; |
335
|
|
|
|
|
|
|
} |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
sub recalc { |
338
|
3
|
|
|
3
|
1
|
2464
|
my ($self) = @_; |
339
|
3
|
|
|
|
|
9
|
my $dimap = $self->dimap; |
340
|
3
|
100
|
|
|
|
16
|
croak "cannot recalc unset map" if !defined $dimap; |
341
|
2
|
|
|
|
|
6
|
my $maxcost = $self->max_cost; |
342
|
2
|
|
|
|
|
4
|
my $mincost = $self->min_cost; |
343
|
2
|
|
|
|
|
5
|
my $maxcol = $dimap->[0]->$#*; |
344
|
2
|
|
|
|
|
6
|
for my $r ( 0 .. $dimap->$#* ) { |
345
|
7
|
|
|
|
|
14
|
for my $c ( 0 .. $maxcol ) { |
346
|
39
|
100
|
|
|
|
70
|
$dimap->[$r][$c] = $maxcost if $dimap->[$r][$c] > $mincost; |
347
|
|
|
|
|
|
|
} |
348
|
|
|
|
|
|
|
} |
349
|
2
|
|
|
|
|
6
|
$self->_set_iters( $self->normfn->( $dimap, $mincost, $maxcost ) ); |
350
|
2
|
|
|
|
|
16
|
$self->dimap($dimap); |
351
|
2
|
|
|
|
|
8
|
return $self; |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
sub str2map { |
355
|
6
|
|
|
6
|
1
|
862
|
my ( $self_or_class, $str, $lf ) = @_; |
356
|
6
|
50
|
|
|
|
16
|
croak "no string given" if !defined $str; |
357
|
6
|
|
33
|
|
|
32
|
$lf //= $/; |
358
|
6
|
|
|
|
|
9
|
my @map; |
359
|
6
|
|
|
|
|
85
|
for my $line ( split $lf, $str ) { |
360
|
18
|
|
|
|
|
78
|
push @map, [ split //, $line ]; |
361
|
|
|
|
|
|
|
} |
362
|
6
|
|
|
|
|
24
|
return \@map; |
363
|
|
|
|
|
|
|
} |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
sub to_tsv { |
366
|
3
|
|
|
3
|
1
|
1561
|
my ( $self, $ref ) = @_; |
367
|
3
|
100
|
|
|
|
10
|
if ( !defined $ref ) { |
368
|
2
|
|
|
|
|
7
|
$ref = $self->dimap; |
369
|
2
|
100
|
|
|
|
12
|
croak "cannot use an unset map" if !defined $ref; |
370
|
|
|
|
|
|
|
} |
371
|
2
|
|
|
|
|
5
|
my $s = ''; |
372
|
2
|
|
|
|
|
4
|
my $cols = $ref->[0]->$#*; |
373
|
2
|
|
|
|
|
7
|
for my $r ( 0 .. $ref->$#* ) { |
374
|
5
|
|
|
|
|
9
|
my $d = "\t"; |
375
|
5
|
|
|
|
|
9
|
for my $c ( 0 .. $cols ) { |
376
|
13
|
|
|
|
|
24
|
$s .= $ref->[$r][$c] . $d; |
377
|
13
|
100
|
|
|
|
29
|
$d = '' if $c == $cols - 1; |
378
|
|
|
|
|
|
|
} |
379
|
5
|
|
|
|
|
10
|
$s .= $/; |
380
|
|
|
|
|
|
|
} |
381
|
2
|
|
|
|
|
16
|
return $s; |
382
|
|
|
|
|
|
|
} |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
sub unconnected { |
385
|
3
|
|
|
3
|
1
|
975
|
my ($self) = @_; |
386
|
3
|
|
|
|
|
9
|
my $dimap = $self->dimap; |
387
|
3
|
100
|
|
|
|
17
|
croak "nothing unconnected on unset map" if !defined $dimap; |
388
|
2
|
|
|
|
|
3
|
my @points; |
389
|
2
|
|
|
|
|
5
|
my $maxcost = $self->max_cost; |
390
|
2
|
|
|
|
|
5
|
my $maxcol = $dimap->[0]->$#*; |
391
|
2
|
|
|
|
|
7
|
for my $r ( 0 .. $dimap->$#* ) { |
392
|
7
|
|
|
|
|
12
|
for my $c ( 0 .. $maxcol ) { |
393
|
39
|
100
|
|
|
|
75
|
push @points, [ $r, $c ] if $dimap->[$r][$c] == $maxcost; |
394
|
|
|
|
|
|
|
} |
395
|
|
|
|
|
|
|
} |
396
|
2
|
|
|
|
|
17
|
return \@points; |
397
|
|
|
|
|
|
|
} |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
sub update { |
400
|
2
|
|
|
2
|
1
|
710
|
my $self = shift; |
401
|
2
|
|
|
|
|
7
|
my $dimap = $self->dimap; |
402
|
2
|
100
|
|
|
|
15
|
croak "cannot update unset map" if !defined $dimap; |
403
|
1
|
|
|
|
|
2
|
my $maxrow = $dimap->$#*; |
404
|
1
|
|
|
|
|
3
|
my $maxcol = $dimap->[0]->$#*; |
405
|
1
|
|
|
|
|
3
|
for my $ref (@_) { |
406
|
1
|
|
|
|
|
3
|
my ( $r, $c ) = ( $ref->[0], $ref->[1] ); |
407
|
1
|
50
|
33
|
|
|
8
|
croak "row $r out of bounds" if $r > $maxrow or $r < 0; |
408
|
1
|
50
|
33
|
|
|
9
|
croak "col $c out of bounds" if $c > $maxcol or $c < 0; |
409
|
1
|
50
|
|
|
|
23
|
croak "value must be a number" unless looks_like_number $ref->[2]; |
410
|
1
|
|
|
|
|
5
|
$dimap->[$r][$c] = int $ref->[2]; |
411
|
|
|
|
|
|
|
} |
412
|
1
|
|
|
|
|
5
|
$self->dimap($dimap); |
413
|
1
|
|
|
|
|
3
|
return $self; |
414
|
|
|
|
|
|
|
} |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
sub values { |
417
|
24
|
|
|
24
|
1
|
692
|
my $self = shift; |
418
|
24
|
|
|
|
|
46
|
my $dimap = $self->dimap; |
419
|
24
|
100
|
|
|
|
53
|
croak "cannot get values from unset map" if !defined $dimap; |
420
|
23
|
|
|
|
|
32
|
my @values; |
421
|
23
|
|
|
|
|
32
|
my $maxrow = $dimap->$#*; |
422
|
23
|
|
|
|
|
34
|
my $maxcol = $dimap->[0]->$#*; |
423
|
23
|
|
|
|
|
39
|
for my $point (@_) { |
424
|
29
|
|
|
|
|
51
|
my ( $r, $c ) = ( $point->[0], $point->[1] ); |
425
|
29
|
50
|
33
|
|
|
101
|
croak "row $r out of bounds" if $r > $maxrow or $r < 0; |
426
|
29
|
50
|
33
|
|
|
105
|
croak "col $c out of bounds" if $c > $maxcol or $c < 0; |
427
|
29
|
|
|
|
|
63
|
push @values, $dimap->[$r][$c]; |
428
|
|
|
|
|
|
|
} |
429
|
23
|
|
|
|
|
114
|
return \@values; |
430
|
|
|
|
|
|
|
} |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
1; |
433
|
|
|
|
|
|
|
__END__ |