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.20';
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   12252 use warnings;
  26         42  
  26         771  
7 26     26   122 use strict;
  26         41  
  26         513  
8 26     26   118 use Carp;
  26         36  
  26         1407  
9              
10 26     26   522 use Games::Tournament::Swiss::Config;
  26         42  
  26         1159  
11              
12 26 100       1618 use constant ROLES => @Games::Tournament::Swiss::Config::roles?
13             @Games::Tournament::Swiss::Config::roles:
14 26     26   122 Games::Tournament::Swiss::Config->roles;
  26         36  
15 26     26   133 use constant FIRSTROUND => $Games::Tournament::Swiss::Config::firstround;
  26         45  
  26         1422  
16              
17 26     26   124 use base qw/Games::Tournament/;
  26         44  
  26         1461  
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   1081 use Games::Tournament::Contestant::Swiss;
  26         39  
  26         632  
23 26     26   11213 use Games::Tournament::Swiss::Procedure;
  26         78  
  26         953  
24 26     26   222 use Games::Tournament::Contestant::Swiss::Preference;
  26         53  
  26         778  
25              
26 26     26   134 use List::Util qw/max min reduce sum first/;
  26         48  
  26         2584  
27 26     26   191 use List::MoreUtils qw/any all/;
  26         46  
  26         179  
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 4291 my $self = shift;
67 78         110 my @players = @{ $self->entrants };
  78         219  
68 78         501 $self->log( 'Pairing numbers' );
69             my $numbers = sub { join ', ',
70 78     78   241 map { $_->id . ": " . $_->pairingNumber } @players;
  1632         3800  
71 78         282 };
72 78 100   97   477 if ( all { $_->pairingNumber } @players ) {
  97         299  
73 1         3 $self->log( &$numbers );
74 1         5 return;
75             }
76 77         425 my @rankings = $self->rank(@players);
77 77         403 foreach my $n ( 0 .. $#rankings ) {
78 1627         3793 my $id = $rankings[$n]->id;
79 1627         3779 my $player = $self->ided($id);
80 1627         6163 $player->pairingNumber( $n+1 );
81             }
82 77         205 $self->log( &$numbers );
83 77         381 $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 137     137 1 550 my $self = shift;
97 137         750 my @players = @{ $self->{entrants} };
  137         659  
98 137         436 my @rankings = $self->rank( @players );
99 137         729 my ( $evenRole, $oddRole ) = $self->randomRole;
100 137         343 my $p = int( @rankings / 2 );
101 137 100       366 if ( $p == 0 ) {
102 1         5 $rankings[ 0 ]->preference->sign('');
103 1         4 $rankings[ 0 ]->preference->difference(0);
104 1         3 return $self->entrants( \@rankings );
105             }
106 136         395 for ( my $n=0; $n <= $p-1; $n+=2 ) {
107 785         1956 $rankings[ $n ]->preference->sign($evenRole);
108 785         2071 $rankings[ $n ]->preference->difference(0);
109             }
110 136         371 for ( my $n=1; $n <= $p-1; $n+=2 ) {
111 722         1693 $rankings[ $n ]->preference->sign($oddRole);
112 722         1832 $rankings[ $n ]->preference->difference(0);
113             }
114 136         330 foreach my $n ( 0 .. $#rankings ) {
115 3083         7137 my $id = $rankings[$n]->id;
116 3083         7139 my $player = $self->ided($id);
117 3083         11394 my $preference = $rankings[$n]->preference;
118 3083         7148 $player->preference( $preference );
119             }
120 136         409 $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 335 my $self = shift;
139 6         11 my $args = shift;
140 6         20 my $round = $args->{round};
141 6         18 my $opponents = $args->{opponents};
142 6         11 my $roles = $args->{roles};
143 6         11 my $floats = $args->{floats};
144 6         29 my $players = $self->entrants;
145 6         17 my @ids = map { $_->id } @$players;
  120         266  
146 6         32 my $absentees = $self->absentees;
147 6         15 my @absenteeids = map { $_->id } @$absentees;
  0         0  
148             my $test = sub {
149 6     6   12 my %count = ();
150 6         232 $count{$_}++ for @ids, keys %$opponents, keys %$roles, keys %$floats;
151 6         42 return grep { $count{$_} != 4 } keys %count;
  120         225  
152 6         33 };
153 6         18 carp "Game card not constructable for player $_ in round $round" for &$test;
154 6         17 my (%games, @games);
155 6         14 for my $id ( @ids )
156             {
157 120 100       284 next if $games{$id};
158 60         160 my $player = $self->ided($id);
159 60 50       280 next if $round < $player->firstround;
160 60         113 my $opponentId = $opponents->{$id};
161 60 50       127 croak "Round $round: opponent info for Player $id?" unless $opponentId;
162 60         155 my $opponent = $self->ided($opponentId);
163 60         211 my $opponentsOpponent = $opponents->{$opponentId};
164 60 50 33     415 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         92 my $opponentRole = $roles->{$opponentId};
170 60 50       148 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     533 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     378 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         70 my $contestants;
193 60 50       105 if ( $opponentId eq 'Bye' ) { $contestants = { Bye => $player } }
  0         0  
194 60         171 else { $contestants = { $role => $player, $opponentRole => $opponent } }
195 60         203 my $game = Games::Tournament::Card->new(
196             round => $round,
197             contestants => $contestants,
198             result => undef
199             );
200 60         107 my $float = $floats->{$id};
201 60         155 $game->float( $player, $float );
202              
203 60 50       128 unless ( $opponentId eq 'Bye' ) {
204 60         95 my $opponentFloat = $floats->{$opponentId};
205 60         149 $game->float( $opponent, $opponentFloat );
206             }
207 60         103 $games{$id} = $game;
208 60         97 $games{$opponentId} = $game;
209 60         133 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 972 my $self = shift;
226 71         172 my @games = @_;
227 71   100     289 my $play = $self->play || {};
228             # my @entrants = @{ $self->entrants };
229 71         113 my %games;
230 71         146 for my $game ( @games )
231             {
232 339         864 my $round = $game->round;
233 339 50       1474 carp "round $round is not a number." unless $round =~ m/^\d+$/;
234 339         418 push @{ $games{$round} }, $game;
  339         1017  
235             }
236 71         228 for my $round ( sort { $a <=> $b } keys %games )
  2         10  
237             {
238 72         140 my $games = $games{$round};
239 72         130 for my $game ( @$games ) {
240 339         936 my @players = $game->myPlayers;
241 339         596 for my $player ( @players ) {
242 648         1708 my $id = $player->id;
243 648         1812 my $entrant = $self->ided($id);
244 648         2918 my $oldroles = $player->roles;
245 648         1737 my $scores = $player->scores;
246 648         871 my ( $role, $float, $score );
247 648         1722 $role = $game->myRole($player);
248 648         1729 $float = $game->myFloat($player);
249             $scores->{$round} = ref $game->result eq 'HASH'?
250 648 100       1717 $game->result->{$role}: undef;
251 648         1031 $score = $scores->{$round};
252             #carp
253             # "No result on card for player $id as $role in round $round,"
254             # unless $score;
255 648   50     1387 $game ||= "No game";
256 648         1469 $play->{$round}->{$id} = $game;
257 648         1664 $entrant->scores($scores);
258             carp "No record in round $round for player $id $player->{name},"
259 648 50       1650 unless $play->{$round}->{$id};
260 648         1735 $entrant->roles( $round, $role );
261 648         1633 $entrant->floats( $round, $float );
262 648         1609 $entrant->floating('');
263 648 100 66     3453 $entrant->preference->update( $entrant->rolesPlayedList ) unless
      66        
264             $score and ( $score eq 'Bye' or $score eq 'Forfeit' );
265             ;
266             }
267             }
268             }
269 71         258 $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 70 my $self = shift;
283 20         46 my @games = @_;
284 20         51 my $entrants = $self->entrants;
285 20         62 my @rankedentrants = $self->rank(@$entrants);
286 20         86 my %ranking = map { $rankedentrants[$_]->id => $_ } 0 .. $#rankedentrants;
  140         332  
287             my @orderings = map {
288 20         56 my @players = $_->myPlayers;
  70         187  
289 70 100       99 my @scores = map { $_->score || 0 } @players;
  140         331  
290 70         149 my $higherscore = max @scores;
291 70         131 my $totalscore = sum @scores;
292 70         154 my @rankedplayers = $self->rank( @players );
293             { higherscore => $higherscore,
294             totalscore => $totalscore,
295 70         283 higherranking => $ranking{$rankedplayers[0]->id} };
296             } @games;
297 70         114 my @neworder = map { $games[$_] } sort {
298 20         61 $orderings[$b]->{higherscore} <=> $orderings[$a]->{higherscore} ||
299             $orderings[$b]->{totalscore} <=> $orderings[$a]->{totalscore} ||
300             $orderings[$a]->{higherranking} <=> $orderings[$b]->{higherranking}
301 74 50 100     296 } 0 .. $#orderings;
302 20         132 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 509 my $self = shift;
364 60         175 my $players = $self->entrants;
365 60         183 my $absentees = $self->absentees;
366 60         97 my %hashed;
367             my %brackets;
368 60         134 foreach my $player (@$players) {
369 456         1132 my $id = $player->id;
370 456 50   0   2139 next if any { $id eq $_->id } @$absentees;
  0         0  
371 456 100       1726 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         1878 $hashed{$score}{ $player->pairingNumber } = $player;
375             }
376 60         101 my $number = 1;
377 60         243 foreach my $score ( reverse sort keys %hashed ) {
378 166         221 my @members;
379 166         222 foreach
380 508         786 my $pairingNumber ( sort { $a <=> $b } keys %{ $hashed{$score} } )
  166         715  
381             {
382 456         894 push @members, $hashed{$score}{$pairingNumber};
383             }
384 26     26   89352 use Games::Tournament::Swiss::Bracket;
  26         72  
  26         42548  
385 166         750 my $group = Games::Tournament::Swiss::Bracket->new(
386             score => $score,
387             members => \@members,
388             number => $number
389             );
390 166         338 $brackets{$score} = $group;
391 166         387 $number++;
392             }
393 60         577 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 310 my $self = shift;
406 60         168 my $entrants = $self->entrants;
407 60         106 my $brackets = shift;
408 60         164 my $round = $self->round;
409 60         290 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 102 my $self = shift;
460 68         193 my $players = $self->entrants;
461 68         179 my @ids = map { $_->id } @$players;
  520         1227  
462 68         219 my $absentees = $self->absentees;
463 68         168 my @absenteeids = map { $_->id } @$absentees;
  0         0  
464 68         215 my $play = $self->play;
465 68         101 my $dupes;
466 68         183 my $lastround = $self->round;
467 68         190 for my $round ( FIRSTROUND .. $lastround ) {
468 140         245 for my $id (@ids) {
469 1312         3281 my $player = $self->ided($id);
470 1312 50       4802 die "No player with $id id in round $round game of @ids"
471             unless $player;
472 1312         2637 my $game = $play->{$round}->{$id};
473 1312 100 66     7075 if ( $game and $game->can("myRole") ) {
    50 33        
474 1308 50 66     3228 next if $game->result and $game->result eq 'Bye';
475 1308         3358 my $role = $game->myRole($player);
476             die
477             "Player $id, $player->{name}'s role is $role, in round $round?"
478 1308 50   2019   5967 unless any { $_ eq $role } ROLES, 'Bye';
  2019         4156  
479             next if $game->result and exists $game->result->{$role} and
480 1308 50 66     4975 $game->result->{$role} eq 'Forfeit';
      33        
481 1308 100   1981   5477 if ( any { $role eq $_ } ROLES ) {
  1981         4343  
482 1270     1905   4969 my $otherRole = first { $role ne $_ } ROLES;
  1905         3157  
483 1270         4623 my $opponent = $game->contestants->{$otherRole};
484 1270         4103 $dupes->{$id}->{ $opponent->id } = $round;
485             }
486             }
487             elsif ( $player->firstround > $round or
488 0     0   0 any { $id eq $_ } @absenteeids ) { next }
  4         10  
489 0         0 else { warn "Player ${id} game in round $round?"; }
490             }
491             }
492 68         390 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 96 my $self = shift;
507 60         166 my $players = $self->entrants;
508 60         128 my @id = map { $_->id } @$players;
  456         1033  
509 60         116 my $clashes;
510 60         168 for my $player ( 0 .. $#$players ) {
511 456         1103 for ( 0 .. $#$players ) {
512 5158 100 100     14012 $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         318 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 106 my $self = shift;
537 60         219 my $players = $self->entrants;
538 60         150 my @ids = map { $_->id } @$players;
  456         1075  
539 60         213 my $absentees = $self->absentees;
540 60         138 my @absenteeids = map { $_->id } @$absentees;
  0         0  
541 60         176 my $play = $self->play;
542 60         126 my $byes = {};
543 60         194 my $round = $self->round;
544 60         166 for my $round ( FIRSTROUND .. $round ) {
545 116         218 for my $id (@ids) {
546 1120         2893 my $player = $self->ided($id);
547 1120         4083 my $game = $play->{$round}->{$id};
548 1120 100 66     6103 if ( $game and $game->can("myRole") ) {
    50 33        
549 1116         1409 eval { $game->myRole($player) };
  1116         2700  
550 1116 50 33     3643 die "Role of player $id in round $round? $@"
551             if not $player or $@;
552 1116         2728 my $role = $game->myRole($player);
553 1116 100       3644 if ( $role eq 'Bye' ) {
554 38         139 $byes->{$id} = $round;
555             }
556             }
557             elsif ( $player->firstround > $round or
558 0     0   0 any { $id eq $_ } @absenteeids ) { next }
  4         9  
559 0         0 else { warn "Player ${id} had Bye in round $round?"; }
560             }
561             }
562 60         624 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         5 my $round = shift;
604 4         23 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: