line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#====================================================================== |
2
|
|
|
|
|
|
|
package Games::Pentominos; # see doc at end of file |
3
|
|
|
|
|
|
|
#====================================================================== |
4
|
|
|
|
|
|
|
our $VERSION = "1.0"; |
5
|
1
|
|
|
1
|
|
26804
|
use strict; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
37
|
|
6
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
29
|
|
7
|
1
|
|
|
1
|
|
2990
|
use Time::HiRes qw/time/; |
|
1
|
|
|
|
|
1786
|
|
|
1
|
|
|
|
|
4
|
|
8
|
1
|
|
|
1
|
|
1266
|
use List::MoreUtils qw/uniq/; |
|
1
|
|
|
|
|
1274
|
|
|
1
|
|
|
|
|
1086
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
# work mostly with global vars because this is fastest than parameter-passing |
11
|
|
|
|
|
|
|
our # because accessed from eval |
12
|
|
|
|
|
|
|
$board; # cells remaining to be filled |
13
|
|
|
|
|
|
|
my $placed; # cells filled so far |
14
|
|
|
|
|
|
|
my $print_solution; # callback for printing a solution |
15
|
|
|
|
|
|
|
my ($t_ini, $t_tot); # times in milliseconds |
16
|
|
|
|
|
|
|
my $n_solutions; # how many solutions found |
17
|
|
|
|
|
|
|
my %substitutions; # a coderef for each pentomino/permutation |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
# description of the 12 pentominos. Each of them has a labelling letter, |
20
|
|
|
|
|
|
|
# a number of permutations, and for each permutation a rectangle describing |
21
|
|
|
|
|
|
|
# the pentomino shape. Occupied cells are shown with an 'x', untouched cells |
22
|
|
|
|
|
|
|
# with a '.' (this character explicitly chosen so that in regexes it will |
23
|
|
|
|
|
|
|
# match anything except a newline character). |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
my %pentominos = ( |
26
|
|
|
|
|
|
|
F => [8, qw/.xx xx. x.. ..x .x. .x. .x. .x. |
27
|
|
|
|
|
|
|
xx. .xx xxx xxx xxx xxx xx. .xx |
28
|
|
|
|
|
|
|
.x. .x. .x. .x. x.. ..x .xx xx./], |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
I => [2, qw/xxxxx x |
31
|
|
|
|
|
|
|
..... x |
32
|
|
|
|
|
|
|
..... x |
33
|
|
|
|
|
|
|
..... x |
34
|
|
|
|
|
|
|
..... x/], |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
L => [4, qw/xxxx xxxx x. .x |
37
|
|
|
|
|
|
|
x... ...x x. .x |
38
|
|
|
|
|
|
|
.... .... x. .x |
39
|
|
|
|
|
|
|
.... .... xx xx/], |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
P => [8, qw/xx xx xxx xxx x. .x xx. .xx |
42
|
|
|
|
|
|
|
xx xx xx. .xx xx xx xxx xxx |
43
|
|
|
|
|
|
|
x. .x ... ... xx xx ... .../], |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
S => [8, qw/xx.. ..xx xxx. .xxx x. .x x. .x |
46
|
|
|
|
|
|
|
.xxx xxx. ..xx xx.. xx xx x. .x |
47
|
|
|
|
|
|
|
.... .... .... .... .x x. xx xx |
48
|
|
|
|
|
|
|
.... .... .... .... .x x. .x x./], |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
T => [4, qw/xxx .x. x.. ..x |
51
|
|
|
|
|
|
|
.x. .x. xxx xxx |
52
|
|
|
|
|
|
|
.x. xxx x.. ..x/], |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
U => [4, qw/xxx x.x xx xx |
55
|
|
|
|
|
|
|
x.x xxx x. .x |
56
|
|
|
|
|
|
|
... ... xx xx/], |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
V => [4, qw/xxx xxx x.. ..x |
59
|
|
|
|
|
|
|
x.. ..x x.. ..x |
60
|
|
|
|
|
|
|
x.. ..x xxx xxx/], |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
W => [4, qw/xx. .xx x.. ..x |
63
|
|
|
|
|
|
|
.xx xx. xx. .xx |
64
|
|
|
|
|
|
|
..x x.. .xx xx./], |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
X => [1, qw/.x. |
67
|
|
|
|
|
|
|
xxx |
68
|
|
|
|
|
|
|
.x./], |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
Y => [8, qw/.x x. .x x. xxxx xxxx ..x. .x.. |
71
|
|
|
|
|
|
|
xx xx .x x. .x.. ..x. xxxx xxxx |
72
|
|
|
|
|
|
|
.x x. xx xx .... .... .... .... |
73
|
|
|
|
|
|
|
.x x. .x x. .... .... .... ..../], |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
Z => [4, qw/xx. .xx x.. ..x |
76
|
|
|
|
|
|
|
.x. .x. xxx xxx |
77
|
|
|
|
|
|
|
.xx xx. ..x x../], |
78
|
|
|
|
|
|
|
); |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
83
|
|
|
|
|
|
|
sub solve { |
84
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
85
|
1
|
|
|
1
|
1
|
391
|
my ($self, $submitted_board, $submitted_callback) = @_; |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
# initialize globals |
88
|
1
|
|
|
|
|
3
|
($board, $placed) = ($submitted_board, ""); |
89
|
1
|
|
|
|
|
2
|
$print_solution = $submitted_callback; |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
# check if $board meets requirements |
92
|
1
|
|
|
|
|
3
|
my $n_cells = ($board =~ tr/x//); |
93
|
1
|
|
|
|
|
30
|
my ($board_n_cols, @others) = uniq map length, ($board =~ m/.+/g); |
94
|
1
|
50
|
|
|
|
7
|
$n_cells == 60 or die "board does not have 60 empty cells noted as 'x'"; |
95
|
1
|
50
|
|
|
|
4
|
not @others or die "board has rows of different lengths"; |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
# check if callback is a coderef |
98
|
1
|
50
|
|
|
|
5
|
ref $print_solution eq 'CODE' or die "improper callback for solutions"; |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
# compile the substitution subroutines |
101
|
1
|
|
|
|
|
4
|
_compile_substitutions($board_n_cols); |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
# anything up to first free cell goes to "placed" |
104
|
1
|
50
|
|
|
|
9
|
$board =~ s/^([^x]+)// and $placed .= $1; |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
# start computing solutions |
107
|
1
|
|
|
|
|
6
|
$t_ini = time; |
108
|
1
|
|
|
|
|
2
|
$t_tot = 0; |
109
|
1
|
|
|
|
|
2
|
$n_solutions = 0; |
110
|
1
|
|
|
|
|
6
|
_place_pentomino(keys %pentominos); |
111
|
|
|
|
|
|
|
} |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
116
|
|
|
|
|
|
|
sub _compile_substitutions { |
117
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
118
|
1
|
|
|
1
|
|
2
|
my ($board_n_cols) = @_; # how many columns in each row |
119
|
|
|
|
|
|
|
|
120
|
1
|
|
|
|
|
3
|
%substitutions = (); |
121
|
1
|
|
|
|
|
7
|
while (my ($letter, $array_ref) = each %pentominos) { |
122
|
|
|
|
|
|
|
|
123
|
12
|
|
|
|
|
22
|
my $n_permutations = $array_ref->[0]; # how many possible layouts |
124
|
12
|
|
|
|
|
23
|
my $n_rows = (@$array_ref-1) / $n_permutations; |
125
|
|
|
|
|
|
|
|
126
|
12
|
|
|
|
|
24
|
for my $perm_id (0 .. $n_permutations-1) { |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
# gather data rows for that permutation |
129
|
59
|
|
|
|
|
126
|
my @rows = map {$array_ref->[$_ * $n_permutations + $perm_id + 1]} |
|
201
|
|
|
|
|
568
|
|
130
|
|
|
|
|
|
|
(0..$n_rows-1); |
131
|
59
|
|
|
|
|
134
|
my $n_cols = length ($rows[0]); |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
# construct regex to match that permutation |
134
|
|
|
|
|
|
|
# NOTE: \D below is just a convenience for char class [FILPSTUVWXYZx.\n] |
135
|
59
|
|
|
|
|
140
|
my $skip_to_next_row = sprintf "\\D{%d}", $board_n_cols + 1 - $n_cols; |
136
|
59
|
|
|
|
|
125
|
my $regex = join $skip_to_next_row, @rows; |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
# remove everything before or after the touched cells |
139
|
59
|
|
|
|
|
152
|
$regex =~ s/^[^x]+//; |
140
|
59
|
|
|
|
|
234
|
$regex =~ s/[^x]+$//; |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
# add capture brackets in regex |
143
|
59
|
|
|
|
|
400
|
$regex =~ s/([^x]+)/($1)/g; |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
# substitution string : replace 'x' by letter |
146
|
|
|
|
|
|
|
# and brackets by captured groups |
147
|
59
|
|
|
|
|
233
|
(my $subst = $regex) =~ s/x/$letter/g; |
148
|
59
|
|
|
|
|
69
|
my $num_paren = 1; |
149
|
59
|
|
|
|
|
407
|
$subst =~ s/\(.*?\)/'$'.$num_paren++/eg; |
|
114
|
|
|
|
|
289
|
|
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
# compile a sub performing the substitution |
152
|
59
|
|
|
|
|
60
|
push @{$substitutions{$letter}}, |
|
59
|
|
|
|
|
6812
|
|
153
|
|
|
|
|
|
|
eval qq{sub {\$board =~ s/^$regex/$subst/}}; |
154
|
|
|
|
|
|
|
} |
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
} |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
160
|
|
|
|
|
|
|
sub _place_pentomino { # the recursive algorithm |
161
|
|
|
|
|
|
|
#---------------------------------------------------------------------- |
162
|
|
|
|
|
|
|
# my @letters = @_; # commented out for speed (avoiding copy) |
163
|
|
|
|
|
|
|
|
164
|
9060
|
|
|
9060
|
|
18101
|
my ($board_ini, $placed_ini) = ($board, $placed); |
165
|
|
|
|
|
|
|
|
166
|
9060
|
|
|
|
|
14529
|
foreach my $letter (@_) { |
167
|
25905
|
|
|
|
|
34076
|
foreach my $substitution (@{$substitutions{$letter}}) { |
|
25905
|
|
|
|
|
57019
|
|
168
|
94237
|
100
|
|
|
|
2606189
|
if ($substitution->()) { # try to apply this pentomino to $board |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
# anything up to next free cell goes to "placed" |
171
|
9060
|
50
|
|
|
|
56926
|
$board =~ s/^([^x]+)// and $placed .= $1; |
172
|
|
|
|
|
|
|
|
173
|
9060
|
100
|
|
|
|
17628
|
if (!$board) { # no more free cells, so this is a solution |
174
|
1
|
|
|
|
|
7
|
my $t_solution = time - $t_ini; |
175
|
1
|
|
|
|
|
3
|
$t_tot += $t_solution; |
176
|
1
|
|
|
|
|
2
|
$n_solutions += 1; |
177
|
1
|
50
|
|
|
|
10
|
$print_solution->($placed, $n_solutions, $t_solution, $t_tot) |
178
|
|
|
|
|
|
|
or return; # stop searching if callback did not return true |
179
|
0
|
|
|
|
|
0
|
$t_ini = time; |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
else { |
182
|
9059
|
100
|
|
|
|
21554
|
_place_pentomino(grep {$_ ne $letter} @_) |
|
34999
|
|
|
|
|
77685
|
|
183
|
|
|
|
|
|
|
or return; |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
# restore to previous state (remove pentomino from board) |
187
|
9048
|
|
|
|
|
27868
|
($board, $placed) = ($board_ini, $placed_ini); |
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
} |
190
|
|
|
|
|
|
|
} |
191
|
9048
|
|
|
|
|
26140
|
return 1; # continue searching |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
__END__ |