File Coverage

blib/lib/Game/Theory/TwoPersonMatrix.pm
Criterion Covered Total %
statement 244 244 100.0
branch 45 48 93.7
condition 24 33 72.7
subroutine 26 26 100.0
pod 12 12 100.0
total 351 363 96.6


line stmt bran cond sub pod time code
1             package Game::Theory::TwoPersonMatrix;
2             our $AUTHORITY = 'cpan:GENE';
3              
4             # ABSTRACT: Analyze a 2 person matrix game
5              
6 1     1   774 use strict;
  1         2  
  1         29  
7 1     1   5 use warnings;
  1         2  
  1         40  
8              
9             our $VERSION = '0.2207';
10              
11 1     1   5 use Carp qw( carp );
  1         2  
  1         49  
12 1     1   489 use Algorithm::Combinatorics qw( permutations );
  1         3575  
  1         66  
13 1     1   439 use Array::Transpose qw( transpose );
  1         366  
  1         59  
14 1     1   522 use List::SomeUtils qw( all zip );
  1         12755  
  1         91  
15 1     1   8 use List::Util qw( max min sum0 );
  1         4  
  1         58  
16 1     1   426 use List::Util::WeightedChoice qw( choose_weighted );
  1         6106  
  1         2861  
17              
18              
19              
20             sub new {
21 38     38 1 1678 my $class = shift;
22 38         114 my %args = @_;
23             my $self = {
24             1 => $args{1},
25             2 => $args{2},
26             payoff => $args{payoff},
27             payoff1 => $args{payoff1},
28             payoff2 => $args{payoff2},
29 38         147 };
30 38         73 bless $self, $class;
31 38         102 return $self;
32             }
33              
34              
35             sub expected_payoff
36             {
37 13     13 1 2201 my ($self) = @_;
38              
39 13         21 my $expected_payoff;
40              
41             # For each strategy of player 1...
42 13         21 for my $i ( sort keys %{ $self->{1} } )
  13         78  
43             {
44             # For each strategy of player 2...
45 29         47 for my $j ( sort keys %{ $self->{2} } )
  29         77  
46             {
47             # Expected value is the sum of the probabilities of each payoff
48 76 100 66     181 if ( $self->{payoff1} && $self->{payoff2} )
49             {
50 8         38 $expected_payoff->[0] += $self->{1}{$i} * $self->{2}{$j} * $self->{payoff1}[$i - 1][$j - 1];
51 8         26 $expected_payoff->[1] += $self->{1}{$i} * $self->{2}{$j} * $self->{payoff2}[$i - 1][$j - 1];
52             }
53             else {
54 68         210 $expected_payoff += $self->{1}{$i} * $self->{2}{$j} * $self->{payoff}[$i - 1][$j - 1];
55             }
56             }
57             }
58              
59 13         116 return $expected_payoff;
60             }
61              
62              
63             sub s_expected_payoff
64             {
65 4     4 1 1450 my ($self) = @_;
66              
67 4         6 my $expected_payoff;
68              
69             # For each strategy of player 1...
70 4         7 for my $i ( sort keys %{ $self->{1} } )
  4         21  
71             {
72             # For each strategy of player 2...
73 8         11 for my $j ( sort keys %{ $self->{2} } )
  8         22  
74             {
75             # Expected value is the sum of the probabilities of each payoff
76 16 100 66     53 if ( $self->{payoff1} && $self->{payoff2} )
77             {
78 8         31 $expected_payoff->[0] .= " + $self->{1}{$i} * $self->{2}{$j} * $self->{payoff1}[$i - 1][$j - 1]";
79 8         22 $expected_payoff->[1] .= " + $self->{1}{$i} * $self->{2}{$j} * $self->{payoff2}[$i - 1][$j - 1]";
80             }
81             else {
82 8         31 $expected_payoff .= " + $self->{1}{$i} * $self->{2}{$j} * $self->{payoff}[$i - 1][$j - 1]";
83             }
84             }
85             }
86              
87             my $deplus = sub
88             {
89 6     6   12 my ($string) = @_;
90 6         34 $string =~ s/^ \+ (.+)$/$1/;
91 6         17 return $string;
92 4         18 };
93              
94 4 100 66     17 if ( $self->{payoff1} && $self->{payoff2} )
95             {
96 2         5 $expected_payoff->[0] = $deplus->($expected_payoff->[0]);
97 2         5 $expected_payoff->[1] = $deplus->($expected_payoff->[1]);
98             }
99             else {
100 2         5 $expected_payoff = $deplus->($expected_payoff);
101             }
102              
103 4         29 return $expected_payoff;
104             }
105              
106              
107             sub counter_strategy
108             {
109 3     3 1 723 my ( $self, $player ) = @_;
110              
111 3         8 my $counter_strategy = [];
112 3         6 my %seen;
113              
114 3 100       9 my $opponent = $player == 1 ? 2 : 1;
115              
116 3         10 my @keys = 1 .. keys %{ $self->{$player} };
  3         12  
117 3         12 my @pure = ( 1, (0) x ( @keys - 1 ) );
118              
119 3         15 my $i = permutations( \@pure );
120              
121 3         173 while ( my $strategies = $i->next )
122             {
123 10 100       175 next if $seen{"@$strategies"}++;
124              
125             my $g = Game::Theory::TwoPersonMatrix->new(
126             $player => { zip @keys, @$strategies },
127             $opponent => $self->{$opponent},
128 7   66     82 payoff => $self->{payoff} || $self->{"payoff$player"},
129             );
130              
131 7         21 push @$counter_strategy, $g->expected_payoff;
132             }
133              
134 3         55 return $counter_strategy;
135             }
136              
137              
138             sub saddlepoint
139             {
140 5     5 1 1800 my ($self) = @_;
141              
142 5         9 my $saddlepoint;
143              
144 5         9 my $rsize = @{ $self->{payoff} } - 1;
  5         12  
145 5         7 my $csize = @{ $self->{payoff}[0] } - 1;
  5         11  
146              
147 5         16 for my $row ( 0 .. $rsize )
148             {
149             # Get the minimum value of the current row
150 14         19 my $min = min @{ $self->{payoff}[$row] };
  14         37  
151              
152             # Inspect each column given the row
153 14         26 for my $col ( 0 .. $csize )
154             {
155             # Get the payoff
156 36         56 my $val = $self->{payoff}[$row][$col];
157              
158             # Is the payoff also the row minimum?
159 36 100       72 if ( $val == $min )
160             {
161             # Gather the column values for each row
162 16         27 my @col;
163 16         23 for my $r ( 0 .. $rsize )
164             {
165 50         82 push @col, $self->{payoff}[$r][$col];
166             }
167             # Get the maximum value of the columns
168 16         30 my $max = max @col;
169              
170             # Is the payoff also the column maximum?
171 16 100       37 if ( $val == $max )
172             {
173 5         20 $saddlepoint->{"$row,$col"} = $val;
174             }
175             }
176             }
177             }
178              
179 5         28 return $saddlepoint;
180             }
181              
182              
183             sub oddments
184             {
185 1     1 1 3 my ($self) = @_;
186              
187 1         2 my $rsize = @{ $self->{payoff}[0] };
  1         4  
188 1         2 my $csize = @{ $self->{payoff} };
  1         3  
189 1 50 33     7 carp 'Payoff matrix must be 2x2' unless $rsize == 2 && $csize == 2;
190              
191 1         2 my ( $player, $opponent );
192              
193 1         2 my $A = $self->{payoff}[0][0];
194 1         2 my $B = $self->{payoff}[0][1];
195 1         2 my $C = $self->{payoff}[1][0];
196 1         2 my $D = $self->{payoff}[1][1];
197              
198 1         2 my ( $x, $y );
199 1         3 $x = abs( $D - $C );
200 1         2 $y = abs( $A - $B );
201 1         2 my $i = $x / ( $x + $y );
202 1         3 my $j = $y / ( $x + $y );
203 1         2 $player = [ $i, $j ];
204              
205 1         2 $x = abs( $D - $B );
206 1         2 $y = abs( $A - $C );
207 1         2 $i = $x / ( $x + $y );
208 1         1 $j = $y / ( $x + $y );
209 1         3 $opponent = [ $i, $j ];
210              
211 1         19 return [ $player, $opponent ];
212             }
213              
214              
215             sub row_reduce
216             {
217 2     2 1 345 my ($self) = @_;
218              
219 2         4 my @spliced;
220              
221 2         4 my $rsize = @{ $self->{payoff} } - 1;
  2         5  
222 2         3 my $csize = @{ $self->{payoff}[0] } - 1;
  2         6  
223              
224 2         6 for my $row ( 0 .. $rsize )
225             {
226             #warn "R:$row = @{ $self->{payoff}[$row] }\n";
227 7         14 for my $r ( 0 .. $rsize )
228             {
229 25 100       59 next if $r == $row;
230             #warn "\tN:$r = @{ $self->{payoff}[$r] }\n";
231 18         26 my @cmp;
232 18         31 for my $x ( 0 .. $csize )
233             {
234 54 100       110 push @cmp, ( $self->{payoff}[$row][$x] <= $self->{payoff}[$r][$x] ? 1 : 0 );
235             }
236             #warn "\t\tC:@cmp\n";
237 18 100   30   54 if ( all { $_ == 1 } @cmp )
  30         71  
238             {
239 3         8 push @spliced, $row;
240             }
241             }
242             }
243              
244 2         7 $self->_reduce_game( $self->{payoff}, \@spliced, 1 );
245              
246 2         13 return $self->{payoff};
247             }
248              
249              
250             sub col_reduce
251             {
252 3     3 1 351 my ($self) = @_;
253              
254 3         6 my @spliced;
255              
256 3         10 my $transposed = transpose( $self->{payoff} );
257              
258 3         143 my $rsize = @$transposed - 1;
259 3         7 my $csize = @{ $transposed->[0] } - 1;
  3         5  
260              
261 3         7 for my $row ( 0 .. $rsize )
262             {
263             #warn "R:$row = @{ $transposed->[$row] }\n";
264 10         17 for my $r ( 0 .. $rsize )
265             {
266 34 100       93 next if $r == $row;
267             #warn "\tN:$r = @{ $transposed->[$r] }\n";
268 24         32 my @cmp;
269 24         42 for my $x ( 0 .. $csize )
270             {
271 72 100       153 push @cmp, ( $transposed->[$row][$x] >= $transposed->[$r][$x] ? 1 : 0 );
272             }
273             #warn "\t\tC:@cmp\n";
274 24 100   42   98 if ( all { $_ == 1 } @cmp )
  42         99  
275             {
276 3         8 push @spliced, $row;
277             }
278             }
279             }
280              
281 3         10 $self->_reduce_game( $transposed, \@spliced, 2 );
282              
283 3         20 $self->{payoff} = transpose( $transposed );
284              
285 3         113 return $self->{payoff};
286             }
287              
288             sub _reduce_game
289             {
290 5     5   12 my ( $self, $payoff, $spliced, $player ) = @_;
291              
292 5         9 my $seen = 0;
293 5         10 for my $row ( @$spliced )
294             {
295 6         10 $row -= $seen++;
296             # Reduce the payoff column
297 6         12 splice @$payoff, $row, 1;
298             # Eliminate the strategy of the opponent
299 6 50       30 delete $self->{$player}{$row + 1} if exists $self->{$player}{$row + 1};
300             }
301             }
302              
303              
304             sub mm_tally
305             {
306 2     2 1 699 my ($self) = @_;
307              
308 2         3 my $mm_tally;
309              
310 2 100 66     22 if ( $self->{payoff1} && $self->{payoff2} )
311             {
312             # Find maximum of row minimums for the player
313 1         4 $mm_tally = $self->_tally_max( $mm_tally, 1, $self->{payoff1} );
314              
315             # Find minimum of column maximums for the opponent
316 1         2 my @m = ();
317 1         3 my %s = ();
318              
319 1         4 my $transposed = transpose( $self->{payoff2} );
320              
321 1         28 for my $row ( 0 .. @$transposed - 1 )
322             {
323 2         4 $s{$row} = min @{ $transposed->[$row] };
  2         5  
324 2         5 push @m, $s{$row};
325             }
326              
327 1         14 $mm_tally->{2}{value} = max @m;
328              
329 1         5 for my $row ( sort keys %s )
330             {
331 2 100       4 push @{ $mm_tally->{2}{strategy} }, ( $s{$row} == $mm_tally->{2}{value} ? 1 : 0 );
  2         10  
332             }
333             }
334             else
335             {
336             # Find maximum of row minimums
337 1         4 $mm_tally = $self->_tally_max( $mm_tally, 1, $self->{payoff} );
338              
339             # Find minimum of column maximums
340 1         3 my @m = ();
341 1         3 my %s = ();
342              
343 1         4 my $transposed = transpose( $self->{payoff} );
344              
345 1         39 for my $row ( 0 .. @$transposed - 1 )
346             {
347 4         6 $s{$row} = max @{ $transposed->[$row] };
  4         9  
348 4         10 push @m, $s{$row};
349             }
350              
351 1         4 $mm_tally->{2}{value} = min @m;
352              
353 1         5 for my $row ( sort keys %s )
354             {
355 4 100       7 push @{ $mm_tally->{2}{strategy} }, ( $s{$row} == $mm_tally->{2}{value} ? 1 : 0 );
  4         13  
356             }
357             }
358              
359 2         20 return $mm_tally;
360             }
361              
362             sub _tally_max
363             {
364 2     2   6 my ( $self, $mm_tally, $player, $payoff ) = @_;
365              
366 2         4 my @m;
367             my %s;
368              
369             # Find maximum of row minimums
370 2         7 for my $row ( 0 .. @$payoff - 1 )
371             {
372 5         8 $s{$row} = min @{ $payoff->[$row] };
  5         16  
373 5         12 push @m, $s{$row};
374             }
375              
376 2         9 $mm_tally->{$player}{value} = max @m;
377              
378 2         12 for my $row ( sort keys %s )
379             {
380 5 100       7 push @{ $mm_tally->{$player}{strategy} }, ( $s{$row} == $mm_tally->{$player}{value} ? 1 : 0 );
  5         19  
381             }
382              
383 2         7 return $mm_tally;
384             }
385              
386              
387             sub pareto_optimal
388             {
389 4     4 1 356 my ($self) = @_;
390              
391 4         9 my $pareto_optimal;
392              
393 4         7 my $rsize = @{ $self->{payoff1} } - 1;
  4         11  
394 4         6 my $csize = @{ $self->{payoff1}[0] } - 1;
  4         9  
395              
396             # Compare each row & column with every other
397 4         11 for my $row ( 0 .. $rsize )
398             {
399 8         16 for my $col ( 0 .. $csize )
400             {
401             #warn "RC:$row,$col = ($self->{payoff1}[$row][$col],$self->{payoff2}[$row][$col])\n";
402              
403             # Find all pairs to compare against
404 16         22 my %seen;
405 16         27 for my $r ( 0 .. $rsize )
406             {
407 32         48 for my $c ( 0 .. $csize )
408             {
409 64 100 100     309 next if ( $r == $row && $c == $col ) || $seen{"$r,$c"}++;
      66        
410              
411 48         82 my $p = $self->{payoff1}[$row][$col];
412 48         70 my $q = $self->{payoff2}[$row][$col];
413             #warn "\trc:$r,$c = ($self->{payoff1}[$r][$c],$self->{payoff2}[$r][$c])\n";
414              
415 48 100 100     145 if ( $p >= $self->{payoff1}[$r][$c] && $q >= $self->{payoff2}[$r][$c] )
416             {
417             #warn "\t\t$row,$col > $r,$c at ($p,$q)\n";
418             # XXX We exploit the unique key feature of perl hashes
419 8         32 $pareto_optimal->{ "$row,$col" } = [ $p, $q ];
420             }
421             }
422             }
423             }
424             }
425              
426 4         28 return $pareto_optimal;
427             }
428              
429              
430             sub nash
431             {
432 7     7 1 2535 my ($self) = @_;
433              
434 7         12 my $nash;
435              
436 7         11 my $rsize = @{ $self->{payoff1} } - 1;
  7         17  
437 7         12 my $csize = @{ $self->{payoff1}[0] } - 1;
  7         13  
438              
439             # Find all row & column max pairs
440 7         19 for my $row ( 0 .. $rsize )
441             {
442 15         23 my $rmax = max @{ $self->{payoff2}[$row] };
  15         39  
443              
444 15         29 for my $col ( 0 .. $csize )
445             {
446             #warn "RC:$row,$col = ($self->{payoff1}[$row][$col],$self->{payoff2}[$row][$col])\n";
447              
448 36         43 my @col;
449 36         64 for my $r ( 0 .. $rsize )
450             {
451 84         136 push @col, $self->{payoff1}[$r][$col];
452             }
453 36         65 my $cmax = max @col;
454              
455 36         56 my $p = $self->{payoff1}[$row][$col];
456 36         52 my $q = $self->{payoff2}[$row][$col];
457              
458 36 100 100     110 if ( $p == $cmax && $q == $rmax )
459             {
460             #warn "\t$p == $cmax && $q == $rmax\n";
461 10         42 $nash->{"$row,$col"} = [ $p, $q ];
462             }
463             }
464             }
465              
466 7         49 return $nash;
467             }
468              
469              
470             sub play
471             {
472 4     4 1 768 my ( $self, %strategies ) = @_;
473              
474 4         7 my $play;
475              
476             # Allow for alternate strategies
477 4         20 $self->{$_} = $strategies{$_} for keys %strategies;
478              
479 4         11 my $rplay = $self->_player_move(1);
480 4         292 my $cplay = $self->_player_move(2);
481              
482             $play->{ "$rplay,$cplay" } = exists $self->{payoff} && $self->{payoff}
483             ? $self->{payoff}[$rplay - 1][$cplay - 1]
484 4 100 66     211 : [ $self->{payoff1}[$rplay - 1][$cplay - 1], $self->{payoff2}[$rplay - 1][$cplay - 1] ];
485              
486 4         27 return $play;
487             }
488              
489             sub _player_move {
490 8     8   17 my ( $self, $player ) = @_;
491              
492 8         15 my $keys = [ sort keys %{ $self->{$player} } ];
  8         36  
493 8         19 my $weights = [ map { $self->{$player}{$_} } @$keys ];
  16         38  
494              
495             # Handle the [0, 0, ...] edge case
496 8 50       36 $weights = [ (1) x @$weights ] if 0 == sum0 @$weights;
497              
498 8         26 return choose_weighted( $keys, $weights );
499             }
500              
501             1;
502              
503             __END__