File Coverage

blib/lib/Games/Tournament/Card.pm
Criterion Covered Total %
statement 133 174 76.4
branch 43 66 65.1
condition 12 24 50.0
subroutine 25 29 86.2
pod 17 17 100.0
total 230 310 74.1


line stmt bran cond sub pod time code
1             package Games::Tournament::Card;
2             $Games::Tournament::Card::VERSION = '0.19';
3             # Last Edit: 2011 2月 27, 21時34分46秒
4             # $Id: $
5              
6 27     27   2778 use warnings;
  27         53  
  27         780  
7 27     27   134 use strict;
  27         114  
  27         540  
8 27     27   130 use Carp;
  27         44  
  27         1656  
9              
10 27     27   133 use List::Util qw/min reduce sum first/;
  27         47  
  27         1945  
11 27     27   132 use List::MoreUtils qw/any all/;
  27         210  
  27         176  
12 27     27   12412 use Scalar::Util qw/looks_like_number/;
  27         48  
  27         1922  
13              
14 27 100       53110 use constant ROLES => @Games::Tournament::Swiss::Config::roles?
15             @Games::Tournament::Swiss::Config::roles:
16 27     27   137 Games::Tournament::Swiss::Config->roles;
  27         44  
17              
18             =head1 NAME
19              
20             Games::Tournament::Card - A record of the results of a match
21              
22             =cut
23              
24             =head1 SYNOPSIS
25              
26             $game = Games::Tournament:Card->new(round => 1, contestants => {Black => $knicks, White => $deepblue}, result => { Black => 'Win', White => 'Loss' });
27              
28             =head1 DESCRIPTION
29              
30             In a tournament, matches take place in rounds between contestants, who are maybe floated, and who have roles, and there is a result for these matches, which can be written on a card.
31              
32             =head1 METHODS
33              
34             =head2 new
35              
36             $game = Games::Tournament:Card->new(
37             round => 1,
38             contestants => {Black => $knicks, White => $deepblue},
39             result => { Black => 'Win', White => 'Loss' },
40             floats => { Black => 'Up', White => 'Down' }, or
41             floats => { Black => 'Not', White => 'Not' }
42             );
43             $bye = Games::Tournament:Card->new(
44             round => 1,
45             contestants => {Bye => $player},
46             result => "Bye"
47             floats => 'Down' );
48              
49             'contestants' is a hash ref of player objects, keyed on Black and White, or Home and Away, or some other role distinction that needs to be balanced over the tournament. The players are probably instances of the Games::Tournament::Contestant::Swiss class. 'result' is a hash reference, keyed on the same keys as contestants, containing the results of the match. 'floats' is a hash of which role was floated up and which down. The default is neither contestant was floated, and 'Down' for a Bye. A4. What are the fields in Forfeits and byes? Forfeit and Tardy have no special form, other than { White => 'Forfeit', Black => 'Tardy' }. Bye is { Bye => $player }. TODO Perhaps the fields should be Winner and Loser, and Down and Up?
50              
51             =cut
52              
53             sub new {
54 608     608 1 2125 my $self = shift;
55 608         2097 my %args = @_;
56 608         2008 return bless \%args, $self;
57             }
58              
59              
60             =head2 canonize
61              
62             $game->canonize
63              
64             Fleshes out a partial statement of the result. From an abbreviated match result (eg, { Black => 'Win' }), works out a canonical representation (eg, { Black => 'Win', White => 'Loss' }). A bye result is represented as { Bye => 'Bye' }.
65              
66             =cut
67              
68             sub canonize {
69 22     22 1 71 my $self = shift;
70 22         45 my $round = $self->round;
71 22         43 my $contestants = $self->contestants;
72 22         44 my $result = $self->result;
73 22         24 my %result;
74 22         57 my %roles = map { $contestants->{$_}->{id} => $_ } keys %$contestants;
  42         151  
75 22 50 66     83 warn
76 0         0 "Incomplete match of @{[values( %roles )]} players @{[map {$_->id} values %$contestants]} in round $round.\n"
  0         0  
  0         0  
77             unless keys %roles == 2
78             or grep m/bye/i, values %roles;
79 22         45 ROLE: foreach my $contestant ( values %$contestants ) {
80 42         81 my $role = $roles{ $contestant->{id} };
81 42 100       130 if ( $role eq 'Bye' ) {
    100          
    50          
82 2         7 $result{$role} = $result->{$role} = 'Bye';
83             }
84             elsif ( exists $result->{$role} ) {
85 20 50       68 if ( $result->{$role} =~ m/^(?:Win|Loss|Draw|Forfeit)$/i ) {
86 20         44 $result{$role} = $result->{$role};
87             }
88             else {
89 0         0 warn
90             "$result->{$role} result for player $contestant->{id} in round $round";
91             }
92 20         38 next ROLE;
93             }
94             elsif ( values %$contestants != 1 ) {
95             my @opponents =
96 20         31 grep { $contestant->id ne $_->id } values %$contestants;
  40         98  
97 20         31 my $opponent = $opponents[0];
98 20         44 my $other = $roles{ $opponent->id };
99 20 50       47 if ( exists $result->{$other} ) {
100             $result{$role} = 'Loss'
101 20 100       72 if $result->{$other} =~ m/^Win$/i;
102             $result{$role} = 'Win'
103 20 100       72 if $result->{$other} =~ m/^Loss$/i;
104             $result{$role} = 'Draw'
105 20 50       77 if $result->{$other} =~ m/^Draw$/i;
106             }
107             else {
108 0         0 warn
109             "$result->{$role}, $result->{$other} result for player $contestant->{id} and opponent $opponent->{id} in round $round";
110             }
111             }
112             else {
113 0         0 die "Not a Bye, but no result or no partner";
114             }
115             }
116 22         51 $self->result( \%result );
117             }
118              
119              
120             =head2 myResult
121              
122             $game->myResult($player)
123              
124             Returns the result for $player from $game, eg 'Win', 'Loss' or 'Draw'.
125             TODO Should return 0,0.5,1 in numerical context.
126              
127             =cut
128              
129             sub myResult {
130 0     0 1 0 my $self = shift;
131 0         0 my $contestant = shift;
132 0         0 $self->canonize;
133 0         0 my $contestants = $self->contestants;
134 0         0 my $result = $self->result;
135 0         0 my %result;
136 0         0 my %roles = map { $contestants->{$_}->id => $_ } keys %$contestants;
  0         0  
137 0         0 my $role = $roles{ $contestant->id };
138 0         0 return $result->{$role};
139             }
140              
141              
142             =head2 myPlayers
143              
144             $game->myPlayers
145              
146             Returns an array of the players from $game, eg ($alekhine, $yourNewNicks) in ROLES order.
147              
148             =cut
149              
150             sub myPlayers {
151 11673     11673 1 14128 my $self = shift;
152 11673         20311 my $contestants = $self->contestants;
153 11673         14374 my @players;
154 11673         23596 for my $role ( ROLES ) {
155 23346 100       73091 push @players, $contestants->{$role} if exists $contestants->{$role};
156             }
157 11673 100       27446 push @players, $contestants->{Bye} if exists $contestants->{Bye};
158 11673         27274 return @players;
159             }
160              
161              
162             =head2 hasPlayer
163              
164             $game->hasPlayer($player)
165              
166             A predicate to perform a test to see if a player is a contestant in $game. Because different objects may refer to the same player when copied by value, use id to decide.
167              
168             =cut
169              
170             sub hasPlayer {
171 5621     5621 1 7067 my $self = shift;
172 5621         7075 my $player = shift;
173 5621         10432 my @contestants = $self->myPlayers;
174 5621     8327   22397 any { $player->id eq $_->id } @contestants;
  8327         21951  
175             }
176              
177              
178             =head2 myOpponent
179              
180             $game->myOpponent($player)
181              
182             Returns the opponent of $player from $game. If $player has a Bye, return a Games::Tournament::Contestant::Swiss object with name 'Bye', and id 'Bye'.
183              
184             =cut
185              
186             sub myOpponent {
187 0     0 1 0 my $self = shift;
188 0         0 my $contestant = shift;
189 0         0 my $id = $contestant->id;
190 0         0 my $contestants = $self->contestants;
191 0         0 my @contestants = values %$contestants;
192 0         0 my %dupes;
193 0         0 for my $contestant ( @contestants )
194             {
195 0 0 0     0 die "Player $contestant isn't a contestant"
196             unless $contestant and
197             $contestant->isa('Games::Tournament::Contestant::Swiss');
198             }
199 0         0 my @dupes = grep { $dupes{$_->id}++ } @contestants;
  0         0  
200 0 0       0 croak "Players @dupes had more than one role" if @dupes;
201 0     0   0 my $opponent = first { $id ne $_->id } @contestants;
  0         0  
202 0 0       0 $opponent = Games::Tournament::Contestant::Swiss->new(
203             name => "Bye", id => "Bye") if $self->isBye;
204 0         0 return $opponent;
205             }
206              
207              
208             =head2 myRole
209              
210             $game->myRole($player)
211              
212             Returns the role for $player from $game, eg 'White', 'Banker' or 'Away'.
213              
214             =cut
215              
216             sub myRole {
217 5621     5621 1 7346 my $self = shift;
218 5621         6491 my $contestant = shift;
219 5621         13252 my $id = $contestant->id;
220 5621         11915 my $round = $self->round;
221 5621         10804 my $contestants = $self->contestants;
222 5621         11619 my @contestants = $self->myPlayers;
223 5621         7095 my $players;
224 5621         24866 $players .= " $_: " . $contestants->{$_}->id for keys %$contestants;
225 5621 50       13322 unless ( $self->hasPlayer($contestant) ) {
226 0         0 carp "Player $id not in Round $round. Contestants are $players.";
227 0         0 return;
228             }
229 5621         16224 my %dupes;
230 5621         9116 for my $contestant ( @contestants )
231             {
232 11038 50 33     61511 die "Player $contestant isn't a contestant"
233             unless $contestant and
234             $contestant->isa('Games::Tournament::Contestant::Swiss');
235             }
236 5621         8852 my @dupes = grep { $dupes{$_->id}++ } @contestants;
  11038         26527  
237 5621 50       12101 croak "Player $id not in Round $round match. Contestants are $players."
238             if @dupes;
239 5621         7065 my %roleReversal;
240 5621         12019 for my $role ( keys %$contestants )
241             {
242 11038         27387 my $id = $contestants->{$role}->id;
243 11038         24845 $roleReversal{$id} = $role;
244             }
245 5621         10413 my $role = $roleReversal{ $id };
246 5621 50       10945 carp "No role for player $id in round " . $self->round unless $role;
247 5621         18916 return $role;
248             }
249              
250              
251             =head2 myFloat
252              
253             $game->myFloat($player)
254              
255             Returns the float for $player in $game, eg 'Up', 'Down' or 'Not'.
256              
257             =cut
258              
259             sub myFloat {
260 648     648 1 865 my $self = shift;
261 648         819 my $contestant = shift;
262             # $self->canonize;
263 648         1298 my $float = $self->float($contestant);
264 648         1586 return $float;
265             }
266              
267              
268             =head2 opponentRole
269              
270             Games::Tournament::Card->opponentRole( $role )
271              
272             Returns the role of the opponent of the player in the given role. Class method.
273              
274             =cut
275              
276             sub opponentRole {
277 5     5 1 36 my $self = shift;
278 5         9 my $role = shift;
279 5         7 my %otherRole;
280 5         27 @otherRole{ (ROLES) } = reverse (ROLES);
281 5         23 return $otherRole{ $role };
282             }
283              
284              
285             =head2 round
286              
287             $game->round
288              
289             Returns the round in which the match is taking place.
290              
291             =cut
292              
293             sub round {
294 5995     5995 1 17041 my $self = shift;
295 5995         11011 return $self->{round};
296             }
297              
298              
299             =head2 contestants
300              
301             $game->contestants
302              
303             Gets/sets the participants as an anonymous array of player objects.
304              
305             =cut
306              
307             sub contestants {
308 19572     19572 1 279189 my $self = shift;
309 19572         22976 my $contestants = shift;
310 19572 50       31661 if ( defined $contestants ) { $self->{contestants} = $contestants; }
  0         0  
311 19572         36668 else { return $self->{contestants}; }
312             }
313              
314              
315             =head2 result
316              
317             $game->result
318              
319             Gets/sets the results of the match.
320              
321             =cut
322              
323             sub result {
324 6363     6363 1 8908 my $self = shift;
325 6363         7334 my $result = shift;
326 6363 100       10467 if ( defined $result ) { $self->{result} = $result; }
  153         487  
327 6210         25482 else { return $self->{result}; }
328             }
329              
330              
331             =head2 equalScores
332              
333             $game->equalScores
334              
335             Tests whether the players have equal scores, returning 1 or ''. If scores were not equal, they are (should be) floating.
336              
337             =cut
338              
339             sub equalScores {
340 3     3 1 12 my $self = shift;
341 3         7 my $contestants = $self->contestants;
342 3         5 my @score = map { $contestants->{$_}->score } ROLES;
  6         25  
343 3 50       17 return unless looks_like_number $score[0];
344 3     6   16 return all { $score[0] == $_ } @score;
  6         24  
345             }
346              
347              
348             =head2 higherScoreRole
349              
350             $game->higherScoreRole
351              
352             Returns the role of the player with the higher score, returning '', if scores are equal.
353              
354             =cut
355              
356             sub higherScoreRole {
357 2     2 1 11 my $self = shift;
358 2         4 my $contestant = $self->contestants;
359 2         5 my @score = map { $contestant->{$_}->score } ROLES;
  4         10  
360 2 100       10 return (ROLES)[0] if $score[0] > $score[1];
361 1 50       6 return (ROLES)[1] if $score[0] < $score[1];
362 0         0 return '';
363             }
364              
365              
366             =head2 floats
367              
368             $game->floats
369              
370             Gets/sets the floats of the match. Probably $game->float($player, 'Up') is used however, instead.
371              
372             =cut
373              
374             sub floats {
375 0     0 1 0 my $self = shift;
376 0         0 my $floats = shift;
377 0 0       0 if ( defined $floats ) { $self->{floats} = $floats; }
  0         0  
378 0         0 else { return $self->{floats}; }
379             }
380              
381              
382             =head2 float
383              
384             $card->float($player[,'Up|Down|Not'])
385              
386             Gets/sets whether the player was floated 'Up', 'Down', or 'Not' floated. $player->floats is not changed. This takes place in $tourney->collectCards. TODO what if $player is 'Bye'?
387              
388             =cut
389              
390             sub float {
391 1422     1422 1 2535 my $self = shift;
392 1422         1734 my $player = shift;
393 1422 50 33     8368 die "Player is $player ref"
394             unless $player and $player->isa('Games::Tournament::Contestant::Swiss');
395 1422         3142 my $role = $self->myRole($player);
396 1422 50 100     8902 croak "Player " . $player->id . " has $role role in round $self->{round}?"
      66        
397             unless $role eq 'Bye'
398             or $role eq (ROLES)[0]
399             or $role eq (ROLES)[1];
400 1422         2057 my $float = shift;
401 1422 100       4443 if ( $role eq 'Bye' ) { return 'Down'; }
  60 100       136  
    100          
402 531         3024 elsif ( defined $float ) { $self->{floats}->{$role} = $float; }
403 153         404 elsif ( $self->{floats}->{$role} ) { return $self->{floats}->{$role}; }
404 678         1503 else { return 'Not'; }
405             }
406              
407             =head2 isBye
408              
409             $card->isBye
410              
411             Returns whether the card is for a bye rather than a game between two oppponents.
412              
413             =cut
414              
415             sub isBye {
416 40     40 1 300 my $self = shift;
417 40         91 my $contestants = $self->contestants;
418 40         113 my @status = keys %$contestants;
419 40 100 66 8   154 return 1 if @status == 1 and any { $_ eq 'Bye' } @status;
  8         48  
420 32 100 33 64   194 return 0 if @status == 2 and all { $_ eq (ROLES)[0] or $_ eq (ROLES)[1] } @status;
  64 50       348  
421 0           return;
422             }
423              
424             =head1 AUTHOR
425              
426             Dr Bean, C<< >>
427              
428             =head1 BUGS
429              
430             Please report any bugs or feature requests to
431             C, or through the web interface at
432             L.
433             I will be notified, and then you'll automatically be notified of progress on
434             your bug as I make changes.
435              
436             =head1 SUPPORT
437              
438             You can find documentation for this module with the perldoc command.
439              
440             perldoc Games::Tournament::Card
441              
442             You can also look for information at:
443              
444             =over 4
445              
446             =item * AnnoCPAN: Annotated CPAN documentation
447              
448             L
449              
450             =item * CPAN Ratings
451              
452             L
453              
454             =item * RT: CPAN's request tracker
455              
456             L
457              
458             =item * Search CPAN
459              
460             L
461              
462             =back
463              
464             =head1 ACKNOWLEDGEMENTS
465              
466             =head1 COPYRIGHT & LICENSE
467              
468             Copyright 2006 Dr Bean, all rights reserved.
469              
470             This program is free software; you can redistribute it and/or modify it
471             under the same terms as Perl itself.
472              
473             =cut
474              
475             1; # End of Games::Tournament::Card
476              
477             # vim: set ts=8 sts=4 sw=4 noet: