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: |