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