| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Settlers::Map; | 
| 2 |  |  |  |  |  |  | $Settlers::Map::VERSION = '0.07'; | 
| 3 | 3 |  |  | 3 |  | 27607 | use strict; | 
|  | 3 |  |  | 1 |  | 6 |  | 
|  | 3 |  |  |  |  | 77 |  | 
|  | 1 |  |  |  |  | 5 |  | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 20 |  | 
| 4 | 3 |  |  | 3 |  | 15 | use warnings; | 
|  | 3 |  |  | 1 |  | 5 |  | 
|  | 3 |  |  |  |  | 74 |  | 
|  | 1 |  |  |  |  | 5 |  | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 23 |  | 
| 5 | 3 |  |  | 3 |  | 1058 | use Settlers::Map::Tile; | 
|  | 3 |  |  | 1 |  | 7 |  | 
|  | 3 |  |  |  |  | 69 |  | 
|  | 1 |  |  |  |  | 5 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 24 |  | 
| 6 | 3 |  |  | 3 |  | 1084 | use Settlers::Map::Intersection; | 
|  | 3 |  |  | 1 |  | 9 |  | 
|  | 3 |  |  |  |  | 91 |  | 
|  | 1 |  |  |  |  | 5 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 18 |  | 
| 7 | 3 |  |  | 3 |  | 1041 | use Settlers::Map::Path; | 
|  | 3 |  |  | 1 |  | 8 |  | 
|  | 3 |  |  |  |  | 94 |  | 
|  | 1 |  |  |  |  | 5 |  | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 23 |  | 
| 8 | 3 |  |  | 3 |  | 16 | use List::Util 'shuffle'; | 
|  | 3 |  |  | 1 |  | 5 |  | 
|  | 3 |  |  |  |  | 316 |  | 
|  | 1 |  |  |  |  | 80 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 50 |  | 
| 9 | 3 |  |  | 3 |  | 16 | use Math::HexGrid::Hex 0.03; | 
|  | 3 |  |  | 1 |  | 86 |  | 
|  | 3 |  |  |  |  | 8043 |  | 
|  | 1 |  |  |  |  | 5 |  | 
|  | 1 |  |  |  |  | 26 |  | 
|  | 1 |  |  |  |  | 3799 |  | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | my @resource_numbers = (5,2,6,3,8,10,9,12,11,4,8,10,9,4,5,6,3,11); | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | my %tile_types = ( | 
| 14 |  |  |  |  |  |  | D   => 'Settlers::Map::Tile::Desert', | 
| 15 |  |  |  |  |  |  | F   => 'Settlers::Map::Tile::Fields', | 
| 16 |  |  |  |  |  |  | FO  => 'Settlers::Map::Tile::Forest', | 
| 17 |  |  |  |  |  |  | H   => 'Settlers::Map::Tile::Hills', | 
| 18 |  |  |  |  |  |  | M   => 'Settlers::Map::Tile::Mountains', | 
| 19 |  |  |  |  |  |  | P   => 'Settlers::Map::Tile::Pastures', | 
| 20 |  |  |  |  |  |  | S   => 'Settlers::Map::Tile::Sea', | 
| 21 |  |  |  |  |  |  | ); | 
| 22 |  |  |  |  |  |  | eval "require $_" for values %tile_types; | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | sub new | 
| 25 |  |  |  |  |  |  | { | 
| 26 | 7 |  |  | 7 | 0 | 163 | my ($class, $args) = @_; | 
| 27 | 7 |  |  |  |  | 21 | my $self = bless {}, $class; | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | die "$class new requires a type argument (starter, random or custom)\n" | 
| 30 | 7 | 50 |  |  |  | 27 | unless exists $args->{type}; | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | # pick map type | 
| 33 | 7 | 100 |  |  |  | 47 | if ($args->{type} eq 'starter') | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | { | 
| 35 | 2 |  |  |  |  | 17 | $self->{schema} = $self->_starter; | 
| 36 | 2 |  |  |  |  | 17 | $self->{tiles} = _build_tiles($self->{schema}{tiles}); | 
| 37 |  |  |  |  |  |  | } | 
| 38 |  |  |  |  |  |  | elsif ($args->{type} eq 'random') | 
| 39 |  |  |  |  |  |  | { | 
| 40 | 2 |  |  |  |  | 10 | $self->{schema} = $self->_random; | 
| 41 | 2 |  |  |  |  | 11 | $self->{tiles} = _build_tiles($self->{schema}{tiles}); | 
| 42 |  |  |  |  |  |  | } | 
| 43 |  |  |  |  |  |  | elsif ($args->{type} eq 'custom') | 
| 44 |  |  |  |  |  |  | { | 
| 45 |  |  |  |  |  |  | die "A custom map requires a schema containing the tiles and harbor data (see https://github.com/dnmfarrell/Settlers-Game-Notation)\n" | 
| 46 | 3 | 100 | 66 |  |  | 34 | unless exists $args->{schema}{tiles} && exists $args->{schema}{harbors}; | 
| 47 | 2 |  |  |  |  | 6 | $self->{schema} = $args->{schema}; | 
| 48 | 2 |  |  |  |  | 10 | $self->{tiles} = _build_tiles($args->{schema}{tiles}); | 
| 49 |  |  |  |  |  |  | } | 
| 50 |  |  |  |  |  |  | else | 
| 51 |  |  |  |  |  |  | { | 
| 52 | 0 |  |  |  |  | 0 | die "$class->new requires a type argument (starter, random or custom)\n"; | 
| 53 |  |  |  |  |  |  | } | 
| 54 |  |  |  |  |  |  |  | 
| 55 | 6 |  |  |  |  | 18 | $self->{type} = $args->{type}; | 
| 56 | 6 |  |  |  |  | 20 | $self->{intersections} = _build_intersections($self->{tiles}); | 
| 57 | 6 |  |  |  |  | 24 | $self->{paths} = _build_paths($self->{intersections}); | 
| 58 | 6 |  |  |  |  | 36 | $self->{harbors} = $self->build_harbors($self->{schema}{harbors}); | 
| 59 | 6 |  |  |  |  | 48 | return $self; | 
| 60 |  |  |  |  |  |  | } | 
| 61 |  |  |  |  |  |  |  | 
| 62 | 5 |  |  | 5 | 0 | 66 | sub schema  { $_[0]->{schema} } | 
| 63 | 15 |  |  | 15 | 0 | 110 | sub harbors { $_[0]->{harbors} } | 
| 64 | 0 |  |  | 0 | 0 | 0 | sub type    { $_[0]->{type} } | 
| 65 | 1 |  |  | 1 | 0 | 8 | sub tiles   { $_[0]->{tiles} } | 
| 66 |  |  |  |  |  |  |  | 
| 67 |  |  |  |  |  |  | sub find_tile | 
| 68 |  |  |  |  |  |  | { | 
| 69 | 580 |  |  | 580 | 0 | 834 | my ($self, $coordinates) = @_; | 
| 70 |  |  |  |  |  |  |  | 
| 71 | 580 | 50 | 33 |  |  | 3820 | die 'find_tile requires a coordinates arrayref of two integers' | 
|  |  |  | 33 |  |  |  |  | 
| 72 |  |  |  |  |  |  | unless $coordinates && ref $coordinates eq 'ARRAY' && @$coordinates == 2; | 
| 73 |  |  |  |  |  |  |  | 
| 74 | 580 |  |  |  |  | 1475 | my $uuid = "$coordinates->[0],$coordinates->[1]"; | 
| 75 | 580 |  | 50 |  |  | 1810 | return $self->{tiles}{$uuid} || die "Tile $uuid not found!"; | 
| 76 |  |  |  |  |  |  | } | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | sub tiles_by_type_code | 
| 79 |  |  |  |  |  |  | { | 
| 80 | 2 |  |  | 2 | 0 | 5 | my ($self, $type_code) = @_; | 
| 81 |  |  |  |  |  |  |  | 
| 82 | 2 | 50 | 33 |  |  | 33 | die "tiles_by_type_code requires a type code letter" | 
| 83 |  |  |  |  |  |  | unless $type_code && $type_code =~ qr/^[A-Z]$/; | 
| 84 |  |  |  |  |  |  |  | 
| 85 | 2 |  |  |  |  | 8 | my @tiles; | 
| 86 | 2 |  |  |  |  | 5 | for my $tile (values %{$self->{tiles}}) | 
|  | 2 |  |  |  |  | 17 |  | 
| 87 |  |  |  |  |  |  | { | 
| 88 | 74 | 100 |  |  |  | 212 | push(@tiles, $tile) if $tile->code eq $type_code; | 
| 89 |  |  |  |  |  |  | } | 
| 90 | 2 |  |  |  |  | 11 | return \@tiles; | 
| 91 |  |  |  |  |  |  | } | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | sub tiles_by_resource_number | 
| 94 |  |  |  |  |  |  | { | 
| 95 | 54 |  |  | 54 | 0 | 102 | my ($self, $number) = @_; | 
| 96 |  |  |  |  |  |  |  | 
| 97 | 54 | 50 | 33 |  |  | 776 | die "tiles_by_resource_number requires a resource number" | 
| 98 |  |  |  |  |  |  | unless defined $number && $number =~ qr/^[0-9]+$/; | 
| 99 |  |  |  |  |  |  |  | 
| 100 | 54 |  |  |  |  | 138 | my @tiles; | 
| 101 | 54 |  |  |  |  | 91 | for my $tile (values %{$self->{tiles}}) | 
|  | 54 |  |  |  |  | 375 |  | 
| 102 |  |  |  |  |  |  | { | 
| 103 | 1998 | 100 |  |  |  | 4815 | push(@tiles, $tile) if $tile->number == $number; | 
| 104 |  |  |  |  |  |  | } | 
| 105 | 54 |  |  |  |  | 184 | return \@tiles; | 
| 106 |  |  |  |  |  |  | } | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | sub tiles_by_intersection | 
| 109 |  |  |  |  |  |  | { | 
| 110 | 5 |  |  | 5 | 0 | 10 | my ($self, $intersection) = @_; | 
| 111 |  |  |  |  |  |  |  | 
| 112 | 5 | 50 | 33 |  |  | 15 | die "tiles_by_intersection requires an intersection argument" | 
| 113 |  |  |  |  |  |  | unless $intersection && ref $intersection eq 'Settlers::Map::Intersection'; | 
| 114 |  |  |  |  |  |  |  | 
| 115 | 5 |  |  |  |  | 10 | my @tiles; | 
| 116 | 5 |  |  |  |  | 9 | for my $tile (values %{$self->{tiles}}) | 
|  | 5 |  |  |  |  | 26 |  | 
| 117 |  |  |  |  |  |  | { | 
| 118 | 185 | 100 |  |  |  | 447 | push(@tiles, $tile) if $intersection->is_adjacent($tile); | 
| 119 |  |  |  |  |  |  | } | 
| 120 | 5 |  |  |  |  | 21 | return \@tiles; | 
| 121 |  |  |  |  |  |  | } | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | sub find_intersection | 
| 124 |  |  |  |  |  |  | { | 
| 125 | 190 |  |  | 190 | 0 | 286 | my ($self, $coordinates) = @_; | 
| 126 | 190 | 50 | 33 |  |  | 1325 | die 'find_intersection requires an arrayref of 3 coordinates pairs' | 
|  |  |  | 33 |  |  |  |  | 
| 127 |  |  |  |  |  |  | unless $coordinates && ref $coordinates eq 'ARRAY' && @$coordinates == 3; | 
| 128 |  |  |  |  |  |  |  | 
| 129 | 190 |  |  |  |  | 224 | my @tiles; | 
| 130 | 190 |  |  |  |  | 363 | for (@$coordinates) | 
| 131 |  |  |  |  |  |  | { | 
| 132 | 570 |  |  |  |  | 1228 | push @tiles, $self->find_tile($_); | 
| 133 |  |  |  |  |  |  | } | 
| 134 | 190 |  |  |  |  | 582 | my $uuid = Settlers::Map::Intersection->new(\@tiles)->uuid; | 
| 135 | 190 |  | 50 |  |  | 866 | return $self->{intersections}{$uuid} || die "Intersection $uuid not found!"; | 
| 136 |  |  |  |  |  |  | } | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | sub find_path | 
| 139 |  |  |  |  |  |  | { | 
| 140 | 85 |  |  | 85 | 0 | 177 | my ($self, $coordinates) = @_; | 
| 141 | 85 | 50 | 33 |  |  | 689 | die 'find_path requires an arrayref of two triples of coordinates pairs' | 
|  |  |  | 33 |  |  |  |  | 
| 142 |  |  |  |  |  |  | unless $coordinates && ref $coordinates eq 'ARRAY' && @$coordinates == 2; | 
| 143 |  |  |  |  |  |  |  | 
| 144 | 85 |  |  |  |  | 103 | my @intersections; | 
| 145 | 85 |  |  |  |  | 194 | for (@$coordinates) | 
| 146 |  |  |  |  |  |  | { | 
| 147 | 170 |  |  |  |  | 382 | push @intersections, $self->find_intersection($_); | 
| 148 |  |  |  |  |  |  | } | 
| 149 | 85 |  |  |  |  | 294 | my $uuid = Settlers::Map::Path->new(\@intersections)->uuid; | 
| 150 | 85 |  | 50 |  |  | 394 | return $self->{paths}{$uuid} || die "Path $uuid not found!"; | 
| 151 |  |  |  |  |  |  | } | 
| 152 |  |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  | sub _build_tiles | 
| 154 |  |  |  |  |  |  | { | 
| 155 | 6 |  |  | 6 |  | 13 | my ($map_plan) = @_; | 
| 156 |  |  |  |  |  |  |  | 
| 157 | 6 |  |  |  |  | 16 | my %tiles = (); | 
| 158 |  |  |  |  |  |  |  | 
| 159 | 6 | 50 | 33 |  |  | 57 | die 'build_tiles requires a arrayref of key/pairs describing the map' | 
| 160 |  |  |  |  |  |  | unless $map_plan && ref $map_plan eq 'ARRAY'; | 
| 161 |  |  |  |  |  |  |  | 
| 162 | 6 |  |  |  |  | 19 | for (@$map_plan) | 
| 163 |  |  |  |  |  |  | { | 
| 164 | 222 |  |  |  |  | 553 | my ($q, $r, $tile_code, $resource_number) = @$_; | 
| 165 |  |  |  |  |  |  |  | 
| 166 | 222 | 50 | 66 |  |  | 1102 | die 'Error building tiles, invalid resource number' | 
| 167 |  |  |  |  |  |  | unless !defined $resource_number # undef is valid | 
| 168 |  |  |  |  |  |  | || grep $resource_number == $_, @resource_numbers; | 
| 169 |  |  |  |  |  |  |  | 
| 170 |  |  |  |  |  |  |  | 
| 171 |  |  |  |  |  |  | my $tile_class = exists $tile_types{$tile_code} | 
| 172 | 222 | 50 |  |  |  | 538 | ? $tile_types{$tile_code} | 
| 173 |  |  |  |  |  |  | : die 'Error building tiles, invalid tile type'; | 
| 174 | 222 |  |  |  |  | 792 | my $tile = $tile_class->new($q, $r, $resource_number); | 
| 175 | 222 |  |  |  |  | 510 | $tiles{$tile} = $tile; | 
| 176 |  |  |  |  |  |  | } | 
| 177 | 6 |  |  |  |  | 19 | return \%tiles; | 
| 178 |  |  |  |  |  |  | } | 
| 179 |  |  |  |  |  |  |  | 
| 180 |  |  |  |  |  |  | sub _build_intersections | 
| 181 |  |  |  |  |  |  | { | 
| 182 | 6 |  |  | 6 |  | 10 | my $map = shift; | 
| 183 |  |  |  |  |  |  |  | 
| 184 | 6 | 50 | 33 |  |  | 78 | die '_building_intersections requires a hashref of 37 tiles' | 
|  |  |  | 33 |  |  |  |  | 
| 185 |  |  |  |  |  |  | unless $map && ref $map eq 'HASH' && keys %$map == 37; | 
| 186 |  |  |  |  |  |  |  | 
| 187 | 6 |  |  |  |  | 14 | my %intersections; | 
| 188 | 6 |  |  |  |  | 13 | my $centre_tile = $map->{"0,0"}; | 
| 189 |  |  |  |  |  |  |  | 
| 190 | 6 | 50 |  |  |  | 18 | die '_building_intersections requires a map with a centre tile' | 
| 191 |  |  |  |  |  |  | unless $centre_tile; | 
| 192 |  |  |  |  |  |  |  | 
| 193 | 6 |  |  |  |  | 55 | for my $k (keys %$map) | 
| 194 |  |  |  |  |  |  | { | 
| 195 | 222 |  |  |  |  | 366 | my $tile1 = $map->{$k}; | 
| 196 |  |  |  |  |  |  |  | 
| 197 | 222 |  |  |  |  | 493 | for my $d (0..5) | 
| 198 |  |  |  |  |  |  | { | 
| 199 | 1332 |  |  |  |  | 3425 | my $tile2 = $map->{ $tile1->tile_neighbor_uuid($d) }; | 
| 200 | 1332 |  |  |  |  | 28340 | my $tile3 = $map->{ $tile1->tile_neighbor_uuid($d + 1) }; | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | # avoid creating intersections that don't exist in map | 
| 203 | 1332 | 100 | 100 |  |  | 26928 | next unless $tile2 && $tile3; | 
| 204 |  |  |  |  |  |  |  | 
| 205 | 972 |  |  |  |  | 3733 | my $i  = Settlers::Map::Intersection->new([$tile1, $tile2, $tile3]); | 
| 206 | 972 |  |  |  |  | 8086 | $intersections{$i->uuid} = $i; | 
| 207 |  |  |  |  |  |  | } | 
| 208 |  |  |  |  |  |  | } | 
| 209 | 6 |  |  |  |  | 43 | return \%intersections; | 
| 210 |  |  |  |  |  |  | } | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | sub _build_paths | 
| 213 |  |  |  |  |  |  | { | 
| 214 | 6 |  |  | 6 |  | 15 | my $intersections = shift; | 
| 215 |  |  |  |  |  |  |  | 
| 216 | 6 | 50 | 33 |  |  | 82 | die '_building_paths requires a hashref of 54 intersections' | 
|  |  |  | 33 |  |  |  |  | 
| 217 |  |  |  |  |  |  | unless $intersections && ref $intersections eq 'HASH' | 
| 218 |  |  |  |  |  |  | && keys %$intersections == 54; | 
| 219 |  |  |  |  |  |  |  | 
| 220 | 6 |  |  |  |  | 12 | my %paths; | 
| 221 |  |  |  |  |  |  |  | 
| 222 | 6 |  |  |  |  | 53 | for my $i (keys %$intersections) | 
| 223 |  |  |  |  |  |  | { | 
| 224 | 324 |  |  |  |  | 2283 | for my $j (keys %$intersections) | 
| 225 |  |  |  |  |  |  | { | 
| 226 | 17496 |  |  |  |  | 26206 | my $i1 = $intersections->{$i}; | 
| 227 | 17496 |  |  |  |  | 23667 | my $i2 = $intersections->{$j}; | 
| 228 |  |  |  |  |  |  |  | 
| 229 |  |  |  |  |  |  | # skip colliding and non-adjacent | 
| 230 | 17496 | 100 | 100 |  |  | 40255 | next if $i1 eq $i2 || !$i1->is_adjacent($i2); | 
| 231 | 864 |  |  |  |  | 3161 | my $p = Settlers::Map::Path->new([$i1, $i2]); | 
| 232 | 864 |  |  |  |  | 2812 | $paths{$p->uuid} = $p; | 
| 233 |  |  |  |  |  |  | } | 
| 234 |  |  |  |  |  |  | } | 
| 235 | 6 |  |  |  |  | 47 | return \%paths; | 
| 236 |  |  |  |  |  |  | } | 
| 237 |  |  |  |  |  |  |  | 
| 238 |  |  |  |  |  |  | my %harbor_types = ( | 
| 239 |  |  |  |  |  |  | HR  => 'Generic Harbor', | 
| 240 |  |  |  |  |  |  | HRB => 'Brick Harbor', | 
| 241 |  |  |  |  |  |  | HRG => 'Grain Harbor', | 
| 242 |  |  |  |  |  |  | HRL => 'Lumber Harbor', | 
| 243 |  |  |  |  |  |  | HRO => 'Ore Harbor', | 
| 244 |  |  |  |  |  |  | HRW => 'Wool Harbor', | 
| 245 |  |  |  |  |  |  | ); | 
| 246 |  |  |  |  |  |  |  | 
| 247 |  |  |  |  |  |  | sub build_harbors | 
| 248 |  |  |  |  |  |  | { | 
| 249 | 6 |  |  | 6 | 0 | 14 | my ($self, $harbors) = @_; | 
| 250 | 6 | 50 | 33 |  |  | 43 | die 'build_harbors requires an arrayref of arrays of paths and harbor types' | 
| 251 |  |  |  |  |  |  | unless $harbors && ref $harbors eq 'ARRAY'; | 
| 252 |  |  |  |  |  |  |  | 
| 253 | 6 |  |  |  |  | 19 | my @harbors  = (); | 
| 254 | 6 |  |  |  |  | 17 | for my $pair (@$harbors) | 
| 255 |  |  |  |  |  |  | { | 
| 256 | 54 |  |  |  |  | 136 | my ($coordinates, $code) = @$pair; | 
| 257 | 54 |  |  |  |  | 128 | my $path = $self->find_path($coordinates); | 
| 258 | 54 | 50 |  |  |  | 148 | die "invalid harbor code!\n" unless exists $harbor_types{$code}; | 
| 259 | 54 |  |  |  |  | 227 | push @harbors, { code => $code, location => $path }; | 
| 260 |  |  |  |  |  |  | } | 
| 261 | 6 |  |  |  |  | 23 | return \@harbors; | 
| 262 |  |  |  |  |  |  | } | 
| 263 |  |  |  |  |  |  |  | 
| 264 |  |  |  |  |  |  | sub _starter | 
| 265 |  |  |  |  |  |  | { | 
| 266 |  |  |  |  |  |  | return { | 
| 267 | 4 |  |  | 4 |  | 379 | tiles => [ | 
| 268 |  |  |  |  |  |  | [0,-3,"S",undef], | 
| 269 |  |  |  |  |  |  | [1,-3,"S",undef], | 
| 270 |  |  |  |  |  |  | [2,-3,"S",undef], | 
| 271 |  |  |  |  |  |  | [3,-3,"S",undef], | 
| 272 |  |  |  |  |  |  | [3,-2,"S",undef], | 
| 273 |  |  |  |  |  |  | [3,-1,"S",undef], | 
| 274 |  |  |  |  |  |  | [3,0,"S",undef], | 
| 275 |  |  |  |  |  |  | [2,1,"S",undef], | 
| 276 |  |  |  |  |  |  | [1,2,"S",undef], | 
| 277 |  |  |  |  |  |  | [0,3,"S",undef], | 
| 278 |  |  |  |  |  |  | [-1,3,"S",undef], | 
| 279 |  |  |  |  |  |  | [-2,3,"S",undef], | 
| 280 |  |  |  |  |  |  | [-3,3,"S",undef], | 
| 281 |  |  |  |  |  |  | [-3,2,"S",undef], | 
| 282 |  |  |  |  |  |  | [-3,1,"S",undef], | 
| 283 |  |  |  |  |  |  | [-3,0,"S",undef], | 
| 284 |  |  |  |  |  |  | [-2,-1,"S",undef], | 
| 285 |  |  |  |  |  |  | [-1,-2,"S",undef], | 
| 286 |  |  |  |  |  |  | [0,-2,"FO",11], | 
| 287 |  |  |  |  |  |  | [1,-2,"P",12], | 
| 288 |  |  |  |  |  |  | [2,-2,"F",9], | 
| 289 |  |  |  |  |  |  | [2,-1,"P",10], | 
| 290 |  |  |  |  |  |  | [2,0,"F",8], | 
| 291 |  |  |  |  |  |  | [1,1,"M",3], | 
| 292 |  |  |  |  |  |  | [0,2,"FO",6], | 
| 293 |  |  |  |  |  |  | [-1,2,"F",2], | 
| 294 |  |  |  |  |  |  | [-2,2,"M",5], | 
| 295 |  |  |  |  |  |  | [-2,1,"H",8], | 
| 296 |  |  |  |  |  |  | [-2,0,"D",undef], | 
| 297 |  |  |  |  |  |  | [-1,-1,"H",4], | 
| 298 |  |  |  |  |  |  | [0,-1,"M",6], | 
| 299 |  |  |  |  |  |  | [1,-1,"H",5], | 
| 300 |  |  |  |  |  |  | [1,0,"FO",4], | 
| 301 |  |  |  |  |  |  | [0,1,"P",9], | 
| 302 |  |  |  |  |  |  | [-1,1,"P",10], | 
| 303 |  |  |  |  |  |  | [-1,0,"FO",3], | 
| 304 |  |  |  |  |  |  | [0,0,"F",11] | 
| 305 |  |  |  |  |  |  | ], | 
| 306 |  |  |  |  |  |  | harbors => [ | 
| 307 |  |  |  |  |  |  | [ [[[0,-3],[0,-2],[-1,-2]],  [[1,-3],[0,-2],[0,-3]]],   "HR"], | 
| 308 |  |  |  |  |  |  | [ [[[2,-3],[1,-2],[1,-3]],   [[2,-3],[2,-2],[1,-2]]],   "HRW"], | 
| 309 |  |  |  |  |  |  | [ [[[3,-2],[2,-1],[2,-2]],   [[3,-2],[3,-1],[2,-1]]],   "HR"], | 
| 310 |  |  |  |  |  |  | [ [[[3,-1],[3,0],[2,0]],     [[3,0],[2,1],[2,0]]],      "HR"], | 
| 311 |  |  |  |  |  |  | [ [[[2,1],[1,2],[1,1]],      [[1,1],[1,2],[0,2]]],      "HRB"], | 
| 312 |  |  |  |  |  |  | [ [[[0,2],[-1,3],[-1,2]],    [[-1,2],[-1,3],[-2,3]]],   "HRL"], | 
| 313 |  |  |  |  |  |  | [ [[[-2,2],[-2,3],[-3,3]],   [[-2,2],[-3,3],[-3,2]]],  "HR"], | 
| 314 |  |  |  |  |  |  | [ [[[-2,1],[-3,2],[-3,1]],   [[-2,0],[-2,1],[-3,1]]],   "HRG"], | 
| 315 |  |  |  |  |  |  | [ [[[-1,-1],[-2,0],[-2,-1]], [[-1,-2],[-1,-1],[-2,-1]]],"HRO"] | 
| 316 |  |  |  |  |  |  | ] | 
| 317 |  |  |  |  |  |  | }; | 
| 318 |  |  |  |  |  |  | } | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | sub _random | 
| 321 |  |  |  |  |  |  | { | 
| 322 |  |  |  |  |  |  | # only the land tiles are random | 
| 323 | 2 |  |  | 2 |  | 122 | my @land_types = shuffle qw/H H H D F F F F FO FO FO FO P P P P M M M/; | 
| 324 |  |  |  |  |  |  |  | 
| 325 |  |  |  |  |  |  | # make a local copy so we can mutate it | 
| 326 | 2 |  |  |  |  | 9 | my @local_resource_numbers = @resource_numbers; | 
| 327 |  |  |  |  |  |  |  | 
| 328 | 2 |  |  |  |  | 4 | my $type       = shift @land_types; | 
| 329 | 2 | 50 |  |  |  | 8 | my $number     = $type eq 'D' ? undef : pop @local_resource_numbers; | 
| 330 | 2 |  |  |  |  | 7 | my @land_tiles = ([0, 0, $type, $number]); | 
| 331 |  |  |  |  |  |  |  | 
| 332 | 2 |  |  |  |  | 15 | while (scalar @land_types) | 
| 333 |  |  |  |  |  |  | { | 
| 334 | 4 |  |  |  |  | 7 | my $i = 0; | 
| 335 | 4 |  |  |  |  | 8 | my @new_tiles = (); | 
| 336 | 4 |  |  |  |  | 7 | for my $tile (@land_tiles) | 
| 337 |  |  |  |  |  |  | { | 
| 338 | 16 |  |  |  |  | 59 | my $hex = Math::HexGrid::Hex->new($tile->[0], $tile->[1]); | 
| 339 |  |  |  |  |  |  |  | 
| 340 | 16 |  |  |  |  | 181 | for my $direction (map { $_ + (2*$hex->hex_length) - $i } reverse 5..10) | 
|  | 96 |  |  |  |  | 573 |  | 
| 341 |  |  |  |  |  |  | { | 
| 342 | 96 |  |  |  |  | 318 | my $neighbor = $hex->hex_neighbor($direction); | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | # skip neighbors we already have | 
| 345 | 96 | 100 | 100 |  |  | 3397 | next if grep($neighbor->{q} == $_->[0] && $neighbor->{r} == $_->[1], | 
| 346 |  |  |  |  |  |  | @land_tiles, @new_tiles); | 
| 347 |  |  |  |  |  |  |  | 
| 348 | 36 |  |  |  |  | 54 | my $type = shift @land_types; | 
| 349 |  |  |  |  |  |  | # pop the number as we're working inside out instead of outside in | 
| 350 | 36 | 100 |  |  |  | 83 | my $number = $type eq 'D' ? undef : pop @local_resource_numbers; | 
| 351 | 36 |  |  |  |  | 178 | push @new_tiles, [$neighbor->{q}, $neighbor->{r}, $type, $number]; | 
| 352 |  |  |  |  |  |  | } | 
| 353 | 16 |  |  |  |  | 48 | $i++; | 
| 354 |  |  |  |  |  |  | } | 
| 355 | 4 |  |  |  |  | 20 | push @land_tiles, @new_tiles; | 
| 356 |  |  |  |  |  |  | } | 
| 357 |  |  |  |  |  |  |  | 
| 358 | 2 |  |  |  |  | 48 | my @tiles = ( | 
| 359 |  |  |  |  |  |  | [0,-3,"S",undef], | 
| 360 |  |  |  |  |  |  | [1,-3,"S",undef], | 
| 361 |  |  |  |  |  |  | [2,-3,"S",undef], | 
| 362 |  |  |  |  |  |  | [3,-3,"S",undef], | 
| 363 |  |  |  |  |  |  | [3,-2,"S",undef], | 
| 364 |  |  |  |  |  |  | [3,-1,"S",undef], | 
| 365 |  |  |  |  |  |  | [3,0,"S",undef], | 
| 366 |  |  |  |  |  |  | [2,1,"S",undef], | 
| 367 |  |  |  |  |  |  | [1,2,"S",undef], | 
| 368 |  |  |  |  |  |  | [0,3,"S",undef], | 
| 369 |  |  |  |  |  |  | [-1,3,"S",undef], | 
| 370 |  |  |  |  |  |  | [-2,3,"S",undef], | 
| 371 |  |  |  |  |  |  | [-3,3,"S",undef], | 
| 372 |  |  |  |  |  |  | [-3,2,"S",undef], | 
| 373 |  |  |  |  |  |  | [-3,1,"S",undef], | 
| 374 |  |  |  |  |  |  | [-3,0,"S",undef], | 
| 375 |  |  |  |  |  |  | [-2,-1,"S",undef], | 
| 376 |  |  |  |  |  |  | [-1,-2,"S",undef], | 
| 377 |  |  |  |  |  |  | ); | 
| 378 | 2 |  |  |  |  | 11 | push @tiles, @land_tiles; | 
| 379 |  |  |  |  |  |  |  | 
| 380 | 2 |  |  |  |  | 96 | my @harbors = ( | 
| 381 |  |  |  |  |  |  | [ [[[0,-3],[0,-2],[-1,-2]],  [[1,-3],[0,-2],[0,-3]]],   "HR"], | 
| 382 |  |  |  |  |  |  | [ [[[2,-3],[1,-2],[1,-3]],   [[2,-3],[2,-2],[1,-2]]],   "HRW"], | 
| 383 |  |  |  |  |  |  | [ [[[3,-2],[2,-1],[2,-2]],   [[3,-2],[3,-1],[2,-1]]],   "HR"], | 
| 384 |  |  |  |  |  |  | [ [[[3,-1],[3,0],[2,0]],     [[3,0],[2,1],[2,0]]],      "HR"], | 
| 385 |  |  |  |  |  |  | [ [[[2,1],[1,2],[1,1]],      [[1,1],[1,2],[0,2]]],      "HRB"], | 
| 386 |  |  |  |  |  |  | [ [[[0,2],[-1,3],[-1,2]],    [[-1,2],[-1,3],[-2,3]]],   "HRL"], | 
| 387 |  |  |  |  |  |  | [ [[[-2,2],[-2,3],[-3,3]],   [[-2,2],[-3,3],[-3,2]]],  "HR"], | 
| 388 |  |  |  |  |  |  | [ [[[-2,1],[-3,2],[-3,1]],   [[-2,0],[-2,1],[-3,1]]],   "HRG"], | 
| 389 |  |  |  |  |  |  | [ [[[-1,-1],[-2,0],[-2,-1]], [[-1,-2],[-1,-1],[-2,-1]]],"HRO"] | 
| 390 |  |  |  |  |  |  | ); | 
| 391 | 2 |  |  |  |  | 21 | return { tiles => \@tiles, harbors => \@harbors }; | 
| 392 |  |  |  |  |  |  | } | 
| 393 |  |  |  |  |  |  | 1; | 
| 394 |  |  |  |  |  |  |  | 
| 395 |  |  |  |  |  |  | __END__ |