File Coverage

blib/lib/Games/Tournament/Contestant.pm
Criterion Covered Total %
statement 81 167 48.5
branch 28 58 48.2
condition 9 25 36.0
subroutine 17 25 68.0
pod 16 16 100.0
total 151 291 51.8


line stmt bran cond sub pod time code
1             package Games::Tournament::Contestant;
2             $Games::Tournament::Contestant::VERSION = '0.21';
3             # Last Edit: 2016 Jan 01, 13:44:39
4             # $Id: $
5              
6 28     28   24262 use warnings;
  28         227  
  28         841  
7 28     28   134 use strict;
  28         43  
  28         553  
8 28     28   128 use Carp;
  28         43  
  28         2093  
9              
10 28     28   142 use base qw/Games::Tournament/;
  28         38  
  28         10017  
11 28     28   273 use List::Util qw/sum/;
  28         50  
  28         2148  
12 28     28   1205 use List::MoreUtils qw/all/;
  28         48  
  28         181  
13 28 100       2819 use constant ROLES => @Games::Tournament::Swiss::Config::roles?
14             @Games::Tournament::Swiss::Config::roles:
15 28     28   11445 Games::Tournament::Swiss::Config->roles;
  28         51  
16 28 100       36821 use constant SCORES => %Games::Tournament::Swiss::Config::scores?
17             %Games::Tournament::Swiss::Config::scores:
18 28     28   131 Games::Tournament::Swiss::Config->scores;
  28         48  
19              
20             # use overload qw/0+/ => 'id', qw/""/ => 'name', fallback => 1;
21              
22             =head1 NAME
23              
24             Games::Tournament::Contestant A competitor matched with others over a series of rounds
25              
26             =cut
27              
28             =head1 SYNOPSIS
29              
30             my $foo = Games::Tournament::Contestant->new( rating => '15', name => 'Your New Knicks' );
31             ...
32              
33             =head1 DESCRIPTION
34              
35             A generic tournament/series player/team contestant object.
36              
37             =head1 METHODS
38              
39             =head2 new
40              
41             $team = Games::Tournament::Contestant->new( id => '15', name => 'Lala Lakers', rating => 0, score => 1000, )
42             $grandmaster = Games::Tournament::Contestant->new( name => 'Jose Raul Capablanca', rating => 1000 )
43              
44             Make sure the ids of all your contestants are unique.
45              
46             =cut
47              
48             sub new {
49 0     0 1 0 my $self = shift;
50 0         0 my %args = @_;
51 0         0 return bless \%args, $self;
52             }
53              
54              
55             =head2 clone
56              
57             $monster = $alekhine->clone( score => 1000, reputation => 'bad' )
58              
59             Creates a similar object to $alekhine, with the same id, name, score, title, and rating fields but with any other changes or additions you want to make.
60              
61             =cut
62              
63             sub clone {
64 0     0 1 0 my $self = shift;
65 0         0 my %args = @_;
66 0   0     0 my $clone = Games::Tournament::Contestant->new(
      0        
      0        
      0        
      0        
67             id => $self->id || undef,
68             name => $self->name || undef,
69             score => $self->score || undef,
70             title => $self->title || undef,
71             rating => $self->rating || undef,
72             );
73 0         0 foreach my $key ( keys %args ) {
74 0         0 $clone->{$key} = $args{$key};
75             }
76 0         0 return $clone;
77             }
78              
79              
80             =head2 findCard
81              
82             @venues = $player->findCard(@games);
83              
84             Returns a/the first game in @games in which $player is a contestant. 'findCard' expects the game objects to have 'contestants' accessors and be 'canonize'able. The players are grepped for stringwise id equality.
85              
86             =cut
87              
88             sub findCard {
89 0     0 1 0 my $self = shift;
90 0         0 my $id = $self->id;
91 0         0 my @games = @_;
92 0         0 my @cards;
93 0         0 foreach my $game (@games) {
94 0         0 $game->canonize;
95 0         0 my $players = $game->contestants;
96             push @cards, $game
97 0 0       0 if grep { $players->{$_}->id eq $id } keys %$players;
  0         0  
98             }
99 0         0 return $cards[0];
100             }
101              
102              
103             =head2 myOpponent
104              
105             $opponent = $player->myOpponent($game);
106              
107             Returns a/the opponent in $game of $player. 'myOpponent' expects the game object to have 'contestants' accessors. The players are grepped for stringwise id equality.
108              
109             =cut
110              
111             sub myOpponent {
112 576     576 1 686 my $self = shift;
113 576         1010 my $id = $self->id;
114 576         754 my $game = shift;
115 576 50 33     2994 croak "Looking for opponent, but no contestants in $game game" unless
116             $game and $game->can('contestants');
117 576         1366 my $contestants = $game->contestants;
118 576         1235 my @contestants = values %$contestants;
119 576         792 my @ids = map { $_->id } @contestants;
  1152         2258  
120 576 50       8179 die "Player $id not in match of @ids" unless grep m/$_/, @ids;
121 576         686 my @opponents;
122              
123 576         875 for my $contestant (@contestants) {
124 1152 100       2146 push @opponents, $contestant if $contestant->id ne $id;
125             }
126 576         2665 return $opponents[0];
127             }
128              
129              
130             =head2 copyCard
131              
132             @result = $player->copyCard(@games);
133              
134             Stores a ref to the @games in which $player has participated and copied the cards for. @games may or may not be a complete list of result for all rounds, and may include games in which $player wasn't a participant. Pushed to an anonymous array stored as the 'play' field. 'copyCard' expects the game objects to have 'round' and 'contestants' accessors and be 'canonize'able.
135              
136             =cut
137              
138             sub copyCard {
139 0     0 1 0 my $self = shift;
140 0         0 my $id = $self->id;
141 0         0 my $play = $self->play;
142 0         0 my @games = @_;
143 0         0 my %result;
144 0         0 foreach my $game (@games) {
145 0         0 $game->canonize;
146 0         0 my $round = $game->round;
147 0         0 my $players = $game->contestants;
148 0         0 my %roles = map { $players->{$_}->id => $_ } keys %$players;
  0         0  
149 0 0       0 next unless exists $roles{$id};
150 0         0 push @$play, $game;
151             }
152 0         0 $self->play($play);
153             }
154              
155             =head2 writeCard (deprecated)
156              
157             @result = $player->writeCard(@games);
158              
159             Updates the contestant's result in the matches played, using no intelligence if records only have only opponents' scores. @games may or may not be a complete list of result for all rounds, and may include games in which $player wasn't a participant. Stored as a 'play' field and keyed on the round, the resultant records have 'opponent' and 'result' subfields. 'writeCard' expects the game objects to have 'round', 'contestants' and 'result' accessors. Returns the new play field.
160             TODO The 'opponent' subfield will be an anonymous array of player objects if it is a multi-player game.
161              
162             =cut
163              
164             sub writeCard {
165 0     0 1 0 my $self = shift;
166 0         0 my $id = $self->id;
167 0         0 my @games = @_;
168 0         0 my %result;
169 0         0 foreach my $game (@games) {
170 0         0 $game->canonize;
171 0         0 my $round = $game->round;
172 0         0 my $players = $game->contestants;
173 0         0 my %roles = map { $players->{$_}->id => $_ } keys %$players;
  0         0  
174 0 0       0 next unless exists $roles{$id};
175 0         0 my $role = $roles{$id};
176 0         0 my $opponent;
177 0         0 foreach my $player ( values %$players ) {
178 0 0       0 $opponent = $player unless $player->id == $self->id;
179             }
180 0         0 $result{$round} = { opponent => $opponent };
181 0         0 $result{$round}{result} = $game->{result}->{$role};
182             }
183 0         0 $self->play( \%result );
184             }
185              
186              
187             =head2 score
188              
189             $rounds = $deepblue->score
190             next if $deepblue->score
191              
192             Gets/sets the total score over the rounds in which $deepblue has a score. Don't forget to tally $deepblue's scorecard with the appropriate games first! We don't check any cards. Internally, this method accumulates the results of all the rounds into a total score, unless no results exist. If they don't exist, a hash key $self->{score} is consulted. You can set the score this way too, but don't do that. It bypasses the elegant code to do it from individual game results stored by the Games::Tournament::Contestant object. It's a hack to allow importing a pairing table. Finally, if none of the above apply, undef is returned, despite FIDE Rule A2. This means that Bracket and FIDE methods using the score method need to handle undef scores.
193              
194             =cut
195              
196             sub score {
197 127852     127852 1 163194 my $self = shift;
198 127852         304160 my %converter = SCORES;
199 127852         153736 my $score = shift;
200 127852 100       240885 if ( defined $score ) { $self->{score} = $score; }
  160         374  
201 127852         238923 my $scores = $self->scores;
202             return $self->{score} unless defined $scores and
203 127852 100 100 146002   632115 all { defined $_ } values %$scores;
  146002         720422  
204 23462         72885 my %lcconverter = map { lc($_) => $converter{$_} } keys %converter;
  126580         298482  
205 23462         68526 my %scores = map { $_ => lc $scores->{$_} } keys %$scores;
  58623         141281  
206 23462         53444 for my $round ( keys %scores ) {
207             die
208             "Round $round $scores->{$round}, $scores{$round} score unconvertible to $lcconverter{$scores{$round}} for player $self->{id}"
209 58623 50 66     262178 unless defined( $scores{$round} and $lcconverter{ $scores{$round} } );
210             }
211 23462         41546 my @values = map { $lcconverter{$_} } values %scores;
  58623         108515  
212 23462         49946 my $sum = sum(@values);
213 23462 50       154336 return $sum if defined $sum;
214 0         0 return undef;
215             }
216              
217              
218             =head2 met
219              
220             $rounds = $deepblue->met(@grandmasters)
221             next if $deepblue->met($capablanca)
222              
223             Returns an anonymous hash, keyed on @grandmasters' ids, either of the gamecards in which $deepblue remembers meeting the members of @grandmasters or of the empty string '' if there is no record of such a meeting. Don't forget to tally $deepblue's scorecard with the appropriate games first (using $deepblue->play?)! We don't check $deepblue's partners' cards. (Assumes players do not meet more than once!) Don't confuse this with Games::Tournament::met!
224              
225             =cut
226              
227             sub met {
228 0     0 1 0 my $self = shift;
229 0         0 my @opponents = @_;
230 0         0 my $games = $self->play;
231 0         0 my @rounds = keys %$games;
232 0         0 my @ids = map { $_->id } @opponents;
  0         0  
233 0         0 my %gameAgainst;
234 0         0 @gameAgainst{@ids} = ('') x @ids;
235 0         0 for my $round ( @rounds )
236             {
237 0         0 my $gameInRound = $games->{$round};
238 0 0       0 next unless UNIVERSAL::isa $gameInRound, 'Games::Tournament::Card';
239 0         0 my $opponent = $self->myOpponent($gameInRound);
240 0         0 my $opponentId = $opponent->id;
241 0         0 $gameAgainst{$opponentId} = $gameInRound;
242             }
243 0 0       0 carp $self->id . " played @ids? Where are the cards?" unless %gameAgainst;
244 0         0 return \%gameAgainst;
245             }
246              
247              
248             =head2 name
249              
250             $member->name('Alexander Alekhine');
251             $member->name
252              
253             Sets or gets the name of the contesting individual or team, a string that may or may not be unique to the tournament member.
254              
255             =cut
256              
257             sub name {
258 2848     2848 1 3717 my $self = shift;
259 2848         3291 my $name = shift;
260 2848 50       8587 if ( defined $name ) { $self->{name} = $name; }
  0 50       0  
261 2848         10033 elsif ( exists $self->{name} ) { return $self->{name}; }
262             }
263              
264              
265             =head2 title
266              
267             $member->title('Grandmaster')
268              
269             Sets/gets the title of the contestant, a courtesy given to the contestant.
270              
271             =cut
272              
273             sub title {
274 1014     1014 1 1295 my $self = shift;
275 1014         1163 my $title = shift;
276 1014 50       2939 if ( defined $title ) { $self->{title} = $title; }
  0 100       0  
277 966         3825 elsif ( exists $self->{title} ) { return $self->{title}; }
278             }
279              
280              
281             =head2 scores
282              
283             $member->scores
284              
285             Sets/gets the scores (actually results, eg 'Draw', 'Win') of the contestant in the different matches of the tournament, an ongoing record of their standing in the competition. These scores may or may not include the current score. To calculate the total score, use 'score', because internally the scores are not stored as number scores.
286              
287             =cut
288              
289             sub scores {
290 129148     129148 1 150383 my $self = shift;
291 129148   66     305588 my $scores = shift() || $self->{scores};
292 129148         176529 $self->{scores} = $scores;
293 129148         199807 return $scores;
294             }
295              
296              
297             =head2 rating
298              
299             $member->rating
300              
301             Sets/gets the rating of the contestant, an estimate of their strength. The constructor assumes if no rating or a non-numeric rating is given, that they don't have a rating, and it is set to 0.
302              
303             =cut
304              
305             sub rating {
306 54710     54710 1 66567 my $self = shift;
307 54710         63277 my $rating = shift;
308 54710 50 33     168691 if ( defined $rating and $rating =~ m/^\d$/ ) { $self->{rating} = $rating; }
  0 100       0  
309 52829         190840 elsif ( exists $self->{rating} ) { return $self->{rating}; }
310 1881         6528 else { return 0; }
311             }
312              
313              
314             =head2 play
315              
316             $games = $member->play;
317             $games = $member->play( { $lastround => $game } );
318              
319             Sets/gets a hash reference to the result of the pairings in each of the rounds played so far. Don't use this to record a player's match result. Use $tourney->collectCards. Implementation: The keys of the hash are the round numbers and the values are the gamecard of the player in that round. Very similar to the play accessor for tournaments, which is what collectCards uses.
320              
321             =cut
322              
323             sub play {
324 0     0 1 0 my $self = shift;
325 0         0 my $play = shift;
326 0 0       0 if ( defined $play ) {
    0          
327 0         0 my @rounds = keys %$play;
328 0         0 for my $round ( @rounds ) {
329 0         0 $self->{play}->{$round} = $play->{$round};
330             }
331             }
332 0         0 elsif ( $self->{play} ) { return $self->{play}; }
333             }
334              
335              
336             =head2 id
337              
338             $member->id
339              
340             Returns/sets the id of the contestant, a number unique to the member. Users must make sure no two players have the same id. Pairing numbers may change with late entries, so the id is important.
341              
342             =cut
343              
344             sub id {
345 190994     190994 1 573168 my $self = shift;
346 190994         225701 my $id = shift;
347 190994 50       544827 if ( defined $id ) { $self->{id} = $id; }
  0 50       0  
348 190994         549212 elsif ( exists $self->{id} ) { return $self->{id}; }
349             }
350              
351             =head2 firstround
352              
353             $member->firstround
354              
355             Returns/sets the firstround of the contestant, the round in which they first played or will play. Necessary for handling late entrants.
356              
357             =cut
358              
359             sub firstround {
360 4210     4210 1 5441 my $self = shift;
361 4210         5063 my $firstround = shift;
362 4210 100       16358 if ( defined $firstround ) { $self->{firstround} = $firstround; }
  1744 100       4321  
363 722         2466 elsif ( exists $self->{firstround} ) { return $self->{firstround}; }
364             }
365              
366              
367             =head2 absent
368              
369             $member->absent(1)
370             puah @absent if $member->absent
371              
372             A flag of convenience telling you whether this player is absent and not to be paired in the tournament. This is not the same as a forfeit. The Games::Tournament::Swiss constructor uses this.
373              
374             =cut
375              
376             sub absent {
377 0     0 1   my $self = shift;
378 0           my $absent = shift;
379 0 0         if ( $absent ) { $self->{absent} = 1; return }
  0 0          
  0            
380 0           elsif ( defined $self->{absent} ) { return $self->{absent}; }
381 0           else { return; }
382             }
383              
384              
385             =head1 AUTHOR
386              
387             Dr Bean, C<< >>
388              
389             =head1 BUGS
390              
391             Please report any bugs or feature requests to
392             C, or through the web interface at
393             L.
394             I will be notified, and then you'll automatically be notified of progress on
395             your bug as I make changes.
396              
397             =head1 SUPPORT
398              
399             You can find documentation for this module with the perldoc command.
400              
401             perldoc Games::Tournament::Contestant
402              
403             You can also look for information at:
404              
405             =over 4
406              
407             =item * AnnoCPAN: Annotated CPAN documentation
408              
409             L
410              
411             =item * CPAN Ratings
412              
413             L
414              
415             =item * RT: CPAN's request tracker
416              
417             L
418              
419             =item * Search CPAN
420              
421             L
422              
423             =back
424              
425             =head1 ACKNOWLEDGEMENTS
426              
427             =head1 COPYRIGHT & LICENSE
428              
429             Copyright 2006 Dr Bean, all rights reserved.
430              
431             This program is free software; you can redistribute it and/or modify it
432             under the same terms as Perl itself.
433              
434             =cut
435              
436             1; # End of Games::Tournament::Contestant
437              
438             # vim: set ts=8 sts=4 sw=4 noet: