line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Freecell::App;
|
2
|
1
|
|
|
1
|
|
24922
|
use version;
|
|
1
|
|
|
|
|
2370
|
|
|
1
|
|
|
|
|
5
|
|
3
|
|
|
|
|
|
|
our $VERSION = '0.03';
|
4
|
1
|
|
|
1
|
|
74
|
use warnings;
|
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
29
|
|
5
|
1
|
|
|
1
|
|
5
|
use strict;
|
|
1
|
|
|
|
|
6
|
|
|
1
|
|
|
|
|
25
|
|
6
|
1
|
|
|
1
|
|
674
|
use Freecell::App::Tableau;
|
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
32
|
|
7
|
1
|
|
|
1
|
|
1210
|
use Getopt::Long;
|
|
1
|
|
|
|
|
15374
|
|
|
1
|
|
|
|
|
10
|
|
8
|
1
|
|
|
1
|
|
1610
|
use Log::Log4perl;
|
|
1
|
|
|
|
|
59445
|
|
|
1
|
|
|
|
|
8
|
|
9
|
1
|
|
|
1
|
|
968
|
use File::Slurp;
|
|
1
|
|
|
|
|
17334
|
|
|
1
|
|
|
|
|
101
|
|
10
|
1
|
|
|
1
|
|
13
|
use List::Util qw(min);
|
|
1
|
|
|
|
|
15
|
|
|
1
|
|
|
|
|
3233
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
my $QUIT_NOW = 0;
|
13
|
|
|
|
|
|
|
$SIG{'INT'} = sub { $QUIT_NOW = 1 };
|
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
my $cnt; # new positions
|
16
|
|
|
|
|
|
|
my $dup; # potential dupes
|
17
|
|
|
|
|
|
|
my $tot = 0;
|
18
|
|
|
|
|
|
|
my $found = 0;
|
19
|
|
|
|
|
|
|
my $depth = 0; # maxnodes estimates - sometimes better solution when higher
|
20
|
|
|
|
|
|
|
my $max_depth = 55; # 2000 < 2GB, 5-10 mins to solve, perl x86 (32bit) ok
|
21
|
|
|
|
|
|
|
my $max_nodes = 2000; # 25000 ~ 2GB, 20-40 mins to solve, perl x64 (64bit) only
|
22
|
|
|
|
|
|
|
my $game_no = 0; # 100000 > 4GB, over 2 hrs to solve
|
23
|
|
|
|
|
|
|
my $log_stats = 0; # 250000 > 16GB, out_of_memory!
|
24
|
|
|
|
|
|
|
my $show_all = 0;
|
25
|
|
|
|
|
|
|
my $fcgsfile = '';
|
26
|
|
|
|
|
|
|
my %stats;
|
27
|
|
|
|
|
|
|
my @solution;
|
28
|
|
|
|
|
|
|
my $position; # seen layouts
|
29
|
|
|
|
|
|
|
my $logger;
|
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
sub getopts {
|
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
my $result = GetOptions(
|
34
|
|
|
|
|
|
|
"gameno:i" => \$game_no, #numeric
|
35
|
|
|
|
|
|
|
"maxnodes:i" => \$max_nodes, #numeric
|
36
|
0
|
|
|
0
|
|
|
"winxp!" => sub { Freecell::App::Tableau->winxp_opt($_[1]) },
|
37
|
0
|
|
|
0
|
1
|
|
"showall!" => \$show_all, #boolean
|
38
|
|
|
|
|
|
|
"maxdepth:i" => \$max_depth, #numeric
|
39
|
|
|
|
|
|
|
"logstats!" => \$log_stats, #boolean
|
40
|
|
|
|
|
|
|
"analyze:s" => \$fcgsfile, #string
|
41
|
|
|
|
|
|
|
);
|
42
|
0
|
0
|
0
|
|
|
|
usage_quit(0) if !($result and ($game_no == -1 or $game_no >= 1 and $game_no <= 1_000_000 or $fcgsfile));
|
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
sub usage_quit {
|
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
# Emit usage message, then exit with given error code.
|
47
|
0
|
|
|
0
|
1
|
|
print <<"END_OF_MESSAGE"; exit;
|
|
0
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
Usage:
|
50
|
|
|
|
|
|
|
freecell-solver [switches]
|
51
|
|
|
|
|
|
|
This will solve a selected game of Freecell.
|
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
Switches:
|
54
|
|
|
|
|
|
|
--gameno number of freecell game to solve (required 1-1000000)
|
55
|
|
|
|
|
|
|
--maxnodes set maximum nodes per level to search (default 2000)
|
56
|
|
|
|
|
|
|
--winxp solve for windows xp (no supermove)(default --nowinxp)
|
57
|
|
|
|
|
|
|
--showall log all moves and layouts with solution (default --noshowall)
|
58
|
|
|
|
|
|
|
END_OF_MESSAGE
|
59
|
|
|
|
|
|
|
}
|
60
|
|
|
|
|
|
|
}
|
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
sub initlog {
|
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
# Initialize Logger
|
65
|
0
|
|
|
0
|
1
|
|
my $log_conf = q(
|
66
|
|
|
|
|
|
|
log4perl.rootLogger = DEBUG, LOG1, LOG2
|
67
|
|
|
|
|
|
|
log4perl.appender.LOG1 = Log::Log4perl::Appender::File
|
68
|
|
|
|
|
|
|
log4perl.appender.LOG1.filename = fc_0_01.log
|
69
|
|
|
|
|
|
|
log4perl.appender.LOG1.mode = append
|
70
|
|
|
|
|
|
|
log4perl.appender.LOG1.layout = Log::Log4perl::Layout::PatternLayout
|
71
|
|
|
|
|
|
|
log4perl.appender.LOG1.layout.ConversionPattern = %d %p %m %n
|
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
log4perl.appender.LOG2 = Log::Log4perl::Appender::Screen
|
74
|
|
|
|
|
|
|
log4perl.appender.LOG2.stderr = 0
|
75
|
|
|
|
|
|
|
log4perl.appender.LOG2.layout = Log::Log4perl::Layout::PatternLayout
|
76
|
|
|
|
|
|
|
log4perl.appender.LOG2.layout.ConversionPattern = %d %p %m %n
|
77
|
|
|
|
|
|
|
);
|
78
|
0
|
|
|
|
|
|
Log::Log4perl::init( \$log_conf );
|
79
|
0
|
|
|
|
|
|
$logger = Log::Log4perl->get_logger();
|
80
|
|
|
|
|
|
|
}
|
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
sub stats {
|
83
|
0
|
|
|
0
|
1
|
|
my $log = "stats\n";
|
84
|
0
|
|
|
|
|
|
foreach my $depth ( sort { $a <=> $b } keys %stats ) {
|
|
0
|
|
|
|
|
|
|
85
|
0
|
|
|
|
|
|
$log .= sprintf "%s\n", $depth;
|
86
|
0
|
|
|
|
|
|
foreach my $score ( sort { $a <=> $b } keys %{ $stats{$depth} } ) {
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
87
|
0
|
0
|
|
|
|
|
$log .= sprintf "\t%s\t%s\t%s\n", $score,
|
88
|
0
|
|
|
|
|
|
map { defined($_) ? $_ : " " }
|
89
|
0
|
|
|
|
|
|
@{ $stats{$depth}{$score} }[ 0, 1 ];
|
90
|
|
|
|
|
|
|
}
|
91
|
|
|
|
|
|
|
}
|
92
|
0
|
|
|
|
|
|
$log;
|
93
|
|
|
|
|
|
|
}
|
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
sub out {
|
96
|
0
|
|
|
0
|
1
|
|
my ( $game_no, $max_nodes, @solution ) = @_;
|
97
|
0
|
|
|
|
|
|
my $depth = @solution;
|
98
|
0
|
0
|
|
|
|
|
my $file = sprintf "#%s %s %sk %s", $game_no, $depth, $max_nodes / 1000,
|
|
|
0
|
|
|
|
|
|
99
|
|
|
|
|
|
|
( Freecell::App::Tableau->winxp_opt() ? "xp" : Freecell::App::Tableau->winxp_warn() ? "w7" : "all" );
|
100
|
0
|
|
|
|
|
|
my $log = "\n\n";
|
101
|
0
|
|
|
|
|
|
my $std = "\n\n#$game_no\n";
|
102
|
0
|
|
|
|
|
|
my $cnt = 0;
|
103
|
0
|
|
|
|
|
|
my $htm = <<"eof";
|
104
|
|
|
|
|
|
|
Freecell in Miniature $file
|
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
eof
|
116
|
0
|
0
|
0
|
|
|
|
$htm .=
|
117
|
|
|
|
|
|
|
"Game #$game_no (Windows "
118
|
|
|
|
|
|
|
. ( Freecell::App::Tableau->winxp_opt() || !Freecell::App::Tableau->winxp_warn() ? "XP, " : "" )
|
119
|
|
|
|
|
|
|
. "Vista, 7)\n";
|
120
|
0
|
|
|
|
|
|
$htm .= " | No | SN | Move | To | Autoplay to home\n";
|
121
|
|
|
|
|
|
|
|
122
|
0
|
|
|
|
|
|
foreach (@solution) {
|
123
|
0
|
|
|
|
|
|
my ( $layout, $score0, $score1, @note ) = split /\t/, $_, 8;
|
124
|
0
|
|
|
|
|
|
$stats{ $note[0] - 1 }{$score0}[1] = scalar @solution;
|
125
|
0
|
|
|
|
|
|
$log .= "($score0) @note\n\n$layout";
|
126
|
0
|
0
|
|
|
|
|
$std .= $note[1] . ( ++$cnt % 8 ? " " : "\n" );
|
127
|
0
|
0
|
|
|
|
|
$htm .=
|
128
|
|
|
|
|
|
|
join( "", " | ", map "" . ( $_ ? $_ : " " ), @note ) . "\n";
|
129
|
|
|
|
|
|
|
}
|
130
|
0
|
|
|
|
|
|
$htm .= " | |