File Coverage

blib/lib/Games/Tournament/Swiss.pm
Criterion Covered Total %
statement 262 332 78.9
branch 46 82 56.1
condition 41 87 47.1
subroutine 30 39 76.9
pod 17 17 100.0
total 396 557 71.1


line stmt bran cond sub pod time code
1             package Games::Tournament::Swiss;
2             $Games::Tournament::Swiss::VERSION = '0.21';
3             # Last Edit: 2016 Jan 01, 13:44:35
4             # $Id: Swiss.pm 1360 2016-01-01 05:54:20Z drbean $
5              
6 26     26   12379 use warnings;
  26         46  
  26         767  
7 26     26   121 use strict;
  26         44  
  26         511  
8 26     26   113 use Carp;
  26         39  
  26         1439  
9              
10 26     26   544 use Games::Tournament::Swiss::Config;
  26         45  
  26         1160  
11              
12 26 100       1655 use constant ROLES => @Games::Tournament::Swiss::Config::roles?
13             @Games::Tournament::Swiss::Config::roles:
14 26     26   123 Games::Tournament::Swiss::Config->roles;
  26         43  
15 26     26   137 use constant FIRSTROUND => $Games::Tournament::Swiss::Config::firstround;
  26         43  
  26         1377  
16              
17 26     26   124 use base qw/Games::Tournament/;
  26         37  
  26         1518  
18              
19             # use Games::Tournament::Swiss::Bracket;
20             #use Games::Tournament::Contestant::Swiss -mixin =>
21             # qw/score scores rating title name pairingNumber oldId roles/;
22 26     26   1005 use Games::Tournament::Contestant::Swiss;
  26         47  
  26         634  
23 26     26   11018 use Games::Tournament::Swiss::Procedure;
  26         123  
  26         928  
24 26     26   203 use Games::Tournament::Contestant::Swiss::Preference;
  26         48  
  26         780  
25              
26 26     26   135 use List::Util qw/max min reduce sum first/;
  26         41  
  26         2527  
27 26     26   140 use List::MoreUtils qw/any all/;
  26         43  
  26         174  
28              
29             =head1 NAME
30              
31             Games::Tournament::Swiss - FIDE Swiss Same-Rank Contestant Pairing
32              
33             =cut
34              
35             =head1 SYNOPSIS
36              
37             @Games::Tournament::Swiss::roles = qw/Black White/;
38             $tourney = Games::Tournament::Swiss->new($rounds, \@entrants);
39             @rankedPlayers = $tourney->assignPairingNumbers;
40             $tourney->initializePreferences;
41              
42              
43             ...
44              
45             $tourney->collectCards(@games);
46             @groups = $tourney->formBrackets($round);
47             $round5 = $tourney->pairing( \@groups );
48             $matches = $round5->matchPlayers;
49             $round5->allocateColors;
50              
51             =head1 DESCRIPTION
52              
53             In a Swiss tournament, there is a pre-declared number of rounds, each contestant meets every other contestant zero or one times, and in each round contestants are paired with other players with the same, or similar, scores.
54              
55             =head1 METHODS
56              
57             =head2 assignPairingNumbers
58              
59             @rankings = $tourney->assignPairingNumbers;
60              
61             Sets the participants pairing numbers, sorting on rating, title and name, and substitutes this for the id they had before (The id was, but is no longer, saved as oldId. But don't change id to pairingNumber. It will change with late entries.) This function uses Games::Tournament::rank. Before the first round, all scores are usually 0. A2
62              
63             =cut
64              
65             sub assignPairingNumbers {
66 78     78 1 3744 my $self = shift;
67 78         115 my @players = @{ $self->entrants };
  78         238  
68 78         466 $self->log( 'Pairing numbers' );
69             my $numbers = sub { join ', ',
70 78     78   246 map { $_->id . ": " . $_->pairingNumber } @players;
  1632         3874  
71 78         297 };
72 78 100   97   469 if ( all { $_->pairingNumber } @players ) {
  97         321  
73 1         3 $self->log( &$numbers );
74 1         5 return;
75             }
76 77         426 my @rankings = $self->rank(@players);
77 77         432 foreach my $n ( 0 .. $#rankings ) {
78 1627         4059 my $id = $rankings[$n]->id;
79 1627         4021 my $player = $self->ided($id);
80 1627         6690 $player->pairingNumber( $n+1 );
81             }
82 77         208 $self->log( &$numbers );
83 77         379 $self->entrants( \@players );
84             }
85              
86              
87             =head2 initializePreferences
88              
89             @rankings = $tourney->initializePreferences;
90              
91             Before the first round, the color (role) preference of the highest ranked player and the other odd-numbered players in the top half of the rankings is determined by lot. The preference of the even-numbered players in the top half is given to the other color. If there is only one player in the tournament, the preference is not initialized. The method assumes all entrants have a preference attribute. This accessor is given the player by the Games::Tournament::Contestant::Swiss constructor. We take care to put the players back in the same order that we got them from entrants method. Users may rely on the original order being maintained in web app cookies. E5
92              
93             =cut
94              
95             sub initializePreferences {
96 144     144 1 591 my $self = shift;
97 144         772 my @players = @{ $self->{entrants} };
  144         682  
98 144         454 my @rankings = $self->rank( @players );
99 144         795 my ( $evenRole, $oddRole ) = $self->randomRole;
100 144         373 my $p = int( @rankings / 2 );
101 144 100       377 if ( $p == 0 ) {
102 1         5 $rankings[ 0 ]->preference->sign('');
103 1         4 $rankings[ 0 ]->preference->difference(0);
104 1         4 return $self->entrants( \@rankings );
105             }
106 143         456 for ( my $n=0; $n <= $p-1; $n+=2 ) {
107 756         1970 $rankings[ $n ]->preference->sign($evenRole);
108 756         2010 $rankings[ $n ]->preference->difference(0);
109             }
110 143         407 for ( my $n=1; $n <= $p-1; $n+=2 ) {
111 678         1689 $rankings[ $n ]->preference->sign($oddRole);
112 678         1722 $rankings[ $n ]->preference->difference(0);
113             }
114 143         372 foreach my $n ( 0 .. $#rankings ) {
115 2937         7328 my $id = $rankings[$n]->id;
116 2937         7153 my $player = $self->ided($id);
117 2937         11560 my $preference = $rankings[$n]->preference;
118 2937         6931 $player->preference( $preference );
119             }
120 143         439 $self->entrants( \@players );
121             }
122              
123              
124             =head2 recreateCards
125              
126             $tourney->recreateCards( {
127             round => $round,
128             opponents => { 1 => 2, 2 => 1, 3 => 'Bye', 4 => '-' },
129             roles => { 1 => 'W', 2 => 'B', 3 => 'Bye', 4 => '-' },
130             floats => { 1 => 'U', 2=> 'D', 3 => 'Down', 4 => 'Not' }
131             } )
132              
133             From hashes of the opponents, roles and floats for each player in a round (as provided by a pairing table), draws up the original game cards for each of the matches of the round. Returned is a list of Games::Tournament::Card objects, with undefined result fields. Pairing numbers are not used. Ids are used. Pairing numbers change with late entries.
134              
135             =cut
136              
137             sub recreateCards {
138 6     6 1 366 my $self = shift;
139 6         12 my $args = shift;
140 6         18 my $round = $args->{round};
141 6         16 my $opponents = $args->{opponents};
142 6         12 my $roles = $args->{roles};
143 6         12 my $floats = $args->{floats};
144 6         28 my $players = $self->entrants;
145 6         14 my @ids = map { $_->id } @$players;
  120         260  
146 6         26 my $absentees = $self->absentees;
147 6         17 my @absenteeids = map { $_->id } @$absentees;
  0         0  
148             my $test = sub {
149 6     6   13 my %count = ();
150 6         250 $count{$_}++ for @ids, keys %$opponents, keys %$roles, keys %$floats;
151 6         51 return grep { $count{$_} != 4 } keys %count;
  120         213  
152 6         30 };
153 6         14 carp "Game card not constructable for player $_ in round $round" for &$test;
154 6         16 my (%games, @games);
155 6         14 for my $id ( @ids )
156             {
157 120 100       292 next if $games{$id};
158 60         152 my $player = $self->ided($id);
159 60 50       241 next if $round < $player->firstround;
160 60         106 my $opponentId = $opponents->{$id};
161 60 50       127 croak "Round $round: opponent info for Player $id?" unless $opponentId;
162 60         141 my $opponent = $self->ided($opponentId);
163 60         194 my $opponentsOpponent = $opponents->{$opponentId};
164 60 50 33     392 croak
      33        
165             "Player ${id}'s opponent is $opponentId, but ${opponentId}'s opponent is $opponentsOpponent, not $id in round $round"
166             unless $opponentId eq 'Bye' or $opponentId eq 'Unpaired'
167             or $opponentsOpponent eq $id;
168 60         95 my $role = $roles->{$id};
169 60         85 my $opponentRole = $roles->{$opponentId};
170 60 50       150 if ( $opponentId eq 'Unpaired' ) {
    50          
171 0 0 0     0 croak "Player $id has $role, in round $round?"
172             unless $player and $role eq 'Unpaired';
173 0         0 next;
174 0         0 next;
175             }
176             elsif ( $opponentId eq 'Bye' ) {
177 0 0 0     0 croak "Player $id has $role role, in round $round?"
178             unless $player and $role eq 'Bye';
179             }
180             else {
181 60 50 33     493 croak
      33        
      33        
182             "Player $id is $role, and opponent $opponentId is $opponentRole, in round $round?"
183             unless $player
184             and $opponent
185             and $role
186             and $opponentRole;
187              
188             }
189 60 50 33     371 croak
      33        
190             "Player $id has same $role role as opponent $opponentId in round $round?"
191             if $opponentId and defined $opponentRole and $role eq $opponentRole;
192 60         68 my $contestants;
193 60 50       102 if ( $opponentId eq 'Bye' ) { $contestants = { Bye => $player } }
  0         0  
194 60         151 else { $contestants = { $role => $player, $opponentRole => $opponent } }
195 60         188 my $game = Games::Tournament::Card->new(
196             round => $round,
197             contestants => $contestants,
198             result => undef
199             );
200 60         102 my $float = $floats->{$id};
201 60         141 $game->float( $player, $float );
202              
203 60 50       127 unless ( $opponentId eq 'Bye' ) {
204 60         88 my $opponentFloat = $floats->{$opponentId};
205 60         143 $game->float( $opponent, $opponentFloat );
206             }
207 60         110 $games{$id} = $game;
208 60         93 $games{$opponentId} = $game;
209 60         121 push @games, $game;
210             }
211 6         82 return @games;
212             }
213              
214              
215             =head2 collectCards
216              
217             $play = $tourney->collectCards( @games );
218             next if $htable->{$player1->id}->{$player2->id};
219              
220             Records @games after they have been played. Stored as $tourney's play field, keyed on round and ids of players. Returns the new play field. Updates player scores, preferences, unless the player forfeited the game or had a Bye. TODO Die (or warn) if game has no results TODO This has non-Swiss subclass elements I could factor out into a method in Games::Tournament. TODO What if player is matched more than one time in the round, filling in for someone? XXX It looks like all the games have to be the same round, or you have to collect all cards in one round before collecting cards in following rounds. XXX I'm having problems with recording roles. I want to be lazy about it, and trust the card I get back before the next round. The problem with this is, I may be getting the role from the wrong place. It should come from the card, and is a role which was assigned in the previous round, and is only now being recorded, at this point between the previous round and the next round. Or is the problem copying by value rather than reference of the entrants? Now I also need to record floats. It would be good to do this at the same time as I record roles. The card is the appropriate place to get this info according to A4.
221              
222             =cut
223              
224             sub collectCards {
225 71     71 1 942 my $self = shift;
226 71         162 my @games = @_;
227 71   100     284 my $play = $self->play || {};
228             # my @entrants = @{ $self->entrants };
229 71         108 my %games;
230 71         159 for my $game ( @games )
231             {
232 339         806 my $round = $game->round;
233 339 50       1378 carp "round $round is not a number." unless $round =~ m/^\d+$/;
234 339         385 push @{ $games{$round} }, $game;
  339         946  
235             }
236 71         235 for my $round ( sort { $a <=> $b } keys %games )
  3         7  
237             {
238 72         133 my $games = $games{$round};
239 72         137 for my $game ( @$games ) {
240 339         896 my @players = $game->myPlayers;
241 339         600 for my $player ( @players ) {
242 648         1673 my $id = $player->id;
243 648         1685 my $entrant = $self->ided($id);
244 648         2611 my $oldroles = $player->roles;
245 648         1655 my $scores = $player->scores;
246 648         821 my ( $role, $float, $score );
247 648         1542 $role = $game->myRole($player);
248 648         1669 $float = $game->myFloat($player);
249             $scores->{$round} = ref $game->result eq 'HASH'?
250 648 100       1593 $game->result->{$role}: undef;
251 648         983 $score = $scores->{$round};
252             #carp
253             # "No result on card for player $id as $role in round $round,"
254             # unless $score;
255 648   50     1422 $game ||= "No game";
256 648         1351 $play->{$round}->{$id} = $game;
257 648         1572 $entrant->scores($scores);
258             carp "No record in round $round for player $id $player->{name},"
259 648 50       1527 unless $play->{$round}->{$id};
260 648         1593 $entrant->roles( $round, $role );
261 648         1630 $entrant->floats( $round, $float );
262 648         1541 $entrant->floating('');
263 648 100 66     3482 $entrant->preference->update( $entrant->rolesPlayedList ) unless
      66        
264             $score and ( $score eq 'Bye' or $score eq 'Forfeit' );
265             ;
266             }
267             }
268             }
269 71         244 $self->play($play);
270             }
271              
272              
273             =head2 orderPairings
274              
275             @schedule = $tourney->orderPairings( @games );
276              
277             Tables are ordered by scores of the player with the higher score at the table, then the total scores of the players (in other words, the scores of the other player), then the A2 ranking of the higher-ranked player, in that order. F1
278              
279             =cut
280              
281             sub orderPairings {
282 20     20 1 72 my $self = shift;
283 20         46 my @games = @_;
284 20         58 my $entrants = $self->entrants;
285 20         56 my @rankedentrants = $self->rank(@$entrants);
286 20         84 my %ranking = map { $rankedentrants[$_]->id => $_ } 0 .. $#rankedentrants;
  140         309  
287             my @orderings = map {
288 20         51 my @players = $_->myPlayers;
  70         186  
289 70 100       99 my @scores = map { $_->score || 0 } @players;
  140         297  
290 70         154 my $higherscore = max @scores;
291 70         157 my $totalscore = sum @scores;
292 70         168 my @rankedplayers = $self->rank( @players );
293             { higherscore => $higherscore,
294             totalscore => $totalscore,
295 70         282 higherranking => $ranking{$rankedplayers[0]->id} };
296             } @games;
297 70         118 my @neworder = map { $games[$_] } sort {
298 20         55 $orderings[$b]->{higherscore} <=> $orderings[$a]->{higherscore} ||
299             $orderings[$b]->{totalscore} <=> $orderings[$a]->{totalscore} ||
300             $orderings[$a]->{higherranking} <=> $orderings[$b]->{higherranking}
301 74 50 100     302 } 0 .. $#orderings;
302 20         130 return @neworder;
303             }
304              
305              
306             =head2 publishCards
307              
308             $schedule = $tourney->publishCards( @games );
309              
310             Stores @games, perhaps before they have been played, as $tourney's play field, keyed on round and ids of players. Returns the games in F1 ordering.
311              
312             =cut
313              
314             sub publishCards {
315 0     0 1 0 my $self = shift;
316 0   0     0 my $play = $self->play || {};
317 0         0 my @entrants = @{ $self->entrants };
  0         0  
318 0         0 my @games = @_;
319 0         0 for my $game (@games) {
320 0         0 my $round = $game->round;
321 0         0 my $contestants = $game->contestants;
322 0         0 my @players = map { $contestants->{$_} } keys %$contestants;
  0         0  
323 0         0 for my $player (@players) {
324 0         0 my $id = $player->id;
325 0         0 my $entrant = $self->ided($id);
326 0 0 0     0 die "Player $id $entrant in round $round?"
327             unless $entrant
328             and $entrant->isa("Games::Tournament::Contestant::Swiss");
329 0         0 $play->{$round}->{$id} = $game;
330             }
331             }
332 0         0 $self->orderPairings( @games );
333             }
334              
335              
336             =head2 myCard
337              
338             $game = $tourney->myCard(round => 4, player => 13301616);
339              
340             Finds match from $tourney's play accessor, which is keyed on round and IDS of players. 'player' is id of player.
341              
342             =cut
343              
344             sub myCard {
345 0     0 1 0 my $self = shift;
346 0         0 my %args = @_;
347 0         0 my $round = $args{round};
348 0         0 my $id = $args{player};
349 0         0 my $roundmatches = $self->{play}->{$round};
350 0         0 return $roundmatches->{$id};
351             }
352              
353              
354             =head2 formBrackets
355              
356             @groups = $tourney->formBrackets
357              
358             Returns for the next round a hash of Games::Tournament::Swiss::Bracket objects grouping contestants with the same score, keyed on score. Late entrants without a score cause the program to die. Some groups may have odd numbers of players, etc, and players will have to be floated to other score groups. A number, from 1 to the total number of brackets, reflecting the order of pairing, is given to each bracket.
359              
360             =cut
361              
362             sub formBrackets {
363 60     60 1 521 my $self = shift;
364 60         168 my $players = $self->entrants;
365 60         186 my $absentees = $self->absentees;
366 60         102 my %hashed;
367             my %brackets;
368 60         131 foreach my $player (@$players) {
369 456         1246 my $id = $player->id;
370 456 50   0   2104 next if any { $id eq $_->id } @$absentees;
  0         0  
371 456 100       1660 my $score = defined $player->score ? $player->score : 0;
372             # die "$player has no score. Give them a zero, perhaps?"
373             # if $score eq "None";
374 456         1931 $hashed{$score}{ $player->pairingNumber } = $player;
375             }
376 60         98 my $number = 1;
377 60         233 foreach my $score ( reverse sort keys %hashed ) {
378 166         196 my @members;
379 166         214 foreach
380 487         766 my $pairingNumber ( sort { $a <=> $b } keys %{ $hashed{$score} } )
  166         597  
381             {
382 456         907 push @members, $hashed{$score}{$pairingNumber};
383             }
384 26     26   89317 use Games::Tournament::Swiss::Bracket;
  26         69  
  26         43574  
385 166         724 my $group = Games::Tournament::Swiss::Bracket->new(
386             score => $score,
387             members => \@members,
388             number => $number
389             );
390 166         307 $brackets{$score} = $group;
391 166         373 $number++;
392             }
393 60         462 return %brackets;
394             }
395              
396             =head2 pairing
397              
398             $pairing = $tourney->pairing( \@groups );
399              
400             Returns a Games::Tournament::Swiss::Procedure object. Groups are Games::Tournament::Swiss::Brackets objects of contestants with the same score and they are ordered by score, the group with the highest score first, and the group with the lowest score last. This is the point where round i becomes round i+1. But the program is expected to update the Games::Tournament::Swiss object itself. (Why?)
401              
402             =cut
403              
404             sub pairing {
405 60     60 1 291 my $self = shift;
406 60         180 my $entrants = $self->entrants;
407 60         105 my $brackets = shift;
408 60         173 my $round = $self->round;
409 60         249 return Games::Tournament::Swiss::Procedure->new(
410             round => $round + 1,
411             brackets => $brackets,
412             whoPlayedWho => $self->whoPlayedWho,
413             colorClashes => $self->colorClashes,
414             byes => $self->byesGone,
415             );
416             }
417              
418              
419             =head2 compatible
420              
421             $games = $tourney->compatible
422             next if $games->{$alekhine->pairingNumber}->{$capablanca->pairingNumber}
423              
424             Returns an anonymous hash, keyed on the ids of @grandmasters, indicating whether or not the individual @grandmasters could play each other in the next round. But what is the next round? This method uses the whoPlayedWho and colorClashes methods to remove incompatible players.
425              
426             =cut
427              
428             sub compatible {
429 0     0 1 0 my $self = shift;
430 0         0 my $players = $self->entrants;
431 0         0 my @ids = map { $_->id } @$players;
  0         0  
432 0         0 my $play = $self->play;
433 0         0 my $dupes = $self->whoPlayedWho;
434 0         0 my $colorbar = $self->colorClashes;
435 0         0 my $compat;
436 0         0 for my $id1 (@ids) {
437              
438 0         0 for my $id2 ( grep { $_ != $id1 } @ids ) {
  0         0  
439             $compat->{$id1}->{$id2} = 1
440             unless exists $dupes->{$id1}->{$id2}
441 0 0 0     0 or exists $colorbar->{$id1}->{$id2};
442             }
443             }
444 0         0 return $compat;
445             }
446              
447              
448             =head2 whoPlayedWho
449              
450             $games = $tourney->whoPlayedWho
451             next if $games->{$alekhine->pairingNumber}->
452             {$capablanca->pairingNumber}
453              
454             Returns an anonymous hash, keyed on the ids of the tourney's entrants, of the round in which individual entrants met. Don't forget to collect scorecards in the appropriate games first! (No tracking of how many times players have met if they have met more than once!) Do you know what round it is? B1 XXX Unplayed pairings are not considered illegal in future rounds. F2 See also Games::Tournament::met.
455              
456             =cut
457              
458             sub whoPlayedWho {
459 68     68 1 107 my $self = shift;
460 68         177 my $players = $self->entrants;
461 68         144 my @ids = map { $_->id } @$players;
  520         1227  
462 68         210 my $absentees = $self->absentees;
463 68         166 my @absenteeids = map { $_->id } @$absentees;
  0         0  
464 68         207 my $play = $self->play;
465 68         118 my $dupes;
466 68         181 my $lastround = $self->round;
467 68         182 for my $round ( FIRSTROUND .. $lastround ) {
468 140         237 for my $id (@ids) {
469 1312         3148 my $player = $self->ided($id);
470 1312 50       4825 die "No player with $id id in round $round game of @ids"
471             unless $player;
472 1312         2440 my $game = $play->{$round}->{$id};
473 1312 100 66     6858 if ( $game and $game->can("myRole") ) {
    50 33        
474 1308 50 66     3117 next if $game->result and $game->result eq 'Bye';
475 1308         3186 my $role = $game->myRole($player);
476             die
477             "Player $id, $player->{name}'s role is $role, in round $round?"
478 1308 50   2019   5759 unless any { $_ eq $role } ROLES, 'Bye';
  2019         4170  
479             next if $game->result and exists $game->result->{$role} and
480 1308 50 66     4677 $game->result->{$role} eq 'Forfeit';
      33        
481 1308 100   1981   5481 if ( any { $role eq $_ } ROLES ) {
  1981         4208  
482 1270     1905   4925 my $otherRole = first { $role ne $_ } ROLES;
  1905         2907  
483 1270         4470 my $opponent = $game->contestants->{$otherRole};
484 1270         3950 $dupes->{$id}->{ $opponent->id } = $round;
485             }
486             }
487             elsif ( $player->firstround > $round or
488 0     0   0 any { $id eq $_ } @absenteeids ) { next }
  4         9  
489 0         0 else { warn "Player ${id} game in round $round?"; }
490             }
491             }
492 68         402 return $dupes;
493             }
494              
495              
496             =head2 colorClashes
497              
498             $nomatch = $tourney->colorClashes
499             next if $nomatch->{$alekhine->id}->{$capablanca->id}
500              
501             Returns an anonymous hash, keyed on the ids of the tourney's entrants, of a color (role) if 2 of the individual @grandmasters both have an absolute preference for it in the next round and so can't play each other (themselves). Don't forget to collect scorecards in the appropriate games first! B2
502              
503             =cut
504              
505             sub colorClashes {
506 60     60 1 97 my $self = shift;
507 60         163 my $players = $self->entrants;
508 60         168 my @id = map { $_->id } @$players;
  456         1043  
509 60         117 my $clashes;
510 60         181 for my $player ( 0 .. $#$players ) {
511 456         1086 for ( 0 .. $#$players ) {
512 5158 100 100     13810 $clashes->{ $id[$player] }->{ $id[$_] } =
      100        
      100        
      100        
513             $players->[$player]->preference->role
514             if $players->[$player]->preference->role
515             and $players->[$_]->preference->role
516             and $players->[$player]->preference->role eq
517             $players->[$_]->preference->role
518             and $players->[$player]->preference->strength eq 'Absolute'
519             and $players->[$player]->preference->strength eq
520             $players->[$_]->preference->strength;
521             }
522             }
523 60         303 return $clashes;
524             }
525              
526             =head2 byesGone
527              
528             next if $tourney->byesGone($grandmasters)
529              
530             Returns an anonymous hash of either the round in which the tourney's entrants had a 'Bye' or the empty string, keyed on @$grandmasters' ids. If a grandmaster had more than one bye, the last one is returned. Don't forget to collect scorecards in the appropriate games first! B1
531              
532             =cut
533              
534              
535             sub byesGone {
536 60     60 1 95 my $self = shift;
537 60         197 my $players = $self->entrants;
538 60         169 my @ids = map { $_->id } @$players;
  456         1085  
539 60         193 my $absentees = $self->absentees;
540 60         150 my @absenteeids = map { $_->id } @$absentees;
  0         0  
541 60         187 my $play = $self->play;
542 60         117 my $byes = {};
543 60         217 my $round = $self->round;
544 60         156 for my $round ( FIRSTROUND .. $round ) {
545 116         208 for my $id (@ids) {
546 1120         2625 my $player = $self->ided($id);
547 1120         3825 my $game = $play->{$round}->{$id};
548 1120 100 66     5996 if ( $game and $game->can("myRole") ) {
    50 33        
549 1116         1406 eval { $game->myRole($player) };
  1116         2569  
550 1116 50 33     3770 die "Role of player $id in round $round? $@"
551             if not $player or $@;
552 1116         2521 my $role = $game->myRole($player);
553 1116 100       3477 if ( $role eq 'Bye' ) {
554 38         128 $byes->{$id} = $round;
555             }
556             }
557             elsif ( $player->firstround > $round or
558 0     0   0 any { $id eq $_ } @absenteeids ) { next }
  4         12  
559 0         0 else { warn "Player ${id} had Bye in round $round?"; }
560             }
561             }
562 60         563 return $byes;
563             }
564              
565             =head2 incompatibles
566              
567             $nomatch = $tourney->incompatibles(@grandmasters)
568             next if $nomatch->{$alekhine->id}->{$capablanca->id}
569              
570             Collates information from the whoPlayedWho and colorClashes methods to show who cannot be matched or given a bye in the next round, returning an anonymous hash keyed on the ids of @grandmasters. B1,2 C1,6
571              
572             =cut
573              
574             sub incompatibles {
575 0     0 1 0 my $self = shift;
576 0         0 my $oldOpponents = $self->whoPlayedWho;
577 0         0 my $colorIncompatible = $self->colorClashes;
578 0         0 my $players = $self->entrants;
579 0         0 my @id = map { $_->id } @$players;
  0         0  
580 0         0 my $unavailables;
581 0         0 for my $player ( 0 .. $#$players ) {
582 0         0 for ( 0 .. $#$players ) {
583 0         0 my $color = $colorIncompatible->{ $id[$player] }->{ $id[$_] };
584 0         0 my $round = $oldOpponents->{ $id[$player] }->{ $id[$_] };
585 0 0       0 $unavailables->{ $id[$player] }->{ $id[$_] } = $color if $color;
586 0 0 0     0 $unavailables->{ $id[$player] }->{ $id[$_] } ||= $round if $round;
587             }
588             }
589 0         0 return $unavailables;
590             }
591              
592              
593             =head2 medianScore
594              
595             $group = $tourney->medianScore($round)
596              
597             Returns the score equal to half the number of rounds that have been played. Half the contestants will have scores above or equal to this score and half will have ones equal to or below it, assuming everyone has played every round. What IS the number of rounds played, again?
598              
599             =cut
600              
601             sub medianScore {
602 4     4 1 11 my $self = shift;
603 4         6 my $round = shift;
604 4         20 return $round / 2;
605             }
606              
607             =head2 rounds
608              
609             $tourney->rounds
610              
611             Gets/sets the total number of rounds to be played in the competition
612              
613             =cut
614              
615             sub rounds {
616 0     0 1   my $self = shift;
617 0           my $rounds = shift;
618 0 0         if ( defined $rounds ) { $self->{rounds} = $rounds; }
  0 0          
619 0           elsif ( $self->{rounds} ) { return $self->{rounds}; }
620             }
621              
622              
623             =head2 size
624              
625             $size = 'Maxi' if $tourney->size > 2**$tourney->rounds
626              
627             Gets the number of entrants
628              
629             =cut
630              
631             sub size {
632 0     0 1   my $self = shift;
633 0           return scalar @{ $self->entrants };
  0            
634             }
635              
636             =head1 AUTHOR
637              
638             Dr Bean, C<< >>
639              
640             =head1 BUGS
641              
642             Please report any bugs or feature requests to
643             C, or through the web interface at
644             L.
645             I will be notified, and then you'll automatically be notified of progress on
646             your bug as I make changes.
647              
648             =head1 SUPPORT
649              
650             You can find documentation for this module with the perldoc command.
651              
652             perldoc Games::Tournament::Swiss
653              
654             You can also look for information at:
655              
656             =over 4
657              
658             =item * AnnoCPAN: Annotated CPAN documentation
659              
660             L
661              
662             =item * CPAN Ratings
663              
664             L
665              
666             =item * RT: CPAN's request tracker
667              
668             L
669              
670             =item * Search CPAN
671              
672             L
673              
674             =back
675              
676             =head1 ACKNOWLEDGEMENTS
677              
678             See L for the FIDE's Swiss rules.
679              
680             =head1 COPYRIGHT & LICENSE
681              
682             Copyright 2006 Dr Bean, all rights reserved.
683              
684             This program is free software; you can redistribute it and/or modify it
685             under the same terms as Perl itself.
686              
687             =cut
688              
689             1; # End of Games::Tournament::Swiss
690              
691             # vim: set ts=8 sts=4 sw=4 noet: