File Coverage

blib/lib/Games/Go/AGA/DataObjects/Tournament.pm
Criterion Covered Total %
statement 90 187 48.1
branch 9 62 14.5
condition 4 25 16.0
subroutine 21 32 65.6
pod 4 19 21.0
total 128 325 39.3


line stmt bran cond sub pod time code
1             #===============================================================================
2             #
3             # FILE: Games::Go::AGA::DataObjects::Tournament.pm
4             #
5             # USAGE: use Games::Go::AGA::DataObjects::Tournament;
6             #
7             # PODNAME: Games::Go::AGA::DataObjects::Tournament
8             # ABSTRACT: models AGA register.tde file information
9             #
10             # AUTHOR: Reid Augustin (REID),
11             # CREATED: 11/19/2010 03:13:05 PM PST
12             #===============================================================================
13              
14              
15 1     1   791 use strict;
  1         1  
  1         31  
16 1     1   4 use warnings;
  1         1  
  1         51  
17              
18             package Games::Go::AGA::DataObjects::Tournament;
19 1     1   4 use Moo;
  1         2  
  1         8  
20 1     1   266 use namespace::clean;
  1         1  
  1         9  
21              
22 1     1   186 use parent 'Games::Go::AGA::DataObjects::Register';
  1         1  
  1         7  
23 1     1   53 use Games::Go::AGA::DataObjects::Round;
  1         2  
  1         25  
24 1     1   4 use Games::Go::AGA::DataObjects::Types qw( isa_CodeRef);
  1         1  
  1         45  
25 1     1   4 use Games::Go::AGA::Parse::Util qw( normalize_ID );
  1         2  
  1         34  
26              
27 1     1   4 use Carp;
  1         1  
  1         48  
28 1     1   3 use IO::File;
  1         2  
  1         141  
29 1     1   586 use IO::String;
  1         2324  
  1         49  
30 1     1   8 use Scalar::Util qw( refaddr );
  1         2  
  1         2033  
31              
32             our $VERSION = '0.152'; # VERSION
33              
34             # public attributes
35             # has 'rounds' => (
36             # isa => 'ArrayRef[Games::Go::AGA::DataObjects::Round]',
37             # is => 'ro',
38             # default => sub { [] }
39             # );
40              
41             # Note: the change callback shouldn't really be necessary since
42             # Directives are changed by the Directives object and player data is
43             # changed by the Player object. But it might be convenient...
44             has change_callback => (
45             isa => \&isa_CodeRef,
46             is => 'rw',
47             lazy => 1,
48             default => sub { sub { } }
49             );
50             has suppress_changes => ( # don't call change_callback if set
51             is => 'rw',
52             lazy => 1,
53             default => sub { 0 }
54             );
55             has fprint_pending => ( # set when changed called, cleared after fprint done
56             is => 'rw',
57             lazy => 1,
58             default => sub { 0 }
59             );
60              
61 1     1 0 18 sub clear_rounds { $_[0]->{rounds} = [ undef ]; } # rounds start at 1
62              
63             sub BUILD {
64 1     1 0 5 my ($self) = @_;
65 1         3 $self->clear_rounds;
66             }
67              
68             sub changed {
69 11     11 0 17 my ($self) = @_;
70              
71 11         269 $self->fprint_pending(1);
72 11 50       934 &{$self->change_callback}(@_) if (not $self->suppress_changes);
  11         309  
73             }
74              
75             sub add_round {
76 1     1 1 15 my ($self, $round_num, $round, $replace) = @_;
77              
78 1 50 33     14 if ($round_num <= 0) {
    50          
79 0         0 croak "Round number must be >= 1\n";
80             }
81             elsif ($round_num > 1 and # allow round 1
82             not defined $self->{rounds}[$round_num - 1]) {
83 0         0 croak "Can't add round $round_num, previous round doesn't exist yet\n";
84             }
85 1         5 my $rm_round = $self->{rounds}[$round_num];
86 1 50 33     5 if ( defined $rm_round
87             and not $replace) {
88 0         0 croak "$round_num already exists\n";
89             }
90 1         7 my $prev_callback = $round->change_callback;
91             $round->change_callback(
92             sub {
93 7     7   76 delete $self->{player_stats}; # force re-count
94 7         24 $prev_callback->(@_);
95 7         24 $self->changed;
96             }
97 1         49 );
98 1         15 $self->{rounds}[$round_num] = $round;
99             # $self->changed; # rounds are recorded in N.tde files, not in register.tde
100             }
101              
102             # NOTE: returns games for ALL ROUNDS up to and including $round_num
103             sub games {
104 0     0 0 0 my ($self, $round_num) = @_;
105              
106 0 0       0 $round_num = $#{$self->{rounds}} if (not defined $round_num);
  0         0  
107 0         0 my @games;
108 0         0 foreach my $r_num (1 .. $round_num) {
109 0         0 my $round = $self->{rounds}[$r_num];
110 0 0       0 next if (not $round);
111 0         0 push @games, $round->games;
112             }
113             return wantarray
114             ? @games
115 0 0       0 : \@games;
116             }
117              
118             sub rounds {
119 3     3 1 15 my ($self) = @_;
120              
121 3         5 return $#{$self->{rounds}}; # we don't count 0
  3         19  
122             }
123              
124             sub round {
125 8     8 1 31 my ($self, $round_num) = @_;
126              
127 8 50       33 if (not defined $self->{rounds}[$round_num]) {
128 0         0 croak "Round $round_num doesn't exist\n";
129             }
130 8         91 return $self->{rounds}[$round_num];
131             }
132              
133             #return unpaired players for a round, sorted by rank
134             sub unpaired_in_round_num {
135 0     0 0 0 my ($self, $round_num) = @_;
136              
137 0         0 my %un_paired = map { $_->id => $_ } @{$self->players}; # players by ID
  0         0  
  0         0  
138 0         0 my $round = $self->round($round_num);
139 0         0 for my $game (@{$round->games}) {
  0         0  
140 0         0 delete $un_paired{$game->white->id};
141 0         0 delete $un_paired{$game->black->id};
142             }
143 0         0 my @unpaired = sort { $b->rating <=> $a->rating } values %un_paired;
  0         0  
144             return wantarray
145             ? @unpaired
146 0 0       0 : \@unpaired;
147             }
148              
149             sub swap_players_in_round_num {
150 0     0 0 0 my ($self, $p0, $p1, $round_num) = @_;
151              
152 0         0 $p0 = $self->get_player($p0); # ensure both players are valid
153 0         0 $p1 = $self->get_player($p1);
154 0   0     0 my $round = $self->round($round_num)
155             || die("No round number $round_num in this tournament");
156 0         0 my $id0 = $p0->id;
157 0         0 my $id1 = $p1->id;
158 0         0 my @games;
159 0         0 for my $game (@{$round->games}) {
  0         0  
160 0 0 0     0 push @games, $game if ($game->white->id eq $id0 or $game->black->id eq $id0);
161 0 0 0     0 push @games, $game if ($game->white->id eq $id1 or $game->black->id eq $id1);
162             }
163 0 0       0 return if not @games; # both in unpaired list?
164 0 0       0 if (@games > 2) { # if a player is in more than one game
165 0         0 die "Too many games. Please un-pair and re-pair games instead";
166             }
167             # two games if both are playing, one if there is a bye
168 0 0       0 if ($games[0]->white->id eq $id0) { $games[0]->white($p1) }
  0 0       0  
169 0         0 elsif ($games[0]->white->id eq $id1) { $games[0]->white($p0) }
170 0 0       0 if ($games[0]->black->id eq $id0) { $games[0]->black($p1) }
  0 0       0  
171 0         0 elsif ($games[0]->black->id eq $id1) { $games[0]->black($p0) }
172 0         0 $games[0]->handicap;
173 0 0 0     0 if (@games == 2 and refaddr($games[0]) != refaddr($games[1])) {
174 0 0       0 if ($games[1]->white->id eq $id0) { $games[1]->white($p1) }
  0 0       0  
175 0         0 elsif ($games[1]->white->id eq $id1) { $games[1]->white($p0) }
176 0 0       0 if ($games[1]->black->id eq $id0) { $games[1]->black($p1) }
  0 0       0  
177 0         0 elsif ($games[1]->black->id eq $id1) { $games[1]->black($p0) }
178 0         0 $games[1]->handicap;
179             }
180             }
181              
182             sub clear_stats {
183 0     0 0 0 my ($self) = @_;
184              
185 0         0 delete $self->{player_stats};
186             }
187              
188             sub player_stats {
189 8     8 0 20 my ($self, $id, $stat) = @_;
190              
191 8 100       37 if (not defined $self->{player_stats}) {
192 2         4 my %players;
193 2         9 for my $round_num (1 .. $self->rounds) {
194 2         7 my $round = $self->round($round_num);
195 2         4 for my $game (@{$round->games}) {
  2         12  
196 4         209 my $white = $game->white;
197 4         141 my $black = $game->black;
198 4         135 my $wid = $white->id;
199 4         206 my $bid = $black->id;
200 4         33 push @{$players{$wid}{games}}, $game;
  4         23  
201 4         5 push @{$players{$bid}{games}}, $game;
  4         15  
202 4 50       18 if (not defined $game->winner) {
203 0         0 push @{$players{$wid}{no_result}}, $black;
  0         0  
204 0         0 push @{$players{$bid}{no_result}}, $white;
  0         0  
205 0         0 next;
206             }
207 4         187 my $win_id = $game->winner->id;
208 4         295 my $los_id = $game->loser->id;
209 4         293 push @{$players{$win_id}{wins}}, $game;
  4         21  
210 4         7 push @{$players{$win_id}{defeated}}, $game->loser;
  4         21  
211 4         164 push @{$players{$los_id}{losses}}, $game;
  4         14  
212 4         6 push @{$players{$los_id}{defeated_by}}, $game->winner;
  4         22  
213             }
214             }
215 2         94 $self->{player_stats} = \%players;
216             }
217 8         32 $id = normalize_ID($id);
218 8   100     136 $self->{player_stats}{$id}{$stat} ||= [];
219             return wantarray
220 0         0 ? @{$self->{player_stats}{$id}{$stat}}
221 8 50       70 : $self->{player_stats}{$id}{$stat};
222             }
223              
224 0     0 0 0 sub player_games { shift->player_stats(@_, 'games'); }
225 8     8 0 31 sub player_wins { shift->player_stats(@_, 'wins'); }
226 0     0 0   sub player_losses { shift->player_stats(@_, 'losses'); }
227 0     0 0   sub player_no_result { shift->player_stats(@_, 'no_result'); }
228 0     0 0   sub player_defeated { shift->player_stats(@_, 'defeated'); }
229 0     0 0   sub player_defeated_by { shift->player_stats(@_, 'defeated_by'); }
230              
231             sub send_to_AGA {
232 0     0 1   my ($self, $fd) = @_;
233              
234 0 0         if (not $fd) {
235 0 0         $fd = IO::String->new() or die "Failed to create IO::String\n";
236             }
237              
238 0           $fd->printf("TOURNEY %s\n",
239             $self->get_directive_value('TOURNEY'));
240              
241 0           my $date = $self->get_directive_value('DATE');
242 0           my ($start, $finish) = $date =~ m/^(\S+)[\-\s]+(\S+)$/;
243 0   0       $start ||= $date;
244 0   0       $finish ||= $start;
245 0           $start =~ s/\D/\//g; # use slash date notation
246 0           $finish =~ s/\D/\//g;
247 0           $fd->print(" start=$start\n"),
248             $fd->print(" finish=$finish\n"),
249              
250             $fd->printf(" rules=%s\n",
251             $self->get_directive_value('RULES'));
252 0           $fd->print("\nPLAYERS\n");
253              
254             # print player info
255 0           my $name_width = 5;
256 0           for my $player ($self->players) {
257 0 0         $name_width = length($player->full_name)
258             if (length($player->full_name) > $name_width);
259             }
260              
261 0           for my $player ($self->players) {
262 0           $fd->printf("%9.9s %*.*s %s\n",
263             $player->id,
264             $name_width,
265             $name_width,
266             $player->full_name,
267             $player->rating,
268             );
269             }
270              
271             # print games with results
272 0           $fd->print("\nGAMES\n");
273 0           for my $round (@{$self->{rounds}}) {
  0            
274 0 0         next if (not defined $round);
275 0           for my $game ($round->games) {
276 0 0         if ($game->winner) {
277 0 0         my $result = ($game->winner->id eq $game->white->id) ? 'W' : 'B';
278 0           $fd->printf("%9.9s %9.9s $result %s %s\n",
279             $game->white->id,
280             $game->black->id,
281             $game->handi,
282             $game->komi,
283             );
284             }
285             }
286             }
287 0           $fd->print("\n");
288              
289 0           return $fd
290             }
291              
292             # this really shouldn't be necessary. Register and Directives will
293             # fprint the register.tde files, and Round will fprint the N.tde files.
294             sub fprint {
295 0     0 0   my ($self, $fh) = @_;
296              
297 0           $self->SUPER::fprint($fh); # print the register.tde file
298 0           $self->fprint_pending(0);
299             }
300              
301             1;
302              
303             __END__