File Coverage

blib/lib/Games/Roguelike/Area.pm
Criterion Covered Total %
statement 505 853 59.2
branch 182 414 43.9
condition 82 207 39.6
subroutine 44 70 62.8
pod 31 49 63.2
total 844 1593 52.9


line stmt bran cond sub pod time code
1             package Games::Roguelike::Area;
2              
3             # puposely don't use much of the curses windowing stuff since curses doesn't port well
4             # purpose of library:
5             #
6             # keep track of map/location
7             # convenience for collision, line of sight, path-finding
8             # assume some roguelike concepts (mobs/items)
9             # allow me to make 7-day rl's in 7-days
10              
11             =head1 NAME
12              
13             Games::Roguelike::Area - Roguelike area map
14              
15             =head1 SYNOPSIS
16              
17             package myArea;
18             use base 'Games::Roguelike::Area';
19              
20             $a = myArea->new(w=>80,h=>50); # creates an area with specified width/height
21             $a->generate('cavelike'); # make a cavelike maze
22             $char = Games::Roguelike::Mob->new($a, sym=>'@'); # add a mobile object with symbol '@'
23              
24             =head1 DESCRIPTION
25              
26             Library for loading or generating mazes, managing items/mobs
27              
28             * assumes the user will be using overridden Games::Roguelike::Mob's as characters in the game
29             * provides a flexible load() function
30             * contains an array of 'features', which can be named, searched for and positioned
31              
32             =head2 METHODS
33              
34             =over 4
35              
36             =cut
37              
38 5     5   27 use strict;
  5         9  
  5         6542  
39 5     5   967 use Games::Roguelike::Utils qw(:all);
  5         10  
  5         2098  
40 5     5   8553 use Games::Roguelike::Mob;
  5         19  
  5         199  
41              
42 5     5   1285 use Data::Dumper;
  5         13169  
  5         359  
43 5     5   34 use Carp qw(croak confess carp);
  5         9  
  5         539  
44              
45             our $OKINLINEPOV;
46             our $AUTOLOAD;
47              
48             our $VERSION = '0.5.' . [qw$Revision: 259 $]->[1];
49              
50             BEGIN {
51 5     5   448 eval('use Games::Roguelike::Utils::Pov_C;');
  5     5   4164  
  0         0  
  0         0  
52 5         26982 $OKINLINEPOV = !$@;
53             }
54              
55             =item new(OPT1=>VAL1, OPT2=>VAL2...)
56            
57             Options can also all be set/get as class accessors:
58              
59             world => undef, # world this area belongs to (optional container which can "addarea")
60             name => '', # name of this level/area (required if world is specified)
61             map => [] # double-indexed array of map symbols
62             color => [] # double-indexed array of strings (used to color map symbols)
63             mobs => [], # list of mobs
64             items => [], # list of items
65              
66             # These will default to the world defaults, if world is set
67              
68             w=>80, h=>40, # width/height of this area
69             debugmap => 0, # turn on map coordinate display
70             memcolor => 'gray', # color drawn when an area is not in sight
71              
72             # These vars, or the world defaults (if a world is defined)
73             # are used by map-making, pathfinding and field-of view, rather than using hooks
74             # specifically because function calling seems to slow things down
75              
76             wsym => '#', # default wall symbol
77             fsym => '.', # default floor symbol
78             dsym => '+', # default door symbol
79             noview => '#+', # list of symbols that block view
80             nomove => '#', # list of symbols that block movement
81            
82             =cut
83              
84             sub new {
85 16     16 1 6082 my $pkg = shift;
86 16 50       1342 croak "usage: Games::Roguelike::Area->new()" unless $pkg;
87              
88 16         67 my $self = bless {}, $pkg;
89 16         148 $self->init(@_);
90 16         107 return $self;
91             }
92              
93             sub init {
94 16     16 0 29 my $self = shift;
95 16         54 my %opts = @_;
96              
97 16 50 33     84 croak("need to specify a name for this area") if $opts{world} && !$opts{name};
98              
99             # set defaults
100              
101 16         90 $self->{map} = [];
102 16         42 $self->{color} = [];
103 16         40 $self->{mobs} = [];
104 16         53 $self->{items} = [];
105              
106 16 50       56 if (!$opts{world}) {
107 16         39 $self->{h} = 40;
108 16         28 $self->{w} = 80;
109 16         51 $self->{wsym} = '#';
110 16         61 $self->{fsym} = '.';
111 16         37 $self->{dsym} = '+';
112 16         35 $self->{debugmap} = 0;
113             } else {
114             # really, should create functions for each one which reads from world member
115 0         0 for (qw(h w noview nomove wsym fsym dsym debugmap memcolor)) {
116 0         0 $self->{$_} = $opts{world}->$_;
117             }
118             }
119              
120             # override defaults
121 16         51 for (keys(%opts)) {
122 16         61 $self->{$_} = $opts{$_};
123             }
124            
125 16 50       81 $self->{memcolor} = 'gray' unless defined $self->{memcolor};
126 16 50       58 $self->{nomove} = $self->{wsym} unless $self->{nomove};
127 16 50       82 $self->{noview} = $self->{wsym}.$self->{dsym} unless $self->{noview};
128 16 50       68 if ($self->{world}) {
129 0         0 $self->{world}->addarea($self);
130 0 0       0 $self->{world}->area($self) if !$self->{world}->area;
131             }
132             }
133              
134             sub setworld {
135 0     0 0 0 my $self = shift;
136 0         0 my $world = shift;
137              
138 0 0       0 croak("need to specify a name for this area") if !$self->{name};
139              
140 0 0       0 if ($self->{world} != $world) {
141 0 0       0 $self->{world}->delarea($self) if ($self->{world});
142 0         0 $self->{world} = $world;
143 0         0 $world->addarea($self);
144 0         0 for (qw(h w noview nomove wsym fsym dsym debugmap)) {
145 0 0       0 $self->{$_} = $self->{world}->$_ if !defined($self->{$_});
146             }
147             }
148             }
149              
150             # perl accessors are slow compared to just accessing the hash directly
151             # autoload is even slower
152             sub AUTOLOAD {
153 0     0   0 my $self = shift;
154 0 0       0 my $pkg = ref($self) or croak "$self is not an object";
155              
156 0         0 my $name = $AUTOLOAD;
157 0         0 $name =~ s/.*://; # strip fully-qualified portion
158              
159 0 0 0     0 $name =~ s/^set// if @_ && !exists $self->{$name};
160              
161 0 0       0 unless (exists $self->{$name}) {
162 0         0 croak "Can't access `$name' field in class $pkg";
163             }
164              
165 0 0       0 if (@_) {
166 0         0 return $self->{$name} = $_[0];
167             } else {
168 0         0 return $self->{$name};
169             }
170             }
171              
172 0     0   0 sub DESTROY {
173             }
174              
175             =item getmapsym ($x,$y)
176              
177             =item getmapcolor ($x,$y)
178              
179             =item setmap ($x,$y, $sym [,$color])
180              
181             Raw accessors may be subject to change in future versions, but are much faster.
182              
183             $area->{map}[$x][$y] = $sym,
184             $area->{color}[$x][$y] = $color
185              
186             Function accessors will always work, but are slower:
187              
188             $sym=$area->getmapsym($x,$y)
189             $color=$area->getmapcolor($x,$y)
190             $area->setmap($x,$y, $sym [,$color]);
191              
192             If you're looping over the whole map, you can use foreachmap().
193              
194             etcetera.
195              
196             =cut
197              
198             sub getmapsym {
199 0     0 1 0 my $self = shift;
200 0         0 my ($x, $y) = @_;
201 0         0 return $self->{map}->[$x][$y];
202             }
203              
204             sub setmapsym {
205 0     0 0 0 my $self = shift;
206 0         0 my ($x, $y, $sym) = @_;
207 0         0 $self->{map}->[$x][$y] = $sym;
208             }
209              
210             sub setmap {
211 0     0 1 0 my $self = shift;
212 0         0 my ($x, $y, $sym, $color) = @_;
213 0 0       0 if (ref($sym)) {
214 0         0 $self->{map}->[$x][$y] = $sym->{sym};
215 0 0       0 $self->{color}->[$x][$y] = $sym->{color} if defined $sym->{color};
216             } else {
217 0         0 $self->{map}->[$x][$y] = $sym;
218 0 0       0 $self->{color}->[$x][$y] = $color if defined $color;
219             }
220             }
221              
222             sub mapsym {
223 0     0 0 0 my $self = shift;
224 0         0 my ($x, $y, $s) = @_;
225 0 0       0 if ($s) {
226 0         0 return $self->{map}->[$x][$y] = $s;
227             } else {
228 0         0 return $self->{map}->[$x][$y];
229             }
230             }
231              
232             sub mapcolor {
233 0     0 0 0 my $self = shift;
234 0         0 my ($x, $y, $c) = @_;
235 0 0       0 if ($c) {
236 0         0 return $self->{color}->[$x][$y] = $c;
237             } else {
238 0         0 return $self->{color}->[$x][$y];
239             }
240             }
241              
242             sub map {
243 1     1 0 2 my $self = shift;
244 1 50       6 if (@_) {
245 1         3 my ($x, $y) = (shift, shift);
246 1         7 return $self->{map}->[$x][$y];
247             } else {
248 0         0 return $self->{map};
249             }
250             }
251              
252             =item rpoint ()
253              
254             Returns a random point as an X/Y array, that's within the map, and not on the edge..
255              
256             =cut
257              
258             sub rpoint {
259 41     41 1 71 my $self = shift;
260 41 50       202 croak unless $self;
261 41         231 return (1+int(rand()*($self->{w}-2)), 1+int(rand()*($self->{h}-2)));
262             }
263              
264             =item rpoint_empty ()
265              
266             Returns a random point that's empty (eiher the symbol is ''' or is undefined)
267              
268             =cut
269              
270             sub rpoint_empty {
271 0     0 1 0 my $self = shift;
272 0 0       0 croak unless $self;
273             # well, there's better ways to do this, but this has worked so far
274 0         0 while (1) {
275 0         0 my ($x, $y) = (1+int(rand()*($self->{w}-2)), 1+int(rand()*($self->{h}-2)));
276 0 0       0 return ($x, $y) if $self->{map}->[$x][$y] eq '';
277             }
278             }
279              
280             ############ part of genmaze1 ############
281              
282             sub genroom {
283             # make a room around x/y with options
284             # options are: minx, miny, maxx, maxy, nooverlap, withdoor
285            
286 0     0 0 0 my $self = shift;
287 0         0 my ($x, $y, %opts) = @_;
288            
289 0 0       0 if ($x =~ /^[a-z]/i) {
290 0         0 %opts = @_;
291 0         0 $x = $opts{x};
292 0         0 $y = $opts{y};
293 0 0       0 if (! defined $x) {
294 0         0 ($x, $y) = $self->rpoint_empty();
295             }
296             }
297            
298 0         0 my $m = $self->{map};
299              
300             # room width/height
301 0         0 my $rw = max(1,2+($self->{w}/2-2)*rand()*rand());
302 0         0 my $rh = max(1,2+($self->{h}/2-2)*rand()*rand());
303              
304 0 0 0     0 $rw=$opts{minw} if $opts{minw} && $rw < $opts{minw};
305 0 0 0     0 $rh=$opts{minh} if $opts{minh} && $rh < $opts{minh};
306 0 0 0     0 $rw=$opts{maxw} if $opts{maxw} && $rw > $opts{maxw};
307 0 0 0     0 $rh=$opts{maxh} if $opts{maxh} && $rh > $opts{maxh};
308            
309             # top left corner of room (not including walls)
310 0         0 my $rx = min($x, max(1,int(rand() + $x - ($rw-1)/2)));
311 0         0 my $ry = min($y, max(1,int(rand() + $y - ($rh-1)/2)));
312              
313 0         0 $rw = min($rw, $self->{w}-$rx-1);
314 0         0 $rh = min($rh, $self->{h}-$ry-1);
315              
316 0 0 0     0 if (!$rh || !$rw) {
317             #push @{$self->{f}}, [$rx, $ry, 'NULLROOM'];
318 0         0 return 0;
319             }
320              
321 0 0       0 if ($opts{nooverlap}) {
322 0         0 my $ov = 0;
323 0         0 for (my $i = -1; $i <= $rw; ++$i) {
324 0 0       0 if ($m->[$rx+$i][int($ry+($i * $rw/$rh))]) {
325 0         0 $rw = $i-1;
326 0         0 $rh = int(($i * $rw/$rh)) - 1;
327             }
328             }
329 0 0 0     0 if ($rw <= 1 || $rh <= 1) {
330 0         0 return 0;
331             }
332             }
333              
334 0         0 intify($rx, $ry, $rw, $rh);
335              
336 0 0 0     0 if ($x > ($rx+$rw) || $y > ($ry+$rh)) {
337 0         0 return 0;
338             }
339              
340 0         0 for (my $i = -1; $i <= $rw; ++$i) {
341 0 0       0 next if ($rx+$i) < 0;
342 0 0 0     0 $m->[$rx+$i][$ry-1] = $self->{wsym} unless $m->[$rx+$i][$ry-1] || ($ry == 0);
343 0 0       0 $m->[$rx+$i][$ry+$rh] = $self->{wsym} unless $m->[$rx+$i][$ry+$rh];
344             }
345 0         0 for (my $i = 0; $i < $rw; ++$i) {
346 0         0 for (my $j = 0; $j < $rh; ++$j) {
347 0 0       0 $m->[$rx+$i][$ry+$j] = $self->{fsym} unless $m->[$rx+$i][$ry+$j];
348             }
349             }
350 0         0 for (my $i = -1; $i <= $rh; ++$i) {
351 0 0       0 next if ($ry+$i) < 0;
352 0 0 0     0 $m->[$rx-1][$ry+$i] = $self->{wsym} unless $m->[$rx-1][$ry+$i] || ($rx == 0);
353 0 0       0 $m->[$rx+$rw][$ry+$i] = $self->{wsym} unless $m->[$rx+$rw][$ry+$i];
354             }
355 0 0       0 if ($opts{door}) {
356 0 0       0 if (rand() > .5) {
357 0 0       0 if (rand() > .5) {
358 0         0 $m->[$rx+rand()*$rw][$ry-1] = $self->{dsym};
359             } else {
360 0         0 $m->[$rx+rand()*$rw][$ry+$rh] = $self->{dsym};
361             }
362 0         0 --$opts{door};
363             } else {
364 0 0       0 if (rand() > .5) {
365 0         0 $m->[$rx-1][$ry+rand()*$rh] = $self->{dsym};
366             } else {
367 0         0 $m->[$rx+$rw][$ry+rand()*$rh] = $self->{dsym};
368             }
369 0         0 --$opts{door};
370             }
371             }
372              
373 0         0 if (0) {
374             my $sig = chr(64 + int($rw));
375             print "$sig:$rx, $ry / $rw,$rh\n";
376             $m->[$x][$y] = $sig;
377             $m->[$rx][$ry] = 'C';
378             }
379              
380 0         0 push @{$self->{f}}, [$x, $y, 'ROOM'];
  0         0  
381              
382 0         0 return 1;
383             }
384              
385             =item findpath(x1, y1, x2, y2)
386              
387             Returns 1 if there is a path from x1,y2 to x2,y2. Uses "nomove" member as the list of symbols that cannot be moved through.
388              
389             =cut
390              
391             sub findpath {
392             # flood fill 'path exists' search
393              
394 40     40 1 84 my $self = shift;
395 40         86 my ($x1, $y1, $x2, $y2) = @_;
396 40         87 my $f;
397              
398 40 50 33     168 return 1 if ($x1 == $x2 && $y1 == $y2);
399              
400 40         66 my @f;
401             my @bread;
402 40         130 push @f, [$x1, $y1];
403 40         127 while (@f) {
404 5467         8647 my $c = pop @f;
405 5467         12508 for (my $d=0;$d<8;++$d) {
406 43648         78383 my $tx = $DD[$d]->[0]+$c->[0];
407 43648         65891 my $ty = $DD[$d]->[1]+$c->[1];
408              
409             # not off edge
410 43648 50 33     186700 next if $tx < 0 || $ty < 0;
411 43648 50 33     219649 next if $tx >= $self->{w} || $ty >= $self->{h};
412              
413             # not thru void
414 43648 50       112638 next if !defined($self->{map}->[$tx][$ty]);
415 43648 50       99582 next if $self->{map}->[$tx][$ty] eq '';
416              
417             # not thru wall
418 43648 100       152832 next if index($self->{nomove}, $self->{map}->[$tx][$ty]) >= 0;
419              
420 26435 100       90481 next if $bread[$tx][$ty];
421 7141         14742 $bread[$tx][$ty] = '.'; #been there
422              
423 7141 100 100     19774 return 1 if ($tx == $x2 && $ty == $y2);
424              
425 7115         30097 push @f, [$tx, $ty]; #add to list;
426             }
427             }
428              
429 14         245 return 0;
430             }
431              
432             =item findclose(x1, y1, x2, y2)
433              
434             Returns the closest you can get to x2,y2 from x1,y2 without going through a "nomove" symbol.
435              
436             Return value is (X,Y) array, or undefined if there's no path.
437              
438             =cut
439              
440             #if (!defined(&findclose)) {
441             sub findclose {
442             # flood fill return closest you can get to x2/y2 without going thru a barrier
443 65     65 1 160 my $self = shift;
444 65         136 my ($x1, $y1, $x2, $y2) = @_;
445 65         125 my $f;
446             my @f;
447 65         263 push @f, [$x1, $y1];
448 65         120 my @bread;
449 65         196 my ($cx, $cy) = ($x1, $y1);
450 65         213 my $mindist = ($self->{w} + $self->{h}) * 2;
451              
452 65         180 while (@f) {
453 6194         9109 my $c = pop @f;
454 6194         13414 for (my $d=0;$d<8;++$d) {
455 49496         97986 my $tx = $DD[$d]->[0]+$c->[0];
456 49496         88090 my $ty = $DD[$d]->[1]+$c->[1];
457              
458             # not off edge
459 49496 50 33     204329 next if $tx < 0 || $ty < 0;
460 49496 50 33     220724 next if $tx >= $self->{w} || $ty >= $self->{h};
461              
462             # not thru void
463 49496 50 33     233902 next if !defined($self->{map}->[$tx][$ty]) || $self->{map}->[$tx][$ty] eq '';
464              
465             # not thru wall
466 49496 100       177610 next if index($self->{nomove}, $self->{map}->[$tx][$ty]) >= 0;
467              
468 31072 100       110289 next if $bread[$tx][$ty];
469 6854         17157 $bread[$tx][$ty] = '.'; #been there
470              
471 6854 100 100     17437 return ($tx, $ty, 0) if ($tx == $x2 && $ty == $y2);
472              
473 6840 100       253581 if ((my $tdist = distance($tx, $ty, $x2, $y2)) < $mindist) {
474 776         973 $cx = $tx;
475 776         812 $cy = $ty;
476 776         1081 $mindist = $tdist;
477             }
478              
479 6840         30519 push @f, [$tx, $ty]; #add to list of places can get to;
480             }
481             }
482              
483             # my ($ax,$ay,$ad) = findclose_c($self, $x1, $y1, $x2,$y2);
484             # if ($ax!=$cx||$ay!=$cy) {
485             # croak "C and perl disagree: ($ax,$ay <> $cx,$cy);"
486             # }
487              
488             # $self->{color}->[$cx][$cy] = 'green';
489 51         1259 return ($cx, $cy, $mindist);
490             }#}
491              
492              
493             =item maxcardinal ()
494              
495             Maximum point someone can walk from x/y in each of 4 cardinal directions, returned as an array of XY arrays.
496              
497             Array is sorted n,s,e,w.
498              
499             =cut
500              
501             sub maxcardinal {
502 22     22 1 47 my $self = shift;
503 22         45 my ($x, $y) = @_;
504              
505             # maximum direction you can walk from x/y in each of the 4 directions, returned as a nsew array of points
506 22         189 my @r;
507 22         94 for (my $d=0;$d<4;++$d) {
508 88         193 my ($cx, $cy) = ($x,$y);
509 88         102 while (1) {
510 217         350 my $tx = $DD[$d]->[0]+$cx;
511 217         298 my $ty = $DD[$d]->[1]+$cy;
512              
513             # not thru void
514 217 50 33     1085 last if !defined($self->{map}->[$tx][$ty]) || $self->{map}->[$tx][$ty] eq '';
515              
516             # not thru wall
517 217 100       635 last if index($self->{nomove}, $self->{map}->[$tx][$ty]) >= 0;
518              
519             # not off edge
520 129 50 33     496 last if $tx < 0 || $ty < 0;
521 129 50 33     656 last if $tx >= $self->{w} || $ty > $self->{h};
522              
523             # record
524 129         381 ($cx, $cy) = ($tx,$ty);
525             };
526              
527 88         436 push @r, [$cx, $cy];
528             }
529              
530 22         130 return @r;
531             }
532              
533             =item digone (x, y, $ws, $fs)
534              
535             "Digs" one square of the map, at position x,y - turning it into a "floor",
536             while also turning the surrounding areas into "walls", if they are currently
537             not assigned.
538              
539             Optionally can specify wall & floor symbols (or uses area defaults).
540              
541             Does nothing if the map symbol at x,y is not a wall or void.
542              
543             Returns the number of walls that the new point is surrounded by or -1 if the point was off the map.
544              
545             =cut
546              
547             sub digone {
548 2937     2937 1 3781 my $self = shift;
549 2937         4238 my ($x, $y, $ws, $fs) = @_;
550              
551 2937 50       7391 $ws = $self->{wsym} unless $ws;
552 2937 50       6007 $fs = $self->{fsym} unless $fs;
553            
554 2937 50 33     13461 return -1 if ($x <=0 || $y <= 0);
555 2937 50 33     14930 return -1 if ($x >=($self->{w}-1) || $y >= ($self->{h}-1));
556              
557 2937         6184 my $c = $self->{map}->[$x][$y];
558 2937 100 100     15085 return unless !defined($c) || ($c eq $ws) || ($c eq '');
      66        
559 2771         6063 $self->{map}->[$x][$y] = $fs;
560              
561 2771         7530 for (my $d=0;$d<8;++$d) {
562 22168         43746 my $tx = $DD[$d]->[0]+$x;
563 22168         30049 my $ty = $DD[$d]->[1]+$y;
564 22168         39520 my $c = $self->{map}->[$tx][$ty];
565 22168 100 66     115447 next unless !defined($c) || $c eq '';
566 5543         22505 $self->{map}->[$tx][$ty] = $ws;
567             }
568              
569             #$self->drawmap();
570            
571 2771         8221 return 1;
572             }
573              
574             sub debug {
575 31     31 0 86 return 1;
576             }
577              
578              
579             =item nexttosym(x, y, sym)
580              
581             Returns a direction if x,y are adjacent to sym, otherwise returns undef.
582              
583             =cut
584              
585             sub nexttosym {
586 0     0 1 0 my $self = shift;
587 0         0 my ($x, $y, $sym) = @_;
588 0         0 my $dn = 0;
589 0         0 for (@DD) {
590 0         0 my $tx = $x + $_->[0];
591 0         0 my $ty = $y + $_->[1];
592 0 0       0 return $DIRS[$dn] if index($sym, $self->{map}[$tx][$ty]) >= 0;
593 0         0 ++$dn;
594             }
595 0         0 return undef;
596             }
597              
598              
599             =item okdoor(x, y)
600              
601             Looks at the clockwize adjacent symbols for an alternating pattern of walls and floors, to see if a door
602             would be meaningful.
603              
604             Uses {nomove} as the list of "wall" symbols.
605              
606             =cut
607              
608             sub okdoor {
609 33     33 1 50 my $self = shift;
610 33         50 my ($x, $y) = @_;
611 33         50 my $cnt = 0;
612 33         67 my $cur;
613             my $first;
614 33         43 my $flip = 0;
615 33         79 for (@CWDIRS) {
616 264         542 my $tx = $x + $DD{$_}->[0];
617 264         356 my $ty = $y + $DD{$_}->[1];
618 264         639 my $sym = $self->{map}[$tx][$ty];
619 264   100     1576 my $wl = !defined($sym) || (index($self->{nomove}, $sym) >= 0) || $sym eq $self->{dsym};
620 264 100       438 if (! defined $cur) {
621 33         48 $cur = $wl;
622 33         81 $first = $wl;
623             } else {
624 231 100       489 if ($cur != $wl) {
625 114         130 ++$flip;
626 114         214 $cur = $wl;
627             }
628             }
629            
630             }
631 33 100       101 if ($cur != $first) {
632 10         19 ++$flip;
633             }
634              
635 33         202 return $flip == 4;
636             }
637              
638             =item makepath(x1, y1, x2, y2)
639              
640             Drill a right-angled corridor between 2 nonempty points using digone().
641              
642             ** Notably the whole auto-door upon breaking into an open area doesn't always work right, and should.
643              
644             =cut
645              
646             sub placedoors {
647 22     22 0 40 my $self=shift;
648 22         57 my @doors = @_;
649 22         59 for (@doors) {
650 33         75 my ($x, $y) = @$_;
651 33 100 66     102 if ($self->okdoor($x, $y) && $self->{dsym}) {
652 29         115 $self->{map}->[$x][$y] = $self->{dsym};
653             }
654             }
655             }
656              
657             sub makepath {
658 32     32 1 73 my $self = shift;
659 32         75 my ($ox, $oy, $x2, $y2) = @_;
660              
661 32 50 33     317 croak "can't make a path without floor and wall symbols set"
662             unless $self->{wsym} && $self->{fsym};
663              
664             # $self->{map}->[$x][$y] = chr(64 + ++$self->{dseq}) if $self->debug;
665             # $self->{map}->[$x2][$y2] = chr(64 + ++$->{dseq}) if $self->debug;
666              
667 32         108 my ($x, $y) = ($ox, $oy);
668              
669 32 50       249 if ($self->{map}->[$x][$y] eq '') {
670 0         0 return;
671             }
672 32 50       138 if ($self->{map}->[$x2][$y2] eq '') {
673 0         0 return;
674             }
675              
676 32         92 my $d;
677 32 100       191 if ($y < $y2) {
    100          
678 8         22 $d = 's';
679             } elsif ($y > $y2) {
680 13         157 $d = 'n';
681             }
682              
683 32 100       135 if ($x < $x2) {
    100          
684 7         27 $d .= 'e';
685             } elsif ($x > $x2) {
686 15         42 $d .= 'w';
687             }
688              
689 32 100       97 return if !$d;
690              
691             # 2 directions, randomly sorted
692            
693 22         34 my @d;
694 22         57 $d[1] = $d;
695 22         137 $d[0] = substr($d, rand()*2, 1);
696 22         988 $d[1] =~ s/$d[0]//;
697              
698             # closest can get now
699 22         135 ($x, $y) = $self->findclose($x, $y, $x2, $y2);
700              
701             # $self->dprint "($x, $y) closest from $ox $oy to $x2, $y2";
702              
703             # choose a random square among maximum wall range from closest point
704 22         166 my @mc = $self->maxcardinal($x, $y);
705 22         70 $d=$d[0];
706 22         55 my $len;
707 22 100       303 if ($d =~ /^n|s$/) {
708 11         208 $x = $mc[$DI{w}]->[0] + rand() * ($mc[$DI{e}]->[0] - $mc[$DI{w}]->[0]);
709             } else {
710 11         111 $y = $mc[$DI{n}]->[1] + rand() * ($mc[$DI{s}]->[1] - $mc[$DI{n}]->[1]);
711             }
712 22         136 intify($x, $y);
713              
714 22         38 my @doors;
715              
716 22         42 my $firstdig = 1; # first dig out of an area gets a door
717 22         47 for my $d (@d) {
718 32 100       77 next unless $d;
719              
720 31 50       125 $self->{map}->[$x][$y] = $d if $self->debug > 1;
721              
722 31 100       137 if ($d =~ /^n|s$/) {
723 16         43 $len = abs($y-$y2);
724             } else {
725 15         41 $len = abs($x-$x2);
726             }
727              
728 31         58 my $already_dug = 0;
729 31         57 my $waswall = 0;
730            
731 31         81 for (my $i = 0; $i < $len; ++$i) {
732 329         483 my ($px,$py) = ($x, $y);
733 329         611 $x += $DD{$d}->[0];
734 329         721 $y += $DD{$d}->[1];
735              
736 329         915 my $iswall = $self->{map}->[$x][$y] eq $self->{wsym};
737 329 100 66     1126 if ($iswall || $self->{map}->[$x][$y] eq '') {
738 303         737 $self->digone($x,$y);
739 303 100       662 if ($firstdig) {
740 17         56 push @doors, [$x, $y];
741 17         117 $firstdig = 0;
742             }
743             } else {
744             # not a wall, already dug
745 26         43 $already_dug = 1;
746 26 100       60 if ($waswall) {
747 16         59 push @doors, [$px, $py];
748             } else {
749             # 2 non walls in a row = reset firstdig
750 10         31 $firstdig = 1;
751             }
752             }
753 329         504 $waswall = $iswall;
754              
755            
756 329 100       687 if ( $already_dug ) {
757 43         190 my ($fx, $fy, $dist) = $self->findclose($x, $y, $x2, $y2);
758 43 100       1689 if (&distance($fx, $fy, $x, $y) >= 2 ) {
759 18         176 my $res = $self->makepath($fx, $fy, $x2, $y2);
760 18         94 $self->placedoors(@doors);
761 18         205 return $res;
762             }
763 25 100       102 last if $dist == 0;
764             }
765              
766             #$self->{world}->drawmap();
767             # too close to edge
768 307 50 33     3666 last if $x<=1 || $y<=1 || $x>=($self->{w}-1) || $y>=($self->{h}-1);
      33        
      33        
769             }
770 13 100 100     71 last if ($x == $x2 && $y == $y2);
771             }
772              
773            
774 4 50       28 if (!$self->findpath($ox, $oy, $x2, $y2)) {
775 0         0 $self->makepath2($ox, $oy, $x2, $y2);
776             }
777              
778 4         39 $self->placedoors(@doors);
779              
780 4         42 return 1;
781             }
782              
783             =item makepath2 (x1, y1, x2, y2)
784              
785             Like makepath, but diagonal and with no doors.
786              
787             =cut
788              
789              
790             sub makepath2 {
791 0     0 1 0 my $self = shift;
792 0         0 my ($ox, $oy, $x2, $y2) = @_;
793              
794 0 0 0     0 croak "can't make a path without floor and wall symbols set"
795             unless $self->{wsym} && $self->{fsym};
796              
797 0         0 my ($x, $y) = ($ox, $oy);
798              
799 0         0 ($x, $y) = $self->findclose($x, $y, $x2, $y2);
800              
801 0   0     0 while (($x != $x2) || ($y != $y2)) {
802 0         0 $self->digone($x, $y);
803 0         0 my $d;
804 0 0       0 if ($y < $y2) {
    0          
805 0         0 $d = 's';
806             } elsif ($y > $y2) {
807 0         0 $d = 'n';
808             }
809 0 0       0 if ($x < $x2) {
    0          
810 0         0 $d .= 'e';
811             } elsif ($x > $x2) {
812 0         0 $d .= 'w';
813             }
814              
815 0         0 $x += $DD{$d}->[0];
816 0         0 $y += $DD{$d}->[1];
817             }
818             }
819              
820              
821             =item findfeature (symbol)
822              
823             Searches "map feature list" for the given symbol, returns coordinates if found.
824              
825             =cut
826              
827             sub findfeature {
828 0     0 1 0 my $self = shift;
829 0         0 my ($sym) = @_;
830              
831 0         0 for (@{$self->{f}}) {
  0         0  
832 0         0 my ($fx, $fy) = @$_;
833 0 0       0 if ($self->{map}->[$fx][$fy] eq $sym) {
834 0         0 return ($fx, $fy);
835             }
836             }
837             }
838              
839             =item addfeature (symbol [, x, y])
840              
841             Adds a symbol to the map (to a random open floor point if one is not specified), and also adds it to the searchable "feature list".
842              
843             =cut
844              
845             sub addfeature {
846 0     0 1 0 my $self = shift;
847 0         0 my ($sym, $x, $y) = @_;
848              
849 0 0       0 if (!defined($x)) {
850 0         0 ($x, $y) = $self->findrandmap($self->{fsym}, 0, 1);
851             }
852 0         0 $self->{map}->[$x][$y] = $sym;
853 0         0 push @{$self->{f}}, [$x, $y];
  0         0  
854             }
855              
856             =item inbound(x, y)
857              
858             Returns true if x >= 0 and x < $self->{w} and y >= 0 and y < $self->{h}.
859              
860             =cut
861              
862             sub inbound {
863 0     0 1 0 my $self = shift;
864 0   0     0 return ($_[0]>=0)&&($_[0]<($self->{w}))&&($_[1]>=0)&&($_[1]<($self->{h}));
865             }
866              
867              
868             =item generate ($type [, options ... ])
869              
870             This calls "$type::generate" if it is defined, perhaps in the caller's package, etc.
871              
872             Predefined types are "Rooms", "Cavelike" and "Maze" (see below).
873              
874             If no "::" is present, 'Games::Roguelike::Area::' is prepended, and the first character is propercased.
875              
876             If &{"$type::generate"} is not defined, it will require "$type" and then try again.
877              
878             Assuming there is no function named "wilderness::generate", then the following are roughly equivalent:
879              
880             $self->generate('Games::Roguelike::Area::Rooms');
881             or
882             $self->generate('rooms');
883             or
884             Games::Roguelike::Area::Rooms::generate($self);
885             or
886             $self = new Games::Roguelike::Area::Rooms; $self->generate();
887              
888             Additonal options are passed to the generate function. Generate functions should support named parameters..
889              
890             Common option is a "with=>[feature1, feature2]" feature list, that get added to empty floor map locations,
891             and appended to the map's feature list.
892              
893             Height, width are taken from the area object.
894              
895             =cut
896              
897             sub generate {
898 2     2 1 21 my $self = shift;
899 2         5 my $type = shift;
900              
901 2         7 my $path = $type . '::generate';
902 2 50       10 if (defined &$path) {
903 5     5   65 no strict 'refs';
  5         18  
  5         990  
904 0         0 return &$type($self, @_);
905             }
906              
907 2 50       10 if ($type !~ /::/) {
908 2         18 $type =~ s/^(\w)/\U$1/;
909 2         7 $type = 'Games::Roguelike::Area::' . $type;
910 2         6 $path = $type . '::generate';
911 2 50       15 if (defined &$path) {
912 5     5   37 no strict 'refs';
  5         13  
  5         261  
913 2         11 return &$path($self, @_);
914             }
915             }
916 0         0 eval "require $type";
917 5     5   27 no strict 'refs';
  5         13  
  5         5943  
918 0         0 return &$path($self, @_);
919             }
920              
921              
922             # this is intended as *example* of making a map that i got to work in a few hours
923             # it is not intended as a good map
924             # if map-making isn't what you want to work on in the beginning, you can start here
925              
926              
927             =item generate('cavelike', [with=>[sym1[,sym2...]])
928              
929             Makes a random map with a bunch of cave-like rooms connected by corridors.
930              
931             Can specify a list of symbols to be added as "features" of the map.
932              
933             =cut
934              
935             sub diginbound {
936 2680     2680 0 4876 my $self = shift;
937 2680   100     33527 return ($_[0]>0)&&($_[0]<($self->{w}-2))&&($_[1]>0)&&($_[1]<($self->{h}-2));
938             }
939              
940             sub genmaze2 {
941 5     5 0 10 my $self = shift;
942 5         9 my %opts = @_;
943              
944 5         6 my ($m, $fx, $fy);
945              
946 5         10 my $digc = 0;
947              
948 5         15 do {
949 41         226 my ($cx, $cy) = $self->rpoint();
950 41         221 $self->digone($cx, $cy);
951 41 50       56 if (my $feature = shift @{$opts{with}}) {
  41         174  
952 0         0 $self->{map}->[$cx][$cy]=$feature;
953 0         0 push @{$self->{f}}, [$cx, $cy, 'FEATURE'];
  0         0  
954             } else {
955 41         78 push @{$self->{f}}, [$cx, $cy, 'ROOM'];
  41         202  
956             }
957 41         73 my @v;
958 41         100 $v[$cx][$cy]=1;
959 41         51 my $dug = 0;
960 41         68 do {
961 2634         8576 my $o = randi(4);
962 2634         3484 $dug = 0;
963 2634         7162 for (my $i=0;$i<4;++$i) {
964 3969         9125 my ($tx, $ty) = ($cx+$DD[($i+$o)%4]->[0], $cy+$DD[($i+$o)%4]->[1]);
965 3969 100 100     18666 if ((!$v[$tx][$ty]) && $self->diginbound($tx, $ty)) {
966 2593         3523 ($cx, $cy) = ($tx, $ty);
967 2593 100       5743 ++$digc if $self->digone($cx, $cy);
968             #print "dig at $cx, $cy $v[$cx][$cy]\n";
969 2593         8084 $v[$cx][$cy] = 1;
970 2593         3066 $dug = 1;
971 2593         8334 last;
972             }
973             }
974             } while ($dug);
975              
976             } while ($digc < (($self->{w}*$self->{h})/8));
977              
978             # dig out paths
979 5         12 my ($px, $py);
980 5         12 for (randsort(@{$self->{f}})) {
  5         41  
981 41         87 my ($x, $y, $reason) = @{$_};
  41         125  
982 41 100       124 if ($px) {
983 36 100       228 if (!$self->findpath($x, $y, $px, $py)) {
984 14         115 $self->makepath($x, $y, $px, $py);
985             #$self->drawmap();
986             #$self->getch();
987             }
988             }
989 41         616 ($px, $py) = ($x, $y);
990             }
991             }
992              
993             =item generate ('rooms', [with=>[sym1[,sym2...]])
994              
995             Makes a random nethack-style map with a bunch of rectangle rooms connected by corridors
996              
997             If you specify a "with" list, it puts those symbols on the map in random rooms, and calls "addfeature" on them.
998              
999             =cut
1000              
1001             sub genmaze1 {
1002 0     0 0 0 my $self = shift;
1003 0         0 my %opts = @_;
1004              
1005 0         0 my ($m, $fx, $fy);
1006              
1007 0         0 my $rooms = 0;
1008              
1009 0         0 for my $feature (@{$opts{with}}) {
  0         0  
1010 0         0 ($fx, $fy) = $self->rpoint_empty();
1011 0         0 $self->{map}->[$fx][$fy]=$feature;
1012 0         0 push @{$self->{f}}, [$fx, $fy, 'FEATURE'];
  0         0  
1013 0 0       0 ++$rooms if $self->genroom($fx, $fy); # put rooms around features
1014             }
1015              
1016             # some extra rooms
1017 0         0 while($rooms < ($self->{w}*$self->{h}/600)) {
1018 0 0       0 ++$rooms if $self->genroom(($fx, $fy) = $self->rpoint_empty(), nooverlap=>1);
1019             }
1020              
1021             # dig out paths
1022 0         0 my ($px, $py);
1023 0         0 for (randsort(@{$self->{f}})) {
  0         0  
1024 0         0 my ($x, $y, $reason) = @{$_};
  0         0  
1025 0 0       0 if ($px) {
1026 0 0       0 if (!$self->findpath($x, $y, $px, $py)) {
1027 0         0 $self->makepath($x, $y, $px, $py);
1028 0 0       0 if (!$self->findpath($x, $y, $px, $py)) {
1029 0         0 $self->{map}->[$px][$py] = '1';
1030 0         0 $self->{map}->[$x][$y] = '2';
1031 0         0 $self->dump();
1032 0         0 die "makepath failed!!!\n";
1033 0         0 $self->makepath($x, $y, $px, $py);
1034             }
1035             #$self->drawmap();
1036             }
1037             }
1038 0         0 ($px, $py) = ($x, $y);
1039             }
1040             }
1041              
1042             =item generate('maze', rand=>number, with=>feature-list)
1043              
1044             Generate a tight, difficult maze. Rand defaults to 5 (higher numbers are less random).
1045              
1046             =cut
1047              
1048             sub genmaze3 {
1049 0     0 0 0 my $self = shift;
1050 0         0 my %opts = @_;
1051              
1052 0 0       0 $opts{w} = $self->{w} if !$opts{w};
1053 0 0       0 $opts{h} = $self->{h} if !$opts{h};
1054              
1055 0         0 my ($cNx,$cNy, $cSx, $cSy);
1056 0         0 my $intDir;
1057 0         0 my $intDone = 0;
1058              
1059 0         0 my $blnBlocked;
1060              
1061 5     5   45 use constant X => 0;
  5         13  
  5         581  
1062 5     5   33 use constant Y => 1;
  5         10  
  5         30211  
1063              
1064 0 0       0 $opts{rand} = 5 if !$opts{rand};
1065              
1066 0 0       0 $opts{w} -= 1 if !($opts{w}%2);
1067 0 0       0 $opts{h} -= 1 if !($opts{h}%2);
1068              
1069             # stores the directions that corridors go in
1070 0         0 my @cDir;
1071             my @blnMaze;
1072              
1073 0         0 do {
1074             # this code is used to make sure the numbers are odd
1075 0         0 $cSx = 1 + (int((($opts{w} - 1) * rand()) / 2) * 2);
1076 0         0 $cSy = 1 + (int((($opts{h} - 1) * rand()) / 2) * 2);
1077              
1078             # first opening is free!
1079 0 0       0 $blnMaze[$cSx][$cSy] = 1 if !$intDone;
1080              
1081 0 0       0 if ($blnMaze[$cSx][$cSy]) {
1082             # randomize directions to start
1083 0         0 @cDir = &getRandomDirections();
1084 0         0 do {
1085             # only randomisation directions, based on the constant
1086 0 0       0 @cDir = &getRandomDirections() if !int($opts{rand} * rand());
1087 0         0 $blnBlocked = 1;
1088             # loop through order of directions
1089 0         0 for ($intDir = 0; $intDir < 4; ++$intDir) {
1090             # work out where this direction is
1091 0         0 $cNx = $cSx + ($cDir[$intDir][X] * 2);
1092 0         0 $cNy = $cSy + ($cDir[$intDir][Y] * 2);
1093             # check if the tile can be used
1094 0         0 my $isFree;
1095 0 0 0     0 if ($cNx < ($opts{w}-1) && $cNx >= 1 && $cNy < ($opts{h}-1) && $cNy >= 1) {
      0        
      0        
1096             # true if it hasn't been used yet
1097 0         0 $isFree = !$blnMaze[$cNx][$cNy];
1098             }
1099 0 0       0 if ($isFree) {
1100             # create a path
1101 0         0 $blnMaze[$cNx][$cNy] = 1;
1102             # and the square inbetween
1103 0         0 $blnMaze[$cSx + $cDir[$intDir][X]][$cSy + $cDir[$intDir][Y]] = 1;
1104             # this is now the current square
1105 0         0 $cSx = $cNx;
1106 0         0 $cSy = $cNy;
1107 0         0 $blnBlocked = 0;
1108             # increment paths created
1109 0         0 $intDone = $intDone + 1;
1110 0         0 last;
1111             }
1112             }
1113             # loop until a path was created
1114             } while (!$blnBlocked)
1115             }
1116             } while ( $intDone + 1 < ( (($opts{w} - 1) * ($opts{h} - 1)) / 4 ) );
1117             # create enough paths to fill the whole grid
1118              
1119              
1120             # this changes the direction to go from the current square, to the next available
1121             sub getRandomDirections {
1122             # clear the array
1123 0     0 0 0 my @a = ([-1,0],[1,0],[0,-1],[0,1]);
1124 0         0 my @b;
1125 0         0 while (@a) {
1126 0         0 push @b, splice(@a, rand()*scalar(@a), 1);
1127             }
1128 0         0 return @b;
1129             }
1130              
1131 0         0 $self->{map} = [];
1132              
1133 0         0 for (my $y = 0; $y < $opts{h}; ++$y) {
1134 0         0 for (my $x = 0; $x < $opts{w}; ++$x) {
1135 0 0       0 $self->{map}->[$x][$y] = ($blnMaze[$x][$y] ? $self->{fsym} : $self->{wsym});
1136             }
1137             }
1138              
1139              
1140 0         0 for my $feature (@{$opts{with}}) {
  0         0  
1141 0         0 my ($fx, $fy) = $self->findrandmap('.');
1142 0         0 $self->{map}->[$fx][$fy]=$feature;
1143 0         0 push @{$self->{f}}, [$fx, $fy, 'FEATURE'];
  0         0  
1144             }
1145             }
1146              
1147             =item foreachmap (code-reference [,noborder=>1] [,border=>1])
1148              
1149             Loops over each map location, and calls the code with parameters x, y and map-symbol.
1150              
1151             Option "border" calls the code only for border (edge) squares.
1152              
1153             Option "noborder" calls the code for everything but border (edge) squares.
1154              
1155             =cut
1156              
1157             sub foreachmap {
1158 0     0 1 0 my $self = shift;
1159 0         0 my $code = shift;
1160 0         0 my %opts = @_;
1161            
1162 0 0       0 if ($opts{border}) {
1163 0         0 for my $y ((0,$self->{h}-1)) {
1164 0         0 for (my $x = 0; $x < $self->{w}; ++$x) {
1165 0         0 &{$code}($x, $y, $self->{map}->[$x][$y]);
  0         0  
1166             }
1167             }
1168 0         0 for my $x ((0,$self->{w}-1)) {
1169 0         0 for (my $y = 0; $y < $self->{h}; ++$y) {
1170 0         0 &{$code}($x, $y, $self->{map}->[$x][$y]);
  0         0  
1171             }
1172             }
1173             } else {
1174 0         0 my ($x1, $y1, $x2, $y2) = (0, 0, $self->{w},$self->{h});
1175              
1176 0 0       0 if ($opts{nobodrder}) {
1177 0         0 $x1++; $y1++;
  0         0  
1178 0         0 $x2--; $y2--;
  0         0  
1179             }
1180              
1181 0         0 for (my $y = $y1; $y < $y2; ++$y) {
1182 0         0 for (my $x = $x1; $x < $x2; ++$x) {
1183 0         0 &{$code}($x, $y, $self->{map}->[$x][$y]);
  0         0  
1184             }
1185             }
1186             }
1187             }
1188              
1189             =item draw ({dispx=>, dispy=>, vp=>, con=>});
1190              
1191             Draws the map using offset params dispx, dispy,disph,dispw,
1192              
1193             Uses the perspective of $vp (viewpoint), if specified. $vp can contain an x, y coordinate
1194             and a "pov" integer which is the maximum sight distance.
1195              
1196             Uses the console $con to draw the map.
1197              
1198             Uses the algorithm provided by the "checkpov" function for field of view calculations.
1199              
1200             Usually done after each move.
1201              
1202             =cut
1203              
1204             sub draw {
1205 3     3 1 6 my $self = shift;
1206 3         6 my ($opts) = @_;
1207              
1208 3         10 my $dispx = $opts->{dispx};
1209 3         9 my $dispy = $opts->{dispy};
1210 3         5 my $dispw = $opts->{dispw};
1211 3         7 my $disph = $opts->{disph};
1212 3         8 my $vp = $opts->{vp};
1213 3         7 my $con = $opts->{con};
1214              
1215 3         4 my $debugx = $dispx;
1216 3         5 my $debugy = $dispy;
1217 3 50       9 if ($self->{debugmap}) {
1218 0         0 $dispx += 3; $dispw -= 3;
  0         0  
1219 0         0 $dispy += 3; $disph -= 3;
  0         0  
1220             }
1221              
1222 3         8 my $ox = 0;
1223 3         4 my $oy = 0;
1224 3 50       18 if ($vp) {
1225 3         11 $ox = $vp->{x}-($dispw/2); #substract offsets from actual
1226 3         9 $oy = $vp->{y}-($disph/2);
1227 3 100       9 $ox = 0 if $ox < 0;
1228 3 100       9 $oy = 0 if $oy < 0;
1229 3 50       10 $ox = $self->{w}-$dispw if ($ox+$dispw) > $self->{w};
1230 3 50       15 $oy = $self->{h}-$disph if ($oy+$disph) > $self->{h};
1231             }
1232 3         134 intify($ox, $oy);
1233              
1234 3 50       10 if ($self->{debugmap}) {
1235             # show labels to help debuggin map routines
1236 0         0 $con->addstr($debugy,$debugx," " x 3);
1237 0         0 for (my $x = $ox; $x < $dispw+$ox; ++$x) {
1238 0         0 $con->addstr(substr(sprintf("%03.0d", $x),-2,1));
1239             }
1240 0         0 $con->addstr($debugy+1,$debugx," " x 3);
1241 0         0 for (my $x = $ox; $x < $dispw+$ox; ++$x) {
1242 0         0 $con->addstr(substr(sprintf("%03.0d", $x),-1,1));
1243             }
1244 0         0 $con->addstr($debugy+2,$debugx," " x 3);
1245 0         0 for (my $x = $ox; $x < $dispw+$ox; ++$x) {
1246 0         0 $con->addstr("-");
1247             }
1248             }
1249              
1250             #$self->dprint("OXY: $ox, $oy DXY: $dispx,$dispy");
1251            
1252             #actual map drawn at user-requested location/virtual window
1253 3         15 for (my $y = $oy; $y < ($disph+$oy); ++$y) {
1254             # x/y is the game map-coord, not drawn location
1255 54 50       189 if ($self->{debugmap}) {
1256 0         0 $con->addstr($y-$oy+$dispy, $debugx, sprintf("%02.0d|", $y));
1257             }
1258 54         1194 for (my $x = $ox; $x < $dispw+$ox; ++$x) {
1259 2160 100       13247 if (my $memtyp = $self->checkmap($vp, $x, $y, $self->{map}->[$x][$y])) {
1260 186         1889 my ($color) = $self->{color}->[$x][$y];
1261 186 50       877 my $sym = ($memtyp == 2) ? $vp->{memory}->{$self->{name}}->[$x][$y] : $self->{map}->[$x][$y] ? $self->{map}->[$x][$y] : ' ';
    50          
1262 186 50 33     1150 $color = $self->{memcolor} if $memtyp == 2 && $self->{memcolor}; # if the area is memorized, then draw as gray
1263 186         1118 $con->attrch($color, $y-$oy+$dispy,$x-$ox+$dispx,$sym);
1264             } else {
1265 1974         14516 $con->addch($y-$oy+$dispy,$x-$ox+$dispx,' ');
1266             }
1267             }
1268             }
1269              
1270             # drawitems
1271 3         8 for my $i (@{$self->{items}}) {
  3         16  
1272 0         0 $self->drawob($i, $opts, $ox, $oy, $dispx, $dispy);
1273             }
1274              
1275             # drawmobs on top
1276 3         6 for my $m (@{$self->{mobs}}) {
  3         13  
1277 3         17 $self->drawob($m, $opts, $ox, $oy, $dispx, $dispy);
1278             }
1279              
1280 3         18 $con->refresh();
1281             }
1282              
1283             #
1284             # This draws a thing that has a symbol, a color, an x and a y
1285             # It is called by draw() above
1286             #
1287              
1288             sub drawob {
1289 3     3 0 6 my $self = shift;
1290 3         9 my ($ob, $opts, $ox, $oy, $xoff, $yoff) = @_;
1291              
1292 3         11 my $vp = $opts->{vp};
1293 3         6 my $con = $opts->{con};
1294              
1295             # $ox, $oy must be subtracted to get display coords (relative to display box, don't draw if outside box)
1296             # $xoff, $yoff musy be ADDED to get absolute coords (relative to console box)
1297              
1298 3 50       17 if ($self->checkpov($vp, $ob->{x}, $ob->{y})) {
1299 3 50 33     77 if ( (($ob->{y}-$oy) >= 0) && (($ob->{x}-$ox) >= 0) && (($ob->{x}-$ox) < $opts->{dispw}) && (($ob->{y}-$oy) < $opts->{disph}) ) {
      33        
      33        
1300 3         41 $con->attrch($ob->{color},$ob->{y}-$oy+$yoff, $ob->{x}-$ox+$xoff, $ob->{sym});
1301             }
1302             #if the object is not the char, and the object is novel then memorize it and set the "saw something new this turn" flag
1303 3 50 33     30 if ($vp && $ob != $vp && !($vp->{memory}->{$self->{name}}->[$ob->{x}][$ob->{y}] eq $ob->{sym})) {
      33        
1304 0         0 $vp->{memory}->{$self->{name}}->[$ob->{x}][$ob->{y}] = $ob->{sym};
1305 0         0 $vp->{sawnew} = 1;
1306             }
1307 3         14 return 1;
1308             }
1309             }
1310              
1311              
1312             # These can be easily optimized also storing items/mobs at {m-items}[x][y] and {m-mobs}[x][y]
1313             # But a list approach is simpler for now, and reduces some overhead on sets
1314              
1315             =item mobat (x, y)
1316              
1317             Returns a single mob located at x/y, or undef if none is there.
1318              
1319             =cut
1320              
1321             sub mobat {
1322 2749     2749 1 3892 my $self = shift;
1323 2749         3247 my ($x, $y) = @_;
1324             #$self->dprint("mobat $x, $y");
1325 2749         3188 my @r;
1326 2749         2773 for my $m (@{$self->{mobs}}) {
  2749         14306  
1327 1 50 33     24 return $m if ($m->{x} == $x) && ($m->{y} == $y);
1328             }
1329             }
1330              
1331             =item items ([x, y])
1332              
1333             Returns reference to array of items located at x/y, or reference to array of all items if no x/y is supplied.
1334              
1335             =cut
1336              
1337             sub items {
1338 0     0 1 0 my $self = shift;
1339 0 0       0 if (!@_) {
1340 0         0 return $self->{items}
1341             } else {
1342 0         0 my ($x, $y) = @_;
1343 0         0 my @r;
1344 0         0 for my $i (@{$self->{items}}) {
  0         0  
1345 0 0 0     0 push @r, $i if ($i->{x} == $x) && ($i->{y} == $y);
1346             }
1347 0         0 return \@r;
1348             }
1349             }
1350              
1351             =item mobs ([x, y])
1352              
1353             Returns reference to array of all mobs located at x/y, or all mobs if no x/y is supplied.
1354              
1355             =cut
1356              
1357             sub mobs {
1358 0     0 1 0 my $self = shift;
1359 0 0       0 if (!@_) {
1360 0         0 return $self->{mobs}
1361             } else {
1362 0         0 my ($x, $y) = @_;
1363 0         0 my @r;
1364 0         0 for my $m (@{$self->{mobs}}) {
  0         0  
1365 0 0 0     0 push @r, $m if ($m->{x} == $x) && ($m->{y} == $y);
1366             }
1367 0         0 return \@r;
1368             }
1369             }
1370              
1371             =item checkpov (vp, x, y)
1372              
1373             Returns 1 if the $vp object (something that has an x, a y and a pov) can see x/y.
1374              
1375             Uses the "noview" area variable to determine what can be seen through.
1376              
1377             =cut
1378              
1379             # this is used to show monster at the current location
1380             sub checkpov {
1381 2171     2171 1 2627 my $self = shift;
1382 2171         3446 my ($vp, $x, $y) = @_;
1383 2171 50       6122 return 1 if (!$vp); # no viewpoint, draw everything
1384 2171 50 33     12435 return 1 if ($vp->{pov}<0 || !defined($vp->{pov})); # see all
1385 2171 50       6859 return 0 if ($vp->{pov}==0); # blind
1386 2171         2913 my $vx = $vp->{x};
1387 2171         3453 my $vy = $vp->{y};
1388              
1389 2171         113850 my $dist = distance($vx, $vy, $x, $y);
1390            
1391 2171 100       12429 return 0 unless $dist <= $vp->{pov};
1392              
1393 602 100       3441 return 1 if $dist <= 1; # always see close
1394              
1395 584 50       1738 print "---FOV2: $vx, $vy, $x, $y D:$dist\n" if $self->{debugfov};
1396 584 50       1767 if ($OKINLINEPOV) {
1397 0 0       0 print "using inline pov\n" if $self->{debugfov};
1398 0 0       0 return checkpov_c($vx, $vy, $x, $y, $self->{map}, $self->{noview}, $self->{debugfov} ? 1 : 0);
1399             }
1400              
1401             # here's where we need to actually do some field of view calculations
1402              
1403 584         935 my $dx = $x-$vx;
1404 584         807 my $dy = $y-$vy;
1405              
1406             # trace 4 parallel rays from corner to corner
1407             # without cosines!
1408             # this code allows diagonal blocking pillars
1409              
1410 584         1273 my @ok = (1,1,1,1);
1411 584         2147 for (my $i = 1; $i <= $dist; $i+=0.5) {
1412 3029         7424 my $tx = $vx+($i/$dist)*$dx; # delta-fraction of distance
1413 3029         14051 my $ty = $vy+($i/$dist)*$dy;
1414              
1415 3029         3329 my (@x, @y);
1416 3029         7116 $x[0] = (0.1+$tx); # not quite the corners
1417 3029         3838 $y[0] = (0.1+$ty);
1418 3029         4737 $x[1] = (0.9+$tx);
1419 3029         4491 $y[1] = (0.9+$ty);
1420 3029         4558 $x[2] = (0.9+$tx);
1421 3029         33698 $y[2] = (0.1+$ty);
1422 3029         8381 $x[3] = (0.1+$tx);
1423 3029         6607 $y[3] = (0.9+$ty);
1424              
1425 3029         5728 my $ok = 0;
1426 3029         10476 for (my $j = 0; $j < 4; ++$j) {
1427 12116 100       42968 next if !$ok[$j];
1428 8866 100 100     39716 if (int($x[$j]) eq $x && int($y[$j]) eq $y) {
1429 455 50       985 print "$i: sub $j: $x[$j],$y[$j] SAME ($self->{map}->[$x[$j]][$y[$j]])\n" if $self->{debugfov};
1430 455         1535 next;
1431             }
1432 8411 50 100     127452 if ($dx != 0 && $dy != 0 && (abs($dx/$dy) > 0.1) && (abs($dy/$dx) > 0.1)) {
      66        
      66        
1433             # allow peeking around corners if target is near the edge
1434 7441 100 100     40427 if (round($x[$j]) eq $x && round($y[$j]) eq $y && $i >= ($dist -1)) {
      100        
1435 78 50       178 print "$i: sub $j: $x[$j],$y[$j] PEEK ($self->{map}->[$x[$j]][$y[$j]])\n" if $self->{debugfov};
1436 78         226 next;
1437             }
1438             }
1439 8333 100       46632 if (($self->{map}->[$x[$j]][$y[$j]] =~ /^(#|\+)$/)) {
1440 1866         5378 $ok[$j] = 0;
1441 1866 50       7917 print "$i: sub $j: $x[$j],$y[$j] WALL ($self->{map}->[$x[$j]][$y[$j]])\n" if $self->{debugfov};
1442             } else {
1443 6467 50       44715 print "$i: sub $j: $x[$j],$y[$j] OK ($self->{map}->[$x[$j]][$y[$j]])\n" if $self->{debugfov};
1444             }
1445             }
1446 3029 100 66     30430 return 0 if !$ok[0] && !$ok[1] && !$ok[2] && !$ok[3];
      66        
      66        
1447             }
1448 174         821 return 1;
1449             }
1450              
1451             =item checkmap (vp, x, y, sym)
1452              
1453             Returns 1 if the $vp mob can see x/y, 2 if $vp has a memory of x/y,
1454             and also memorizes x/y if it can be seen by adding it to the vp's "memory" variable..
1455              
1456             Uses the "noview" area variable to determine what can be seen through.
1457              
1458             =cut
1459              
1460             # this is used to show the map
1461             sub checkmap {
1462 2160     2160 1 3272 my $self = shift;
1463 2160         5439 my ($vp, $x, $y, $sym) = @_;
1464 2160 50 33     15116 if ($vp && $vp->{hasmem}) {
1465 2160 100       7035 if ($self->checkpov($vp, $x, $y)) {
1466 186         1547 $vp->{memory}->{$self->{name}}->[$x][$y]=$sym;
1467 186         1109 return 1;
1468             }
1469             # $self->dprint("mem $self->{name}: $x,$y") if $vp->{memory}->{$self->{name}}->[$x][$y];
1470 1974 50       7822 return 2 if $vp->{memory}->{$self->{name}}->[$x][$y];
1471 1974         6925 return 0;
1472             } else {
1473 0         0 return $self->checkpov($vp, $x, $y);
1474             }
1475             }
1476              
1477             =item addmob (mob)
1478              
1479             Adds a mob to the area, unless it's already in it.
1480              
1481             =cut
1482              
1483             sub addmob {
1484 16     16 1 31 my $self = shift;
1485 16         23 my $m = shift;
1486 16         21 for (@{$self->{mobs}}) {
  16         50  
1487 1 50       5 return 0 if $_ eq $m;
1488             }
1489 16         34 push @{$self->{mobs}}, $m;
  16         43  
1490 16         42 return $m;
1491             }
1492              
1493             =item delmob (mob)
1494              
1495             Removes mob from the area.
1496              
1497             =cut
1498              
1499             sub delmob {
1500 1     1 1 1 my $self = shift;
1501 1         2 my $m = shift;
1502 1         2 my $i = 0;
1503 1         1 for (@{$self->{mobs}}) {
  1         4  
1504 1 50       4 splice @{$self->{mobs}}, $i, 1 if $_ == $m;
  1         3  
1505 1         4 ++$i;
1506             }
1507             }
1508              
1509             =item findrandmap (symbol[, mobok=0])
1510              
1511             Finds a random map location containing symbol (if mobok is not set, then it won't returns locations that have mobs).
1512              
1513             =cut
1514              
1515             sub findrandmap {
1516 6     6 1 26 my $self = shift;
1517 6         15 my $sym = shift;
1518 6         12 my $mobok = shift;
1519              
1520 6         13 my $index = [];
1521              
1522 6         30 for (my $x = 0; $x < $self->{w}; ++$x) {
1523 407         1126 for (my $y = 0; $y < $self->{h}; ++$y) {
1524 17621 50 33     84253 push @{$index}, [$x, $y]
  2748   66     13343  
      66        
1525             if defined($self->{map}->[$x][$y])
1526             && ($self->{map}->[$x][$y] eq $sym && ($mobok || !$self->mobat($x,$y)));
1527             }
1528             }
1529 6         51 $self->{index}->{$sym} = $index;
1530              
1531 6         54 my $i = int(rand() * scalar(@{$index}));
  6         39  
1532              
1533 6         80 return $index->[$i]->[0], $index->[$i]->[1];
1534             }
1535              
1536             =item dump
1537              
1538             Prints map to stdout, without mobs and items. For a more flexible approach, create a ::Console::Dump object and call draw() using it.
1539              
1540             =cut
1541              
1542             sub dump {
1543 0     0 1 0 my $self = shift;
1544              
1545 0         0 my $ox = 0;
1546 0         0 my $oy = 0;
1547              
1548 0         0 my ($xx, $xy, $mx, $my) = (0, 0, $self->{w}, $self->{h});
1549 0         0 for (my $y = 0; $y < $self->{h}; ++$y) {
1550 0         0 for (my $x = 0; $x < $self->{w}; ++$x) {
1551 0 0       0 if ($self->{map}->[$x][$y]) {
1552 0 0       0 $mx = $x if ($x < $mx);
1553 0 0       0 $my = $y if ($y < $my);
1554 0 0       0 $xx = $x if ($x > $xx);
1555 0 0       0 $xy = $y if ($y > $xy);
1556             }
1557             }
1558             }
1559              
1560 0         0 $ox=max($ox, $mx);
1561 0         0 $oy=max($oy, $my);
1562              
1563             #actual map drawn at user-requested location/virtual window
1564 0   0     0 for (my $y = $oy; $y < $self->{h} && $y <= $xy; ++$y) {
1565 0   0     0 for (my $x = $ox; $x < $self->{w} && $x <= $xx; ++$x) {
1566 0 0       0 print $self->{map}->[$x][$y] ? $self->{map}->[$x][$y] : ' ';
1567             }
1568 0         0 print "\n";
1569             }
1570             }
1571              
1572             =item additem (item)
1573              
1574             Adds item to floor. Override this to add floor full messages, etc.
1575              
1576             Return value 0 = can't add, too full
1577             Return value 1 = add ok
1578             Return value -1 = move occured, but not added
1579              
1580             If the item doesn't have an {x} and {y} value then the item is added to a random location.
1581              
1582             =cut
1583              
1584             sub additem {
1585 1     1 1 2 my $self = shift;
1586 1         3 my $item = shift;
1587 1 50       3 if ($item->setcont($self)) {
1588 1 50       4 if (!defined($item->{x})) {
1589 0         0 ($item->{x}, $item->{y}) = $self->findrandmap($self->{fsym}, 0, 1);
1590             }
1591             }
1592 1         3 return 1; # i'm never full
1593             }
1594              
1595             =item delitem (item)
1596              
1597             Removes item from the area.
1598              
1599             =cut
1600              
1601             sub delitem {
1602 0     0 1 0 my $self = shift;
1603 0         0 my $ob = shift;
1604 0         0 my $i = 0;
1605 0         0 for (@{$self->{items}}) {
  0         0  
1606 0 0       0 splice @{$self->{items}}, $i, 1 if $_ == $ob;
  0         0  
1607 0         0 ++$i;
1608             }
1609             }
1610              
1611             =item load (file | options)
1612              
1613             Loads an area from a file, which is a perl program that sets these vars:
1614              
1615             $map : 2d map as one big string
1616             or $yxarray : 2d map as y then x indexed array
1617             %key : for each symbol in the map *optionally* provide:
1618             color - color of that symbol
1619             sym - real symbol to use
1620             feature - name of feature for feature table, don't specify with class!
1621             class - optional package to use for "new", passed the area itself as the first argument to new
1622             lib - look up item or mob from library
1623              
1624             %lib : hash of hashes, used to populate items or monsters - as needed
1625              
1626             Alternatively, these can be passed as named options to the load function.
1627              
1628             The map system knows very little about game semantics. It's merely a way of loading maps
1629             made of symbols - some of which may correlate to perl objects. The tictactoe example script
1630             uses the map load system.
1631              
1632             '>', and '<' are assumed to be "stair features" unless otherwise specified.
1633              
1634             Objects can be looked up by name from the item library instead of specified in full.
1635              
1636             At a minimum, classes must add themselves, somehow, to the area object when new is called.
1637              
1638             The example below loads a standard map, with blue doors, 2 mobs and 1 item
1639              
1640             One mob is loaded via a package "mymonster", and is passed "hd" & "name" parameters,
1641             in addition to the "x", "y" and "sym" parameters which are derived from its location.
1642              
1643             The other mob is loaded from the library named "blue dragon", and has it's "name" and "hp"
1644             parameters modified.
1645              
1646             About lib entries:
1647              
1648             If a key entry has a "lib", it's assumed to the be the name of an entry in the lib hash.
1649              
1650             The lib hash is then looked up and has its values copied into the key entry before using the key entry.
1651              
1652             "lib" entries can be recursive.
1653              
1654             The "lib" can be loaded from an external shared file, so multiple maps can use the same "lib".
1655              
1656             About items:
1657              
1658             The "items" member of an object (mob or backpack), if it is an array reference, will be auto-expanded
1659             by creating an item object for each array member with the parent object set as the first, unnamed, argument to new.
1660              
1661             If a member of the items array is a hash ref, it's treated like a key entry. If it's a scalar string, it's
1662             equivalent to {lib=>'string'}.
1663              
1664             If there's no class set within an item, a warning is emitted.
1665              
1666             EXAMPLE 1:
1667              
1668             $map = '
1669             ##########
1670             #k <+ ! #
1671             ######## #
1672             #> D #
1673             ##########
1674             ';
1675              
1676             %key = (
1677             'k'=>{class=>'mymonster', type='kobold', name=>'Harvey', hd=>12,
1678             items=>['potion of healing',
1679             {class=>'myweapon', name=>'Blue Sword', hd=>9, dd=>4, drain=>1, glow=>1}
1680             ]
1681             },
1682             '!'=>{lib=>'potion of speed'},
1683             'D'=>{lib=>'blue dragon', name=>'Charlie', hp=>209},
1684             '+'=>{color=>'blue'}
1685             );
1686              
1687             %lib = (
1688             'potion of speed'=>{class=>'myitem', type=>'potion', effect=>'speed', power=>1},
1689             'blue dragon'=>{class=>'mymob', type=>'dragon', breath=>'lightning', hp=>180, hd=>12, at=>[10,5], dm=>[5,10], speed=>5, loot=>4},
1690             );
1691              
1692             $area->load(map=>$map, key=>%key, lib=>\%lib);
1693              
1694             EXAMPLE 2:
1695            
1696             use Games::Roguelike::Caves;
1697             my $yx = generate_cave($r->{w},$r->{h}, 12, .46, '#', '.');
1698             $area->load(yxarray=>$yx);
1699              
1700             =cut
1701              
1702             sub load {
1703 2     2 1 11 my $self = shift;
1704              
1705 2 50       8 confess("cannot call load without a filename or a map/key and lib")
1706             if (!@_);
1707              
1708 2         4 my $map;
1709             my %key;
1710 0         0 my %lib;
1711              
1712 0         0 my ($fn);
1713 0         0 my %opts;
1714              
1715 2 50       9 if (@_ == 1) {
1716 0         0 ($fn) = @_;
1717             } else {
1718 2         8 %opts = @_;
1719 2         6 $fn = $opts{file};
1720             }
1721              
1722 2 50       7 if ($fn) {
1723 0         0 eval {
1724 5     5   7982 use Safe;
  5         364452  
  5         11937  
1725 0         0 my $in = new Safe;
1726 0         0 $in->permit_only(':base_core');
1727 0         0 $in->rdo($fn);
1728 0         0 $map = $in->reval('$map');
1729 0         0 %key = $in->reval('%key');
1730 0         0 %lib = $in->reval('%lib');
1731             };
1732             } else {
1733 2         6 $map = $opts{map};
1734 2 100       10 %key = %{$opts{key}} if $opts{key};
  1         7  
1735 2 100       8 %lib = %{$opts{lib}} if $opts{lib};
  1         5  
1736             }
1737              
1738 2         4 my $mapyx;
1739              
1740 2 50 33     10 if ($opts{yxarray} && ref($opts{yxarray})) {
1741 0         0 $mapyx = $opts{yxarray};
1742             } else {
1743 2 50       9 if (!$map) {
1744 0         0 cluck("no 'map' or 'xyarray' found in parameters");
1745 0         0 return 0;
1746             }
1747 2         17 $map =~ s/^[\r\n]+//;
1748 2         16 $map =~ s/[\r\n]+$//;
1749 2         21 my @ylines = split(/[\r\n]/,$map);
1750 2         5 my $y = 0;
1751 2         6 for (@ylines) {
1752 8         42 my @l = split(//, $_);
1753 8         42 $mapyx->[$y++]= \@l;
1754             }
1755             }
1756              
1757 2         12 $self->{map} = [];
1758 2         6 $self->{color} = [];
1759              
1760 2         4 my $y = 0;
1761 2         5 for (@{$mapyx}) {
  2         5  
1762 8         11 my $x = 0;
1763 8         10 for (@{$mapyx->[$y]}) {
  8         14  
1764 71         88 my $sym = $mapyx->[$y][$x];
1765 71         137 expandkey($sym, \%key, \%lib);
1766 71         94 my $opt = $key{$sym};
1767 71 100       106 if ($opt) {
1768 4 100       12 if ($opt->{sym}) {
1769 2         5 $sym = $self->{map}->[$x][$y] = $opt->{sym};
1770             }
1771 4 100       10 if ($opt->{class}) {
1772 3         7 $self->{map}->[$x][$y] = $self->{fsym};
1773 3         4 my $ob;
1774 3         11 my ($cpack) = caller;
1775 3         4 eval {$ob = $opt->{class}->new($self, x=>$x, y=>$y, sym=>$sym, %{$opt});};
  3         6  
  3         36  
1776 3 50       20 carp "failed to create $opt->{class}: $@" if !$ob;
1777 3 100       10 if (ref($opt->{items}) eq 'ARRAY') {
1778 1         2 for(@{$opt->{items}}) {
  1         4  
1779 3 100       9 if (!ref($_)) {
1780 1         3 expandkey($_, \%lib, \%lib);
1781 1         3 $_ = $lib{$_};
1782             } else {
1783 2         5 expandhash($_, \%lib);
1784             }
1785 3         6 my $it;
1786 3         3 eval {$it = $_->{class}->new($ob, %{$_});};
  3         8  
  3         47  
1787 3 50       20 carp "failed to create $_->{class}: $@" if !$ob;
1788             }
1789             }
1790             } else {
1791 1 50       5 if ($opt->{feature}) {
1792 0         0 push @{$self->{f}}, [$x, $y];
  0         0  
1793             }
1794 1         4 $self->{map}->[$x][$y] = $sym;
1795 1         4 $self->{color}->[$x][$y] = $opt->{color};
1796             }
1797             } else {
1798 67         170 $self->{map}->[$x][$y] = $mapyx->[$y][$x];
1799             }
1800 71         102 $x++;
1801             }
1802 8         20 $y++;
1803             }
1804              
1805 2         3 $self->{w} = @{$self->{map}};
  2         7  
1806 2         2 $self->{h} = @{$self->{map}->[0]};
  2         16  
1807             }
1808              
1809             # this looks in the hash "key" for an entry called "lib", which should be a string
1810             # it then looks in the hash "lib" for that string
1811             # finally it copied the keys from "lib" to the hash "key"
1812             # really no reason for 2 hashes... 1 would suffice (for map keys and lib entries),
1813             # but i think it's easier to keep track of for the users that way
1814              
1815             sub expandhash {
1816 9     9 0 13 my ($hash, $lib) = @_;
1817              
1818 9 50       19 return if !$hash;
1819 9 50       21 return if $hash->{__lib__};
1820 9 100       29 return if !(my $libname = $hash->{lib});
1821              
1822 2 50       7 croak "no entry for '$libname'"
1823             if !$lib->{$libname};
1824              
1825             # allow recursion
1826 2         8 expandhash($lib, $lib);
1827              
1828 2         10 for (keys(%{$lib->{$libname}})) {
  2         17  
1829 15 50       26 next if $_ eq 'lib';
1830 15         61 $hash->{$_}=$lib->{$libname}->{$_};
1831             }
1832              
1833             }
1834              
1835             sub expandkey {
1836 72     72 0 98 my ($index, $key, $lib) = @_;
1837 72 100       164 return if !$key->{$index};
1838 5         27 expandhash($key->{$index}, $lib);
1839             }
1840              
1841              
1842             sub dprint {
1843 0     0 0 0 my $self=shift;
1844 0 0       0 if ($self->{world}) {
1845 0         0 $self->{world}->dprint(@_)
1846             } else {
1847 0         0 print @_, "\n";
1848             }
1849             }
1850              
1851             =back
1852              
1853             =cut
1854              
1855             # this allows the "generate" function to work and support the old interface
1856              
1857             package Games::Roguelike::Area::Rooms;
1858 5     5   72 use base 'Games::Roguelike::Area';
  5         12  
  5         3887  
1859              
1860             sub generate {
1861 0     0   0 Games::Roguelike::Area::genmaze1(@_);
1862             }
1863              
1864             package Games::Roguelike::Area::Cavelike;
1865 5     5   39 use base 'Games::Roguelike::Area';
  5         12  
  5         981  
1866              
1867             sub generate {
1868 2     2   34 Games::Roguelike::Area::genmaze2(@_);
1869             }
1870              
1871             package Games::Roguelike::Area::Maze;
1872 5     5   37 use base 'Games::Roguelike::Area';
  5         15  
  5         646  
1873              
1874             sub generate {
1875 0     0     Games::Roguelike::Area::genmaze3(@_);
1876             }
1877              
1878             =head1 SEE ALSO
1879              
1880             L, L, L
1881              
1882             =head1 AUTHOR
1883              
1884             Erik Aronesty C
1885              
1886             =head1 LICENSE
1887              
1888             This program is free software; you can redistribute it and/or
1889             modify it under the same terms as Perl itself.
1890              
1891             See L or the included LICENSE file.
1892              
1893             =cut
1894              
1895             1;
1896