File Coverage

blib/lib/Games/RolePlay/MapGen/Generator/Basic.pm
Criterion Covered Total %
statement 188 188 100.0
branch 78 86 90.7
condition 39 42 92.8
subroutine 9 9 100.0
pod 0 5 0.0
total 314 330 95.1


line stmt bran cond sub pod time code
1             # vi:tw=0 syntax=perl:
2              
3             package Games::RolePlay::MapGen::Generator::Basic;
4              
5 7     7   579 use common::sense;
  7         13  
  7         632  
6 7     7   389 use Carp;
  7         13  
  7         2458  
7 7     7   7816 use parent q(Games::RolePlay::MapGen::Generator::SparseAndLoops);
  7         2493  
  7         37  
8 7     7   582 use Games::RolePlay::MapGen::Tools qw( choice roll _group irange str_eval );
  7         15  
  7         27214  
9              
10             1;
11              
12             # gen_room_size {{{
13             sub gen_room_size {
14 32     32 0 64 my $this = shift;
15 32         74 my $opts = shift;
16            
17 32         404 my ($xm, $ym) = split /x/, $opts->{min_room_size};
18 32         151 my ($xM, $yM) = split /x/, $opts->{max_room_size};
19              
20 32         152 my $x = irange($xm, $xM);
21 32         114 my $y = irange($ym, $yM);
22              
23 32 50 33     257 die "ERROR: problem generating room size from '$opts->{min_room_size} - $opts->{max_room_size}'" unless $x>0 and $y>0;
24              
25 32         137 return ($x, $y);
26             }
27             # }}}
28              
29             # mark_things_as_pseudo_rooms {{{
30             sub mark_things_as_pseudo_rooms {
31 7     7 0 18 my $this = shift;
32 7         17 my $map = shift;
33 7         16 my $groups = shift;
34              
35             # This function assumes all defined {type}s are corridoors and that all {od} are un-broken.
36              
37 7         65 for my $tile ( map(@$_, @$map) ) {
38 3175 100       8622 if( $tile->{type} ) {
39              
40             # Look for those 2x2 corridor-loops starting with familiar upper left corner tiles.
41 1022 100 100     4804 if( $tile->{od}{e} and $tile->{od}{s} ) {
42 106         149 my $ul = $tile;
43 106         268 my $ur = $tile->{nb}{e};
44 106         212 my $ll = $tile->{nb}{s};
45              
46 106 100 100     2443 if( $ur->{od}{s} and $ll->{od}{e} ) {
    100 100        
    100 100        
47 10         106 my $lr = $ur->{nb}{s};
48              
49 10         51 my $group = &_group;
50 10         46 $group->type("pseudo");
51 10         196 $group->add_rectangle([$ul->{x}, $ul->{y}], [2,2]);
52              
53 10         27 for( $ul, $ur, $ll, $lr) {
54             # $_->{DEBUG_red_mark} = 1;
55 40         71 $_->{group} = $group;
56 40         107 $_->{type} = $group->{type};
57             }
58              
59             } elsif( (my $um = $ur)->{od}{e} and $ll->{od}{e} ) {
60 19         58 $ur = $um->{nb}{e};
61 19         59 my $lm = $ll->{nb}{e};
62              
63 19 100 100     176 if( $lm->{od}{e} and $ur->{od}{s} ) {
64 8         19 my $lr = $lm->{nb}{e};
65              
66 8         34 my $group = &_group;
67 8         38 $group->type("pseudo");
68 8         69 $group->add_rectangle([$ul->{x}, $ul->{y}], [3,2]);
69              
70 8         22 for( $ul, $um, $ur,
71             $ll, $lm, $lr ) {
72              
73             # $_->{DEBUG_blue_mark} = 1;
74 48         1201 $_->{group} = $group;
75 48         110 $_->{type} = $group->{type};
76             }
77              
78             }
79              
80             } elsif( (my $ml = $ll)->{od}{s} and $ur->{od}{s} ) {
81 10         29 $ll = $ml->{nb}{s};
82 10         26 my $mr = $ur->{nb}{s};
83              
84 10 100 100     69 if( $ll->{od}{e} and $mr->{od}{s} ) {
85 2         5 my $lr = $ll->{nb}{e};
86              
87 2         8 my $group = &_group;
88 2         94 $group->type("pseudo");
89 2         13 $group->add_rectangle([$ul->{x}, $ul->{y}], [2,3]);
90              
91 2         4 for( $ul, $ur,
92             $ml, $mr,
93             $ll, $lr ) {
94              
95             # $_->{DEBUG_blue_mark} = 1;
96 12         20 $_->{group} = $group;
97 12         25 $_->{type} = $group->{type};
98             }
99              
100             }
101             }
102             }
103             }
104             }
105              
106 7         368 push @$groups, map($_->{group}, grep {$_->{group}} map(@$_, @$map));
  3175         5701  
107 7         206 for my $g (@$groups) {
108 97         311 $g->{lsize} = $g->enumerate_tiles;
109             }
110             }
111             # }}}
112             # drop_rooms {{{
113             sub drop_rooms {
114 7     7 0 33 my $this = shift;
115 7         17 my $opts = shift;
116 7         14 my $map = shift;
117 7         15 my $groups = shift;
118              
119 7         25 $opts->{y_size} = $#$map;
120 7         13 $opts->{x_size} = $#{ $map->[0] };
  7         44  
121              
122 7         79 for my $rn (1 .. str_eval( $opts->{num_rooms} )) {
123 32         462 my @size = $this->gen_room_size( $opts );
124 32 50       141 $size[0] = $opts->{x_size} if $size[0] > $opts->{x_size};
125 32 50       142 $size[1] = $opts->{y_size} if $size[1] > $opts->{y_size};
126              
127 32         66 my @possible_locs = (); # [ $j, $i, $score ]
128 32         49 my $lowest_score = undef;
129              
130 32 50       134 $opts->{t_cb}->() if exists $opts->{t_cb};
131              
132 32         101 for my $i (0 .. $#$map - $size[1]) {
133 453         670 my $jend = $#{ $map->[$i] };
  453         1902  
134              
135 453         3407 for my $j (0 .. $jend - $size[0]) {
136              
137 7151         28065 my $score = 0;
138 7151         8474 my $pseudo = 0;
139 7151         20145 for my $x (0 .. $size[0]-1) {
140 26293         76499 for my $y (0 .. $size[1]-1) {
141 122495         594646 my $tile = $map->[$i+$y][$j+$x];
142              
143 122495 100       981341 if( exists $tile->{type} ) {
144 43519 100       159396 goto LONGJUMP_PAST_SCORING if $tile->{type} eq "room";
145              
146 41239 100       119221 if( $tile->{type} eq "corridor" ) {
    50          
147 38944         111746 $score += 1.07;
148              
149             } elsif( $tile->{type} eq "pseudo" ) {
150 2295         5789 $tile->{group}{_rd_all} ++;
151              
152 2295         6367 $pseudo = 1;
153             }
154             }
155             }
156             }
157              
158 4871 100       12307 if( $pseudo ) {
159 679         1592 for my $g (grep { $_->{type} eq "pseudo" } @$groups) {
  12077         24272  
160 11614 100       76114 if( exists $g->{_rd_all} ) {
161              
162 746 100 100     4600 if( $g->{_rd_all} == $g->{lsize} ) {
    100          
163 217 100       1049 $score += ( $g->{lsize} == 6 ? 1.01 : 1.03 );
164              
165             } elsif( $g->{lsize} == 6 and $g->{_rd_all} == 4 ) {
166 55         92 $score += 1.05;
167              
168             } else {
169 474         903 $score += 1.07 * 4;
170              
171             }
172              
173 746         1858 delete $g->{_rd_all};
174 746         1275 $pseudo = 0;
175             }
176             }
177             }
178              
179 4871 100       19343 if( $score > 0 ) {
180 4328 100 100     23220 if( not defined $lowest_score or $score < $lowest_score ) {
181 125         178 $lowest_score = $score;
182 125         267 @possible_locs = grep { $_->[2] <= $lowest_score } @possible_locs;
  278         1080  
183             }
184              
185 4328 100       10770 push @possible_locs, [ $j, $i, $score ] if $score <= $lowest_score;
186             }
187              
188             LONGJUMP_PAST_SCORING:
189             # This is a way of short-cutting much looping when we're not
190             # going to be putting the room there anyway.
191 7151         13049 }
192             }
193              
194 32 100       218 if( my $loc = choice( @possible_locs ) ) {
195 30         104 my @corridors = ();
196              
197 30         98 pop @$loc; # ditch the score.
198              
199 30         140 my $group = &_group;
200 30         282 $group->name( "Room #$rn" );
201 30         133 $group->type( "room" );
202 30         255 $group->add_rectangle( [@$loc], [@size] );
203              
204 30         169 my @tiles = $group->enumerate_tiles;
205 30         190 my ($xmin, $ymin, $xmax, $ymax) = $group->enumerate_extents;
206              
207 30         106 for my $tl ( @tiles ) {
208 696         1314 my ($x,$y) = @$tl;
209 696         7641 my $tile = $map->[ $y ][ $x ];
210              
211 696 100       4650 if( exists $tile->{type} ) {
212 119 50 66     626 if( $tile->{type} eq "corridor" or $tile->{type} eq "pseudo" ) {
213 119 100 100     475 push @corridors, [ w => $tile ] if $x == $xmin and $tile->{od}{w};
214 119 100 100     449 push @corridors, [ n => $tile ] if $y == $ymin and $tile->{od}{n};
215 119 100 100     561 push @corridors, [ e => $tile ] if $x == $xmax and $tile->{od}{e};
216 119 100 100     572 push @corridors, [ s => $tile ] if $y == $ymax and $tile->{od}{s};
217             }
218             }
219              
220 696         1357 $tile->{type} = "room";
221 696         1126 $tile->{group} = $group;
222 696         1174 for my $dir (qw(n e s w)) {
223 2784         4503 $tile->{od}{$dir} = 1; # open every direction... close edges below
224              
225 2784 100       7804 if( my $n = $tile->{nb}{$dir} ) {
226 2735         8127 $n->{od}{$Games::RolePlay::MapGen::opp{$dir}} = 1;
227             }
228             }
229             # $tile->{DEBUG_green_mark} = 1;
230             }
231              
232 30         95 for my $y ($ymin .. $ymax) {
233 151         554 (my $west = $map->[$y][ $xmin ])->{od}{w} = 0;
234 151         1323 (my $east = $map->[$y][ $xmax ])->{od}{e} = 0;
235              
236 151 100       991 if( my $west_n = $west->{nb}{w} ) {
237 130         254 $west_n->{od}{e} = 0;
238             }
239              
240 151 50       497 if( my $east_n = $east->{nb}{e} ) {
241 151         533 $east_n->{od}{w} = 0;
242             }
243             }
244              
245 30         89 for my $x ($xmin .. $xmax) {
246 141         520 (my $north = $map->[ $ymin ][$x])->{od}{n} = 0;
247 141         1003 (my $south = $map->[ $ymax ][$x])->{od}{s} = 0;
248              
249 141 100       985 if( my $north_n = $north->{nb}{n} ) {
250 113         223 $north_n->{od}{s} = 0;
251             }
252              
253 141 50       485 if( my $south_n = $south->{nb}{s} ) {
254 141         349 $south_n->{od}{n} = 0;
255             }
256             }
257              
258             # By default, tiles will be open between the new rooms and the corridors they stomped
259 30         77 for my $a (@corridors) {
260 57         94 my $t = $a->[1];
261 57         120 my $n = $t->{nb}{$a->[0]};
262              
263 57         227 $t->{od}{$a->[0]} = $n->{od}{$Games::RolePlay::MapGen::opp{$a->[0]}} = 1;
264             }
265              
266 30         500 push @$groups, $group;
267             }
268              
269             }
270             }
271             # }}}
272             # cleanup_pseudo_rooms {{{
273             sub cleanup_pseudo_rooms {
274 7     7 0 20 my $this = shift;
275 7         17 my $map = shift;
276 7         29 my $groups = shift;
277 7         38 my @pseudo = ();
278              
279 7 100       24 @$groups = grep { my $r = 1; if( $_->{type} eq "pseudo" ) { push @pseudo, $_; $r = 0 } $r } @$groups;
  127         145  
  127         322  
  97         147  
  97         130  
  127         211  
280              
281 7         27 for my $group (@pseudo) {
282              
283 97         129 my $intact = 1;
284 97         131 my @tofix = ();
285 97         276 for my $tl ($group->enumerate_tiles) {
286 502         1915 my $tile = $map->[$tl->[1]][$tl->[0]];
287              
288             # $tile->{DEBUG_red_mark} = 1;
289              
290 502 100       2650 if( $tile->{type} eq "pseudo" ) {
291 49         85 $tile->{type} = "corridor";
292 49         107 push @tofix, $tile;
293              
294             } else {
295 453         787 $intact = 0;
296             }
297             }
298              
299 97         523 my ($xmin, $ymin, $xmax, $ymax) = $group->enumerate_extents;
300 97 100       320 if( $intact ) {
301 8         18 for my $tile (@tofix) {
302 38 100       119 $tile->{od}{n} = 1 if $tile->{y} > $ymin;
303 38 100       115 $tile->{od}{s} = 1 if $tile->{y} < $ymax;
304 38 100       101 $tile->{od}{e} = 1 if $tile->{x} < $xmax;
305 38 100       123 $tile->{od}{w} = 1 if $tile->{x} > $xmin;
306             }
307             }
308             }
309              
310 7         28 for my $tile (grep {$_->{group}} map {@$_} @$map) {
  3175         5013  
  145         2201  
311 745 100       1927 delete $tile->{group}
312             if $tile->{group}{type} eq "pseudo";
313             }
314             }
315             # }}}
316              
317             # genmap {{{
318             sub genmap {
319 7     7 0 13 my $this = shift;
320 7         11 my $opts = $this->gen_opts(%{ $_[0] });
  7         74  
321 7         89 my ($map, $groups) = $this->SUPER::genmap(@_);
322              
323             # There are a few types of random corridors that look enough like rooms
324             # That I didn't think they should count against the room drop score below.
325             # In fact, we'd really rather cover them up if possible.
326 7         61 $this->mark_things_as_pseudo_rooms( $map, $groups );
327 7         52 $this->drop_rooms( $opts, $map, $groups );
328 7         50 $this->cleanup_pseudo_rooms( $map, $groups );
329              
330 7         246 return ($map, $groups);
331             }
332             # }}}
333              
334             __END__