File Coverage

blib/lib/Algorithm/MasterMind.pm
Criterion Covered Total %
statement 134 198 67.6
branch 16 28 57.1
condition 6 18 33.3
subroutine 22 31 70.9
pod 25 25 100.0
total 203 300 67.6


line stmt bran cond sub pod time code
1             package Algorithm::MasterMind;
2              
3 18     18   567280 use warnings;
  18         46  
  18         868  
4 18     18   100 use strict;
  18         34  
  18         511  
5 18     18   201 use Carp;
  18         34  
  18         2353  
6              
7 18     18   17351 use version; our $VERSION = qv("v0.4.5"); #Quest for non-failing tests
  18         46277  
  18         107  
8              
9 18     18   34889 use Algorithm::Combinatorics qw(variations_with_repetition);
  18         93351  
  18         5248  
10              
11             #use Memoize;
12             #memoize( "check_rule" );
13              
14             our @ISA = qw(Exporter);
15              
16             our @EXPORT_OK = qw( check_combination partitions entropy random_string
17             response_as_string);
18              
19 18     18   970 use lib qw( ../../lib ../lib ../../../lib );
  18         683  
  18         157  
20              
21             # Module implementation here
22              
23             sub new {
24              
25 12     12 1 11793 my $class = shift;
26 12   33     56 my $options = shift || croak "Need options here in Algorithm::MasterMind::New\n";
27              
28 12         76 my $self = { _rules => [],
29             _evaluated => 0,
30             _hash_rules => {} };
31              
32 12         46 bless $self, $class;
33 12         288 $self->initialize( $options );
34 11         31 return $self;
35             }
36              
37             sub random_combination {
38 3     3 1 6 my $self = shift;
39 3         15 return random_string( $self->{'_alphabet'}, $self->{'_length'});
40             }
41              
42             sub random_string {
43 132     132 1 25941 my $alphabet = shift;
44 132         142 my $length = shift;
45 132         126 my $string;
46 132         147 my @alphabet = @{$alphabet};
  132         323  
47 132         336 for (my $i = 0; $i < $length; $i++ ) {
48 528         1698 $string .= $alphabet[ rand( @alphabet) ];
49             }
50 132         6065 return $string;
51             }
52              
53             sub issue_first { #Default implementation
54 3     3 1 1566 my $self = shift;
55 3         50 return $self->{'_last'} = $self->random_combination;
56             }
57              
58             sub start_from {
59 0     0 1 0 my $class = shift;
60 0   0     0 my $options = shift || croak "Options needed to start!";
61              
62 0         0 my $self = {};
63 0         0 bless $self, $class;
64 0         0 for my $o ( qw( consistent alphabet rules evaluated ) ) {
65 0         0 $self->{"_$o"} = $options->{$o};
66             }
67 0         0 return $self;
68             }
69              
70             sub issue_first_Knuth {
71 1     1 1 2 my $self = shift;
72 1         2 my $string;
73 1         2 my @alphabet = @{ $self->{'_alphabet'}};
  1         6  
74 1         4 my $half = @alphabet/2;
75 1         7 for ( my $i = 0; $i < $self->{'_length'}; $i ++ ) {
76 4         13 $string .= $alphabet[ $i % $half ]; # Recommendation Knuth
77             }
78 1         4 $self->{'_first'} = 1; # Flag to know when the second is due
79 1         9 return $self->{'_last_string'} = $string;
80             }
81              
82             sub issue_next {
83 0     0 1 0 croak "To be reimplemented in derived classes";
84             }
85              
86             sub add_rule {
87 22     22 1 44 my $self = shift;
88 22         47 my ($combination, $result) = @_;
89 22         99 my %new_rule = %$result;
90 22         79 $new_rule{'combination'} = $combination;
91 22         577 push @{ $self->{'_rules'} }, \%new_rule;
  22         247  
92              
93             }
94              
95             sub feedback {
96 18     18 1 34 my $self = shift;
97 18         34 my ($result) = @_;
98 18         3295 $self->add_rule( $self->{'_last'}, $result );
99             }
100              
101             sub number_of_rules {
102 27     27 1 388 my $self= shift;
103 27         39 return scalar @{ $self->{'_rules'}};
  27         10832  
104             }
105              
106             sub rules {
107 0     0 1 0 my $self= shift;
108 0         0 return $self->{'_rules'};
109             }
110              
111             sub evaluated {
112 5     5 1 76 my $self=shift;
113 5         1711 return $self->{'_evaluated'};
114             }
115              
116             sub matches {
117              
118 4234     4234 1 8153 my $self = shift;
119 4234   33     8270 my $string = shift || croak "No string\n";
120 4234         4160 my @rules = @{$self->{'_rules'}};
  4234         10918  
121 4234         12292 my $result = { matches => 0,
122             result => [] };
123             # print "Checking $string, ", $self->{'_evaluated'}, "\n";
124 4234         6683 for my $r ( @rules ) {
125 11239         20713 my $rule_result = $self->check_rule( $r, $string );
126 11239 100       22256 $result->{'matches'}++ if ( $rule_result->{'match'} );
127 11239         10264 push @{ $result->{'result'} }, $rule_result;
  11239         25956  
128             }
129 4234         6245 $self->{'_evaluated'}++;
130 4234         17340 return $result;
131             }
132              
133             sub check_rule {
134 11239     11239 1 11512 my $self = shift;
135 11239         10448 my $rule = shift;
136 11239         11702 my $string = shift;
137 11239 100       33631 if ( ! $self->{'_rules_hash'}->{ $rule->{'combination'} }{ $string } ) {
138 10874         18367 my $result = check_combination( $rule->{'combination'}, $string );
139 10874 100 100     44209 if ( ( $rule->{'blacks'} == $result->{'blacks'} )
140             && ( $rule->{'whites'} == $result->{'whites'} ) ) {
141 805         1318 $result->{'match'} = 1;
142             } else {
143 10069         20199 $result->{'match'} = 0;
144             }
145 10874         31369 $self->{'_rules_hash'}->{ $rule->{'combination'} }{ $string } = $result;
146             }
147 11239         38915 return $self->{'_rules_hash'}->{ $rule->{'combination'} }{ $string };
148             }
149              
150             sub check_combination {
151 44402     44402 1 281976 my $combination = shift;
152 44402         67405 my $string = shift;
153              
154 44402         41656 my ( %hash_combination, %hash_string );
155 44402         52073 my $blacks = 0;
156 44402         47361 my ($c, $s);
157 44402         104398 while ( $c = chop( $combination ) ) {
158 177580         202812 $s = chop( $string );
159 177580 100       270244 if ( $c eq $s ) {
160 40391         85492 $blacks++;
161             } else {
162 137189         180347 $hash_combination{ $c }++;
163 137189         345281 $hash_string{ $s }++;
164             }
165             }
166 44402         52221 my $whites = 0;
167 44402         100324 for my $k ( keys %hash_combination ) {
168 102911 100       257771 next if ! defined $hash_string{$k};
169 47812 100       114202 $whites += ($hash_combination{$k} > $hash_string{$k})
170             ?$hash_string{$k}
171             :$hash_combination{$k};
172             }
173 44402         235543 return { blacks => $blacks,
174             whites => $whites };
175             }
176              
177             sub distance_taxicab {
178 7     7 1 32 my $self = shift;
179 7   33     16 my $combination = shift || croak "Can't compute distance to nothing";
180 7         15 my $matches = $self->matches( $combination );
181              
182 7         10 my $distance = 0;
183 7         8 my @rules = @{$self->{'_rules'}};
  7         17  
184 7         22 for ( my $r = 0; $r <= $#rules; $r++) {
185 12         61 $distance -= abs( $rules[$r]->{'blacks'} - $matches->{'result'}->[$r]->{'blacks'} ) +
186             abs( $rules[$r]->{'whites'} - $matches->{'result'}->[$r]->{'whites'} );
187             }
188              
189 7         43 return [$distance, $matches->{'matches'}];
190             }
191              
192             sub distance_chebyshev {
193 0     0 1 0 my $self = shift;
194 0   0     0 my $combination = shift || croak "Can't compute distance to nothing";
195 0         0 my $rules = $self->number_of_rules();
196 0         0 my $matches = $self->matches( $combination );
197              
198 0         0 my $distance = 0;
199 0         0 my @rules = @{$self->{'_rules'}};
  0         0  
200 0         0 for ( my $r = 0; $r <= $#rules; $r++) {
201 0         0 my $diff_black = abs( $rules[$r]->{'blacks'} - $matches->{'result'}->[$r]->{'blacks'});
202 0         0 my $diff_white = abs( $rules[$r]->{'whites'} - $matches->{'result'}->[$r]->{'whites'} );
203 0 0       0 my $this_distance = ($diff_black > $diff_white)?$diff_black:$diff_white;
204 0         0 $distance -= $this_distance ;
205             }
206              
207 0         0 return [$distance, $matches->{'matches'}];
208             }
209              
210             sub check_combination_old {
211 0     0 1 0 my $combination = shift;
212 0         0 my $string = shift;
213              
214 0         0 my @combination_arr = split(//, $combination );
215 0         0 my @string_arr = split(//, $string);
216 0         0 my $blacks = 0;
217 0         0 for ( my $i = 0; $i < length($combination); $i ++ ) {
218 0 0       0 if ( $combination_arr[ $i ] eq $string_arr[ $i ] ) {
219 0         0 $combination_arr[ $i ] = $string_arr[ $i ] = 0;
220 0         0 $blacks++;
221             }
222             }
223 0         0 my %hash_combination;
224 0         0 map( $hash_combination{$_}++, @combination_arr);
225 0         0 my %hash_string;
226 0         0 map( $hash_string{$_}++, @string_arr);
227 0         0 my $whites = 0;
228 0         0 for my $k ( keys %hash_combination ) {
229 0 0       0 next if $k eq '0'; # Mark for "already computed"
230 0 0       0 next if ! defined $hash_string{$k};
231 0 0       0 $whites += ($hash_combination{$k} > $hash_string{$k})
232             ?$hash_string{$k}
233             :$hash_combination{$k};
234             }
235 0         0 return { blacks => $blacks,
236             whites => $whites };
237             }
238              
239             sub hashify {
240 0     0 1 0 my $str = shift;
241 0         0 my %hash;
242 0         0 map( $hash{$_}++, split(//, $str));
243 0         0 return %hash;
244             }
245              
246             sub not_in_combination {
247 0     0 1 0 my $self = shift;
248 0         0 my $combination = shift;
249 0         0 my @alphabet = @{$self->{'_alphabet'}};
  0         0  
250 0         0 my %alphabet_hash;
251 0         0 map( $alphabet_hash{$_}=1, @alphabet );
252 0         0 for my $l ( split(//, $combination ) ) {
253 0 0       0 delete $alphabet_hash{$l} if $alphabet_hash{$l};
254             }
255 0         0 return keys %alphabet_hash;
256             }
257              
258             sub partitions {
259 5     5 1 1642 my @combinations = sort @_;
260              
261 5         10 my %partitions;
262             my %hash_results;
263 5         986 for my $c ( @combinations ) {
264 316         517 for my $cc ( @combinations ) {
265 67304 100       149014 next if $c eq $cc;
266 66988         76294 my $result;
267 66988 100       110305 if ( $c lt $cc ) {
268 33494         54936 $result = check_combination ( $c, $cc );
269 33494         84857 $hash_results{$c}{$cc} = $result;
270             } else {
271 33494         104382 $result = $hash_results{$cc}{$c};
272             }
273 66988         264973 $partitions{$c}{$result->{'blacks'}."b-".$result->{'whites'}."w"}++;
274             }
275            
276             }
277 5         19581 return \%partitions;
278             }
279              
280             sub all_combinations {
281 4     4 1 1516 my $self = shift;
282 4         28 my @combinations_array = variations_with_repetition( $self->{'_alphabet'},
283             $self->{'_length'});
284 4         27282 my @combinations = map( join( "", @$_), @combinations_array );
285            
286             }
287              
288             sub all_responses {
289 5     5 1 53 my $self = shift;
290 5         12 my $length = $self->{'_length'};
291 5         35 my @responses_array = variations_with_repetition( ['B', 'W', '-'],
292             $length );
293 5         34460 my %responses;
294 5         16 for my $r ( @responses_array ) {
295 3267         5884 my %partial = ( W => 0,
296             B => 0 );
297 3267         4528 for my $c (@$r) {
298 21303         27037 $partial{$c}++;
299             }
300            
301 3267         10621 $responses{$partial{'B'}."B-".$partial{'W'}."W"} = 1;
302             }
303             # Delete impossible
304 5         18 my $impossible = ($length-1)."B-1W";
305 5         15 delete $responses{$impossible};
306 5         86 my @possible_responses = sort keys %responses;
307 5         2325 return @possible_responses;
308              
309             }
310              
311             sub entropy {
312 0     0 1   my $combination = shift;
313 0           my %freqs;
314 0           map( $freqs{$_}++, split( //, $combination));
315 0           my $entropy;
316 0           for my $k (keys %freqs ) {
317 0           my $probability = $freqs{$k}/length($combination);
318 0           $entropy -= $probability * log ($probability);
319             }
320 0           return $entropy;
321             }
322              
323             sub response_as_string {
324 0     0 1   return $_[0]->{'blacks'}."b-".$_[0]->{'whites'}."w";
325             }
326            
327              
328             "4 blacks, 0 white"; # Magic true value required at end of module
329              
330             __END__