File Coverage

lib/Algorithm/MasterMind/Evo.pm
Criterion Covered Total %
statement 15 15 100.0
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 20 20 100.0


line stmt bran cond sub pod time code
1             package Algorithm::MasterMind::Evo;
2              
3 1     1   792 use warnings;
  1         3  
  1         29  
4 1     1   4 use strict;
  1         1  
  1         53  
5 1     1   5 use Carp;
  1         1  
  1         51  
6              
7 1         6 use lib qw(../../lib ../../../../Algorithm-Evolutionary/lib/
8             ../../Algorithm-Evolutionary/lib/
9 1     1   7 ../../../lib);
  1         1  
10              
11             our $VERSION = sprintf "%d.%03d", q$Revision: 1.14 $ =~ /(\d+)\.(\d+)/g;
12              
13 1     1   271 use base 'Algorithm::MasterMind::Evolutionary_Base';
  1         1  
  1         459  
14             use Algorithm::MasterMind qw(partitions);
15              
16             use Algorithm::Evolutionary qw(Op::String_Mutation
17             Op::Permutation
18             Op::Uniform_Crossover_Diff
19             Op::Breeder_Diverser
20             Op::Replace_Different
21             Op::Tournament_Selection
22             Individual::String );
23              
24             use Algorithm::Combinatorics qw(permutations);
25             use Algorithm::MasterMind::Partition::Most;
26             use Clone qw(clone);
27              
28             # ---------------------------------------------------------------------------
29             use constant { MAX_CONSISTENT_SET => 20, # This number 20 was computed in NICSO paper, valid for default 4-6 mastermind
30             MAX_GENERATIONS_RESET => 100,
31             MAX_GENERATIONS_EQUAL => 3} ;
32              
33             sub factorial {
34             my $value = shift;
35             my $factorial = 1;
36             $factorial *= $_ foreach 1..$value;
37             return $factorial;
38             }
39              
40              
41             sub initialize {
42             my $self = shift;
43             my $options = shift;
44             for my $o ( keys %$options ) {
45             $self->{"_$o"} = clone($options->{$o});
46             }
47             croak "No population" if $self->{'_pop_size'} == 0;
48              
49             # Variation operators
50             my $mutation_rate = $options->{'mutation_rate'} || 1;
51             my $permutation_rate = $options->{'permutation_rate'} || 0;
52             my $permutation_iters = $options->{'permutation_iterations'} || factorial($options->{'length'}) - 1 ;
53             my $xover_rate = $options->{'xover_rate'} || 1;
54             my $max_number_of_consistent = $options->{'consistent_set_card'}
55             || MAX_CONSISTENT_SET;
56             $self->{'_replacement_rate'}= $self->{'_replacement_rate'} || 0.25;
57             my $m = new Algorithm::Evolutionary::Op::String_Mutation $mutation_rate ; # Rate = 1
58             my $c = Algorithm::Evolutionary::Op::Uniform_Crossover_Diff->new( $options->{'length'}/2, $xover_rate );
59             my $operators = [$m,$c];
60             if ( $permutation_rate > 0 ) {
61             my $p = new Algorithm::Evolutionary::Op::Permutation $permutation_rate, $permutation_iters;
62             push @$operators, $p;
63             }
64             my $select = new Algorithm::Evolutionary::Op::Tournament_Selection $self->{'_tournament_size'} || 2;
65             if (! $self->{'_ga'} ) { # Not given as an option
66             $self->{'_ga'} = new Algorithm::Evolutionary::Op::Breeder_Diverser( $operators, $select );
67             }
68             $self->{'_replacer'} = new Algorithm::Evolutionary::Op::Replace_Different;
69              
70             if (!$self->{'_distance'}) {
71             $self->{'_distance'} = 'distance_taxicab';
72             }
73              
74             $self->{'_max_consistent'} = $max_number_of_consistent;
75             }
76              
77             sub compute_fitness {
78             my $pop = shift;
79             #Compute min
80             my $min_distance = 0;
81             for my $p ( @$pop ) {
82             $min_distance = ( $p->{'_distance'} < $min_distance )?
83             $p->{'_distance'}:
84             $min_distance;
85             }
86              
87             for my $p ( @$pop ) {
88             $p->Fitness( $p->{'_distance'}+
89             ($p->{'_partitions'}?$p->{'_partitions'}:0)-
90             $min_distance + 1);
91             }
92             }
93             #----------------------------------------------------------------------------
94             sub eliminate_last_played {
95             my $self = shift;
96             my $last_played = $self->{'_last'};
97              
98             for my $p ( @{$self->{'_pop'}} ) {
99             if ($p->{'_str'} eq $last_played ) {
100             $p = new Algorithm::Evolutionary::Individual::String( $self->{'_alphabet'}, $self->{'_length'} );
101             }
102             }
103             }
104              
105              
106             #----------------------------------------------------------------------------
107              
108             sub issue_next {
109             my $self = shift;
110             my @rules = @{$self->{'_rules'}};
111             my @alphabet = @{$self->{'_alphabet'}};
112             my $length = $self->{'_length'};
113             my $pop = $self->{'_pop'};
114             my $rules = $self->number_of_rules();
115             my $ga = $self->{'_ga'};
116             my $max_number_of_consistent = $self->{'_max_consistent'};
117              
118             my $last_rule = $rules[$#rules];
119             my $alphabet_size = @{$self->{'_alphabet'}};
120              
121             if ( $self->{'_played_out'} ) {
122             $self->eliminate_last_played;
123             }
124             #Check for combination guessed right except for permutation
125             if ($last_rule->{'blacks'}+$last_rule->{'whites'} == $length ) {
126             if ( ! $self->{'_consistent_endgame'} ) {
127             my %permutations;
128             map( $permutations{$_} = 1,
129             map(join("",@$_),
130             permutations([ split( //, $last_rule->{'combination'} ) ] ) ) );
131             my @permutations = keys %permutations;
132             $self->{'_endgame'} =
133             Algorithm::MasterMind::Partition::Most->start_from( { evaluated => $self->{'_evaluated'},
134             alphabet => \@alphabet,
135             rules => $self->{'_rules'},
136             consistent => \@permutations} );
137             } else {
138             $self->{'_endgame'} =
139             Algorithm::MasterMind::Partition::Most->start_from( { evaluated => $self->{'_evaluated'},
140             alphabet => \@alphabet,
141             rules => $self->{'_rules'},
142             consistent => $self->{'_consistent_endgame'} } );
143             }
144             my $string = $self->{'_endgame'}->issue_next();
145             $self->{'_consistent_endgame'} = $self->{'_endgame'}->{'_consistent'};
146             $self->{'_evaluated'} = $self->{'_endgame'}->{'_evaluated'};
147             return $self->{'_last'} = $string;
148             } else {
149             #Check for no pegs
150             if ($last_rule->{'blacks'}+$last_rule->{'whites'} == 0 ) {
151             my %these_colors;
152             map ( $these_colors{$_} = 1, split( //, $last_rule->{'combination'} ) );
153             for (my $i = 0; $i < @{$self->{'_alphabet'}}; $i++ ) {
154             if ($these_colors{$self->{'_alphabet'}->[$i]} ) {
155             delete $self->{'_alphabet'}->[$i] ;
156             }
157             }
158             @{$self->{'_alphabet'}} = grep( $_, @{$self->{'_alphabet'}} ); # Eliminate nulls
159             if ( @{$self->{'_alphabet'}} == 1 ) { # It could happen, and has happened
160             return $self->{'_alphabet'}->[0] x $length;
161             }
162             if ( @{$self->{'_alphabet'}} < $alphabet_size ) {
163             $self->realphabet;
164             if ( !$self->{'_noshrink'} ) {
165             my $shrinkage = @{$self->{'_alphabet'}} /$alphabet_size;
166             print "Shrinking to size ", @$pop * $shrinkage
167             ," with alphabet ", join( " ", @{$self->{'_alphabet'}} ), "\n";
168             $self->shrink_to( (scalar @$pop) * $shrinkage );
169             }
170             }
171            
172             }
173              
174             #Recalculate distances, new turn
175             my (%consistent );
176             my $partitions;
177             my $distance = $self->{'_distance'};
178             # print "Evaluating all \n";
179             for my $p ( @$pop ) {
180             ($p->{'_distance'}, $p->{'_matches'}) = @{$self->$distance( $p->{'_str'} )};
181             # ($p->{'_distance'}, $p->{'_matches'}) = @{$self->distance( $p )};
182             # print "$p->{'_distance'}, $p->{'_matches'}) = $p->{'_str'} \n";
183             if ($p->{'_matches'} == $rules) {
184             push @{$consistent{$p->{'_str'}}}, $p;
185             } else {
186             $p->{'_partitions'} = 0;
187             }
188             }
189            
190             my $number_of_consistent = keys %consistent;
191             if ( $number_of_consistent > 1 ) {
192             $partitions = partitions( keys %consistent );
193             # Need this to compute fitness
194             for my $c ( keys %$partitions ) {
195             for my $p ( @{$consistent{$c}} ) {
196             $p->{'_partitions'} = scalar (keys %{$partitions->{$c}});
197             }
198             }
199             } elsif ( $number_of_consistent == 1 ) {
200             for my $c ( keys %consistent ) {
201             for my $p ( @{$consistent{$c}} ) {
202             $p->{'_partitions'} = 1;
203             }
204             }
205             }
206             my $generations_equal = 0;
207             my $this_number_of_consistent = $number_of_consistent;
208              
209             while ( $this_number_of_consistent < $max_number_of_consistent ) {
210              
211             compute_fitness( $pop );
212             my $new_pop = $ga->apply( $pop, @$pop * $self->{'_replacement_rate'} ); #Apply GA
213             for my $p ( @$new_pop ) {
214             ($p->{'_distance'}, $p->{'_matches'}) = @{$self->$distance( $p->{'_str'} )};
215             if ($p->{'_matches'} == $rules) {
216             push @{$consistent{$p->{'_str'}}}, $p;
217             } else {
218             $p->{'_partitions'} = 0;
219             }
220             }
221             $pop = $self->{'_replacer'}->apply( $pop, $new_pop );
222             $this_number_of_consistent = keys %consistent;
223             if ( $this_number_of_consistent == $number_of_consistent ) {
224             $generations_equal++;
225             } else {
226             $generations_equal = 0;
227             $number_of_consistent = $this_number_of_consistent;
228             # Compute number of partitions
229             if ( $number_of_consistent > 1 ) {
230             $partitions = partitions( keys %consistent );
231             } else {
232             $partitions->{(keys %consistent )[0]} = { "allblacks" => 1}; # I know, this is a hack
233             }
234             }
235             for my $c ( keys %consistent ) {
236             for my $p ( @{$consistent{$c}}) {
237             $p->{'_partitions'} = scalar (keys %{$partitions->{$c}});
238             }
239             }
240              
241             if ($generations_equal == MAX_GENERATIONS_RESET ) { #reset pop
242             # Print for debugging
243             my %population;
244             for my $p ( @$pop ) {
245             $population{$p->{'_str'}}++;
246             }
247             for my $s ( sort { $population{$b} <=> $population{$a} } keys %population ) {
248             print $s, ": ", $population{$s}, " C\n";
249             }
250             print "Reset\n\n";
251             #Do the thing
252             $ga->reset( $pop );
253             for my $p ( @$pop ) {
254             ($p->{'_distance'}, $p->{'_matches'}) = @{$self->$distance( $p->{'_str'} )};
255             }
256             $generations_equal = 0;
257             }
258             last if ( $generations_equal >= MAX_GENERATIONS_EQUAL )
259             && ( $this_number_of_consistent >= 1 ) ;
260             } # end while
261            
262             $self->{'_consistent'} = \%consistent; #This mainly for outside info
263             if ( $this_number_of_consistent > 1 ) {
264             my $max_partitions = 0;
265             my %max_c;
266             for my $c ( keys %$partitions ) {
267             my $this_max = keys %{$partitions->{$c}};
268             $max_c{$c} = $this_max;
269             if ( $this_max > $max_partitions ) {
270             $max_partitions = $this_max;
271             }
272             }
273             # Find all partitions with that max
274             my @max_c = grep( $max_c{$_} == $max_partitions, keys %max_c );
275             # Break ties
276             my $string = $max_c[ rand( @max_c )];
277             # Obtain next
278             return $self->{'_last'} = $string;
279             } else {
280             return $self->{'_last'} = (keys %consistent)[0];
281             }
282             }
283             }
284              
285             "some blacks, 1 white"; # Magic true value required at end of module
286              
287             __END__