|  line  | 
 stmt  | 
 bran  | 
 cond  | 
 sub  | 
 pod  | 
 time  | 
 code  | 
| 
1
 | 
  
 
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # Copyright (C) 2009-2021  Alex Schroeder   | 
| 
2
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 #  | 
| 
3
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # This program is free software: you can redistribute it and/or modify it under  | 
| 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # the terms of the GNU Affero General Public License as published by the Free  | 
| 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # Software Foundation, either version 3 of the License, or (at your option) any  | 
| 
6
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # later version.  | 
| 
7
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 #  | 
| 
8
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # This program is distributed in the hope that it will be useful, but WITHOUT  | 
| 
9
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS  | 
| 
10
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more  | 
| 
11
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # details.  | 
| 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 #  | 
| 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # You should have received a copy of the GNU Affero General Public License along  | 
| 
14
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # with this program. If not, see .  | 
| 
15
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
16
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =encoding utf8  | 
| 
17
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
18
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 NAME  | 
| 
19
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
20
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Game::TextMapper::Apocalypse - generate postapocalyptic landscape  | 
| 
21
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
22
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 SYNOPSIS  | 
| 
23
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
24
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     use Modern::Perl;  | 
| 
25
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     use Game::TextMapper::Apocalypse;  | 
| 
26
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     my $map = Game::TextMapper::Apocalypse->new->generate_map();  | 
| 
27
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     print $map;  | 
| 
28
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
29
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 DESCRIPTION  | 
| 
30
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
31
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 This fills the map with random seed regions which then grow to fill the map.  | 
| 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
33
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Settlements are placed at random.  | 
| 
34
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
35
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Every mountain region is the source of a river. Rivers flow through regions that  | 
| 
36
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 are not themselves mountains or a deserts. Rivers end in swamps.  | 
| 
37
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
38
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =cut  | 
| 
39
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
40
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 package Game::TextMapper::Apocalypse;  | 
| 
41
 | 
1
 | 
 
 | 
 
 | 
  
1
  
 | 
 
 | 
6
 | 
 use Game::TextMapper::Log;  | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2
 | 
    | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
27
 | 
    | 
| 
42
 | 
1
 | 
 
 | 
 
 | 
  
1
  
 | 
 
 | 
5
 | 
 use Modern::Perl '2018';  | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2
 | 
    | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
5
 | 
    | 
| 
43
 | 
1
 | 
 
 | 
 
 | 
  
1
  
 | 
 
 | 
115
 | 
 use List::Util qw(shuffle any none);  | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2
 | 
    | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
69
 | 
    | 
| 
44
 | 
1
 | 
 
 | 
 
 | 
  
1
  
 | 
 
 | 
4
 | 
 use Mojo::Base -base;  | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
10
 | 
    | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
    | 
| 
45
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
46
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 my $log = Game::TextMapper::Log->get;  | 
| 
47
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
48
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 ATTRIBUTES  | 
| 
49
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head2 rows  | 
| 
51
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
52
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 The height of the map, defaults to 10.  | 
| 
53
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
54
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     use Modern::Perl;  | 
| 
55
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     use Game::TextMapper::Apocalypse;  | 
| 
56
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     my $map = Game::TextMapper::Apocalypse->new(rows => 20)  | 
| 
57
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         ->generate_map;  | 
| 
58
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     print $map;  | 
| 
59
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
60
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head2 cols  | 
| 
61
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
62
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 The width of the map, defaults to 20.  | 
| 
63
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     use Modern::Perl;  | 
| 
65
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     use Game::TextMapper::Apocalypse;  | 
| 
66
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     my $map = Game::TextMapper::Apocalypse->new(cols => 30)  | 
| 
67
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         ->generate_map;  | 
| 
68
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     print $map;  | 
| 
69
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
70
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head2 region_size  | 
| 
71
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
72
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 The size of regions sharing the same terrain type, on average, defaults to 5  | 
| 
73
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 hexes. The algorithm computes the number hexes, divides it by the region size,  | 
| 
74
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 and that's the number of seeds it starts with (C × C ÷  | 
| 
75
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 C).  | 
| 
76
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
77
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     use Modern::Perl;  | 
| 
78
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     use Game::TextMapper::Apocalypse;  | 
| 
79
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     my $map = Game::TextMapper::Apocalypse->new(region_size => 3)  | 
| 
80
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         ->generate_map;  | 
| 
81
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     print $map;  | 
| 
82
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
83
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head2 settlement_chance  | 
| 
84
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
85
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 The chance of a hex containing a settlement, from 0 to 1, defaults to 0.1 (10%).  | 
| 
86
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
87
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     use Modern::Perl;  | 
| 
88
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     use Game::TextMapper::Apocalypse;  | 
| 
89
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     my $map = Game::TextMapper::Apocalypse->new(settlement_chance => 0.2)  | 
| 
90
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         ->generate_map;  | 
| 
91
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     print $map;  | 
| 
92
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
93
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head2 loglevel  | 
| 
94
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
95
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 By default, the log level is set by L from the config file. If  | 
| 
96
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 you use the generator on its own, however, the log defaults to log level  | 
| 
97
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 "debug". You might want to change that. The options are "error", "warn", "info"  | 
| 
98
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 and "debug".  | 
| 
99
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
100
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     use Modern::Perl;  | 
| 
101
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     use Game::TextMapper::Apocalypse;  | 
| 
102
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     my $map = Game::TextMapper::Apocalypse->new(loglevel => 'error')  | 
| 
103
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         ->generate_map;  | 
| 
104
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     print $map;  | 
| 
105
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
106
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =cut  | 
| 
107
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
108
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 has 'rows' => 10;  | 
| 
109
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 has 'cols' => 20;  | 
| 
110
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 has 'region_size' => 5;  | 
| 
111
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 has 'settlement_chance' => 0.1;  | 
| 
112
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 has 'loglevel';  | 
| 
113
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
114
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 my @tiles = qw(forest desert mountain jungle swamp grass);  | 
| 
115
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 my @settlements = qw(ruin fort cave);  | 
| 
116
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
117
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 METHODS  | 
| 
118
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
119
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head2 generate_map  | 
| 
120
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
121
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 This method takes no arguments. Set the properties of the map using the  | 
| 
122
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 attributes.  | 
| 
123
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
124
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =cut  | 
| 
125
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
126
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub generate_map {  | 
| 
127
 | 
1
 | 
 
 | 
 
 | 
  
1
  
 | 
  
1
  
 | 
10
 | 
   my $self = shift;  | 
| 
128
 | 
1
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
5
 | 
   $log->level($self->loglevel) if $self->loglevel;  | 
| 
129
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
12
 | 
   my @coordinates = shuffle(0 .. $self->rows * $self->cols - 1);  | 
| 
130
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
32
 | 
   my $seeds = $self->rows * $self->cols / $self->region_size;  | 
| 
131
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
11
 | 
   my $tiles = [];  | 
| 
132
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
37
 | 
   $tiles->[$_] = [$tiles[int(rand(@tiles))]] for splice(@coordinates, 0, $seeds);  | 
| 
133
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
7
 | 
   $tiles->[$_] = [$self->close_to($_, $tiles)] for @coordinates;  | 
| 
134
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   # warn "$_\n" for $self->neighbours(0);  | 
| 
135
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   # push(@{$tiles->[$_]}, "red") for map { $self->neighbours($_) } 70, 75;  | 
| 
136
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   # push(@{$tiles->[$_]}, "red") for map { $self->neighbours($_) } 3, 8, 60, 120;  | 
| 
137
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   # push(@{$tiles->[$_]}, "red") for map { $self->neighbours($_) } 187, 194, 39, 139;  | 
| 
138
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   # push(@{$tiles->[$_]}, "red") for map { $self->neighbours($_) } 0, 19, 180, 199;  | 
| 
139
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   # push(@{$tiles->[$_]}, "red") for map { $self->neighbours($_) } 161;  | 
| 
140
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
5
 | 
   for my $tile (@$tiles) {  | 
| 
141
 | 
200
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
809
 | 
     push(@$tile, $settlements[int(rand(@settlements))]) if rand() < $self->settlement_chance;  | 
| 
142
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   }  | 
| 
143
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
11
 | 
   my $rivers = $self->rivers($tiles);  | 
| 
144
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
   return $self->to_text($tiles, $rivers);  | 
| 
145
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
146
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
147
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub neighbours {  | 
| 
148
 | 
284
 | 
 
 | 
 
 | 
  
284
  
 | 
  
0
  
 | 
337
 | 
   my $self = shift;  | 
| 
149
 | 
284
 | 
 
 | 
 
 | 
 
 | 
 
 | 
309
 | 
   my $coordinate = shift;  | 
| 
150
 | 
284
 | 
 
 | 
 
 | 
 
 | 
 
 | 
316
 | 
   my @offsets;  | 
| 
151
 | 
284
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
433
 | 
   if ($coordinate % 2) {  | 
| 
152
 | 
141
 | 
 
 | 
 
 | 
 
 | 
 
 | 
229
 | 
     @offsets = (-1, +1, $self->cols, -$self->cols, $self->cols -1, $self->cols +1);  | 
| 
153
 | 
141
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
1151
 | 
     $offsets[3] = undef if $coordinate < $self->cols; # top edge  | 
| 
154
 | 
141
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
562
 | 
     $offsets[2] = $offsets[4] = $offsets[5] = undef if $coordinate >= ($self->rows - 1) * $self->cols; # bottom edge  | 
| 
155
 | 
141
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
792
 | 
     $offsets[0] = $offsets[4] = undef if $coordinate % $self->cols == 0; # left edge  | 
| 
156
 | 
141
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
539
 | 
     $offsets[1] = $offsets[5] = undef if $coordinate % $self->cols == $self->cols - 1; # right edge  | 
| 
157
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   } else {  | 
| 
158
 | 
143
 | 
 
 | 
 
 | 
 
 | 
 
 | 
219
 | 
     @offsets = (-1, +1, $self->cols, -$self->cols, -$self->cols -1, -$self->cols +1);  | 
| 
159
 | 
143
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
1209
 | 
     $offsets[3] = $offsets[4] = $offsets[5] = undef if $coordinate < $self->cols; # top edge  | 
| 
160
 | 
143
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
525
 | 
     $offsets[2] = undef if $coordinate >= ($self->rows - 1) * $self->cols; # bottom edge  | 
| 
161
 | 
143
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
744
 | 
     $offsets[0] = $offsets[4] = undef if $coordinate % $self->cols == 0; # left edge  | 
| 
162
 | 
143
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
569
 | 
     $offsets[1] = $offsets[5] = undef if $coordinate % $self->cols == $self->cols - 1; # right edge  | 
| 
163
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   }  | 
| 
164
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   # die "@offsets" if any { $coordinate + $_ < 0 or $coordinate + $_ >= $self->cols * $self->rows } @offsets;  | 
| 
165
 | 
284
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1378
 | 
   return map { $coordinate + $_ } shuffle grep {$_} @offsets;  | 
| 
 
 | 
1538
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1971
 | 
    | 
| 
 
 | 
1704
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2145
 | 
    | 
| 
166
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
167
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
168
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub close_to {  | 
| 
169
 | 
160
 | 
 
 | 
 
 | 
  
160
  
 | 
  
0
  
 | 
205
 | 
   my $self = shift;  | 
| 
170
 | 
160
 | 
 
 | 
 
 | 
 
 | 
 
 | 
173
 | 
   my $coordinate = shift;  | 
| 
171
 | 
160
 | 
 
 | 
 
 | 
 
 | 
 
 | 
164
 | 
   my $tiles = shift;  | 
| 
172
 | 
160
 | 
 
 | 
 
 | 
 
 | 
 
 | 
210
 | 
   for ($self->neighbours($coordinate)) {  | 
| 
173
 | 
309
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
780
 | 
     return $tiles->[$_]->[0] if $tiles->[$_];  | 
| 
174
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   }  | 
| 
175
 | 
9
 | 
 
 | 
 
 | 
 
 | 
 
 | 
36
 | 
   return $tiles[int(rand(@tiles))];  | 
| 
176
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
177
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
178
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub rivers {  | 
| 
179
 | 
1
 | 
 
 | 
 
 | 
  
1
  
 | 
  
0
  
 | 
2
 | 
   my $self = shift;  | 
| 
180
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1
 | 
   my $tiles = shift;  | 
| 
181
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   # the array of rivers has a cell for each coordinate: if there are no rivers,  | 
| 
182
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   # it is undef; else it is a reference to the river  | 
| 
183
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
3
 | 
   my $rivers = [];  | 
| 
184
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
4
 | 
   for my $source (grep { $self->is_source($_, $tiles) } 0 .. $self->rows * $self->cols - 1) {  | 
| 
 
 | 
200
 | 
 
 | 
 
 | 
 
 | 
 
 | 
292
 | 
    | 
| 
185
 | 
43
 | 
 
 | 
 
 | 
 
 | 
 
 | 
80
 | 
     $log->debug("River starting at " . $self->xy($source) . " (@{$tiles->[$source]})");  | 
| 
 
 | 
43
 | 
 
 | 
 
 | 
 
 | 
 
 | 
393
 | 
    | 
| 
186
 | 
43
 | 
 
 | 
 
 | 
 
 | 
 
 | 
227
 | 
     my $river = [$source];  | 
| 
187
 | 
43
 | 
 
 | 
 
 | 
 
 | 
 
 | 
74
 | 
     $self->grow_river($source, $river, $rivers, $tiles);  | 
| 
188
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   }  | 
| 
189
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
7
 | 
   return $rivers;  | 
| 
190
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
191
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
192
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub grow_river {  | 
| 
193
 | 
124
 | 
 
 | 
 
 | 
  
124
  
 | 
  
0
  
 | 
144
 | 
   my $self = shift;  | 
| 
194
 | 
124
 | 
 
 | 
 
 | 
 
 | 
 
 | 
147
 | 
   my $coordinate = shift;  | 
| 
195
 | 
124
 | 
 
 | 
 
 | 
 
 | 
 
 | 
141
 | 
   my $river = shift;  | 
| 
196
 | 
124
 | 
 
 | 
 
 | 
 
 | 
 
 | 
139
 | 
   my $rivers = shift;  | 
| 
197
 | 
124
 | 
 
 | 
 
 | 
 
 | 
 
 | 
129
 | 
   my $tiles = shift;  | 
| 
198
 | 
124
 | 
 
 | 
 
 | 
 
 | 
 
 | 
195
 | 
   my @destinations = shuffle grep { $self->is_destination($_, $river, $rivers, $tiles) } $self->neighbours($coordinate);  | 
| 
 
 | 
676
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1034
 | 
    | 
| 
199
 | 
124
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
260
 | 
   return unless @destinations; # this is a dead end  | 
| 
200
 | 
107
 | 
 
 | 
 
 | 
 
 | 
 
 | 
166
 | 
   for my $next (@destinations) {  | 
| 
201
 | 
113
 | 
 
 | 
 
 | 
 
 | 
 
 | 
193
 | 
     push(@$river, $next);  | 
| 
202
 | 
113
 | 
 
 | 
 
 | 
 
 | 
 
 | 
188
 | 
     $log->debug(" " . $self->xy($river));  | 
| 
203
 | 
113
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
1502
 | 
     if ($rivers->[$next]) {  | 
| 
 
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
204
 | 
24
 | 
 
 | 
 
 | 
 
 | 
 
 | 
48
 | 
       $log->debug(" merge!");  | 
| 
205
 | 
24
 | 
 
 | 
 
 | 
 
 | 
 
 | 
110
 | 
       my @other = @{$rivers->[$next]};  | 
| 
 
 | 
24
 | 
 
 | 
 
 | 
 
 | 
 
 | 
76
 | 
    | 
| 
206
 | 
24
 | 
 
 | 
 
 | 
 
 | 
 
 | 
47
 | 
       while ($other[0] != $next) { shift @other };  | 
| 
 
 | 
95
 | 
 
 | 
 
 | 
 
 | 
 
 | 
139
 | 
    | 
| 
207
 | 
24
 | 
 
 | 
 
 | 
 
 | 
 
 | 
25
 | 
       shift @other; # get rid of the duplicate $next  | 
| 
208
 | 
24
 | 
 
 | 
 
 | 
 
 | 
 
 | 
67
 | 
       push(@$river, @other);  | 
| 
209
 | 
24
 | 
 
 | 
 
 | 
 
 | 
 
 | 
40
 | 
       return $self->mark_river($river, $rivers);  | 
| 
210
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     } elsif ($self->is_sink($next, $tiles)) {  | 
| 
211
 | 
8
 | 
 
 | 
 
 | 
 
 | 
 
 | 
23
 | 
       $log->debug("  done!");  | 
| 
212
 | 
8
 | 
 
 | 
 
 | 
 
 | 
 
 | 
44
 | 
       return $self->mark_river($river, $rivers);  | 
| 
213
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     } else {  | 
| 
214
 | 
81
 | 
 
 | 
 
 | 
 
 | 
 
 | 
192
 | 
       my $result = $self->grow_river($next, $river, $rivers, $tiles);  | 
| 
215
 | 
81
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
194
 | 
       return $result if $result;  | 
| 
216
 | 
7
 | 
 
 | 
 
 | 
 
 | 
 
 | 
20
 | 
       $log->debug("  dead end!");  | 
| 
217
 | 
7
 | 
 
 | 
 
 | 
 
 | 
 
 | 
37
 | 
       $rivers->[$next] = 0; # prevents this from being a destination  | 
| 
218
 | 
7
 | 
 
 | 
 
 | 
 
 | 
 
 | 
17
 | 
       pop(@$river);  | 
| 
219
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
220
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   }  | 
| 
221
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
11
 | 
   return; # all destinations were dead ends  | 
| 
222
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
223
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
224
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub mark_river {  | 
| 
225
 | 
32
 | 
 
 | 
 
 | 
  
32
  
 | 
  
0
  
 | 
45
 | 
   my $self = shift;  | 
| 
226
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
40
 | 
   my $river = shift;  | 
| 
227
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
33
 | 
   my $rivers = shift;  | 
| 
228
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
43
 | 
   for my $coordinate (@$river) {  | 
| 
229
 | 
333
 | 
 
 | 
 
 | 
 
 | 
 
 | 
412
 | 
     $rivers->[$coordinate] = $river;  | 
| 
230
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   }  | 
| 
231
 | 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
75
 | 
   return $river;  | 
| 
232
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
233
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
234
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub is_source {  | 
| 
235
 | 
200
 | 
 
 | 
 
 | 
  
200
  
 | 
  
0
  
 | 
219
 | 
   my $self = shift;  | 
| 
236
 | 
200
 | 
 
 | 
 
 | 
 
 | 
 
 | 
217
 | 
   my $coordinate = shift;  | 
| 
237
 | 
200
 | 
 
 | 
 
 | 
 
 | 
 
 | 
208
 | 
   my $tiles = shift;  | 
| 
238
 | 
200
 | 
 
 | 
 
 | 
  
222
  
 | 
 
 | 
326
 | 
   return any { $_ eq 'mountain' } (@{$tiles->[$coordinate]});  | 
| 
 
 | 
222
 | 
 
 | 
 
 | 
 
 | 
 
 | 
456
 | 
    | 
| 
 
 | 
200
 | 
 
 | 
 
 | 
 
 | 
 
 | 
321
 | 
    | 
| 
239
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
240
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
241
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub is_destination {  | 
| 
242
 | 
676
 | 
 
 | 
 
 | 
  
676
  
 | 
  
0
  
 | 
775
 | 
   my $self = shift;  | 
| 
243
 | 
676
 | 
 
 | 
 
 | 
 
 | 
 
 | 
715
 | 
   my $coordinate = shift;  | 
| 
244
 | 
676
 | 
 
 | 
 
 | 
 
 | 
 
 | 
715
 | 
   my $river = shift;  | 
| 
245
 | 
676
 | 
 
 | 
 
 | 
 
 | 
 
 | 
702
 | 
   my $rivers = shift;  | 
| 
246
 | 
676
 | 
 
 | 
 
 | 
 
 | 
 
 | 
690
 | 
   my $tiles = shift;  | 
| 
247
 | 
676
 | 
  
100
  
 | 
  
100
  
 | 
 
 | 
 
 | 
1235
 | 
   return 0 if defined $rivers->[$coordinate] and $rivers->[$coordinate] == 0;  | 
| 
248
 | 
670
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
1186
 | 
   return 0 if grep { $_ == $coordinate } @$river;  | 
| 
 
 | 
3237
 | 
 
 | 
 
 | 
 
 | 
 
 | 
4648
 | 
    | 
| 
249
 | 
537
 | 
  
100
  
 | 
 
 | 
  
581
  
 | 
 
 | 
1080
 | 
   return none { $_ eq 'mountain' or $_ eq 'desert' } (@{$tiles->[$coordinate]});  | 
| 
 
 | 
581
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1923
 | 
    | 
| 
 
 | 
537
 | 
 
 | 
 
 | 
 
 | 
 
 | 
887
 | 
    | 
| 
250
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
251
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
252
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub is_sink {  | 
| 
253
 | 
89
 | 
 
 | 
 
 | 
  
89
  
 | 
  
0
  
 | 
123
 | 
   my $self = shift;  | 
| 
254
 | 
89
 | 
 
 | 
 
 | 
 
 | 
 
 | 
104
 | 
   my $coordinate = shift;  | 
| 
255
 | 
89
 | 
 
 | 
 
 | 
 
 | 
 
 | 
99
 | 
   my $tiles = shift;  | 
| 
256
 | 
89
 | 
 
 | 
 
 | 
  
99
  
 | 
 
 | 
207
 | 
   return any { $_ eq 'swamp' } (@{$tiles->[$coordinate]});  | 
| 
 
 | 
99
 | 
 
 | 
 
 | 
 
 | 
 
 | 
202
 | 
    | 
| 
 
 | 
89
 | 
 
 | 
 
 | 
 
 | 
 
 | 
211
 | 
    | 
| 
257
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
258
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
259
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub to_text {  | 
| 
260
 | 
1
 | 
 
 | 
 
 | 
  
1
  
 | 
  
0
  
 | 
3
 | 
   my $self = shift;  | 
| 
261
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
   my $tiles = shift;  | 
| 
262
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2
 | 
   my $rivers = shift;  | 
| 
263
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2
 | 
   my $text = "";  | 
| 
264
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
5
 | 
   for my $i (0 .. $self->rows * $self->cols - 1) {  | 
| 
265
 | 
200
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
376
 | 
     $text .= $self->xy($i) . " @{$tiles->[$i]}\n" if $tiles->[$i];  | 
| 
 
 | 
200
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1340
 | 
    | 
| 
266
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   }  | 
| 
267
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
   for my $river (@$rivers) {  | 
| 
268
 | 
196
 | 
  
100
  
 | 
  
 66
  
 | 
 
 | 
 
 | 
1405
 | 
     $text .= $self->xy($river) . " river\n" if ref($river) and @$river > 1;  | 
| 
269
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   }  | 
| 
270
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
32
 | 
   $text .= "\ninclude apocalypse.txt\n";  | 
| 
271
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
40
 | 
   return $text;  | 
| 
272
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
273
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
274
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub xy {  | 
| 
275
 | 
470
 | 
 
 | 
 
 | 
  
470
  
 | 
  
0
  
 | 
610
 | 
   my $self = shift;  | 
| 
276
 | 
470
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
714
 | 
   return join("-", map { sprintf("%02d%02d", $_ % $self->cols + 1, int($_ / $self->cols) + 1) } @_) if @_ > 1;  | 
| 
 
 | 
0
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
    | 
| 
277
 | 
470
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
836
 | 
   return sprintf("%02d%02d", $_[0] % $self->cols + 1, int($_[0] / $self->cols) + 1) unless ref($_[0]);  | 
| 
278
 | 
227
 | 
 
 | 
 
 | 
 
 | 
 
 | 
251
 | 
   return join("-", map { sprintf("%02d%02d", $_ % $self->cols + 1, int($_ / $self->cols) + 1) } @{$_[0]});  | 
| 
 
 | 
2309
 | 
 
 | 
 
 | 
 
 | 
 
 | 
13099
 | 
    | 
| 
 
 | 
227
 | 
 
 | 
 
 | 
 
 | 
 
 | 
333
 | 
    | 
| 
279
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
280
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
281
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 1;  |