File Coverage

blib/lib/Games/Go/AGA/DataObjects/Round.pm
Criterion Covered Total %
statement 63 231 27.2
branch 4 100 4.0
condition 11 36 30.5
subroutine 16 28 57.1
pod 9 16 56.2
total 103 411 25.0


line stmt bran cond sub pod time code
1             #===============================================================================
2             #
3             # FILE: Games::Go::AGA::DataObjects::Round.pm
4             #
5             # USAGE: use Games::Go::AGA::DataObjects::Round;
6             #
7             # PODNAME: Games::Go::AGA::DataObjects::Round
8             # ABSTRACT: model a round of an AGA tournament
9             #
10             # AUTHOR: Reid Augustin (REID),
11             # CREATED: 11/19/2010 03:13:05 PM PST
12             #===============================================================================
13              
14 2     2   1905 use strict;
  2         4  
  2         59  
15 2     2   8 use warnings;
  2         3  
  2         68  
16              
17             package Games::Go::AGA::DataObjects::Round;
18 2     2   9 use Moo;
  2         2  
  2         10  
19 2     2   485 use namespace::clean;
  2         4  
  2         12  
20              
21 2     2   317 use Carp;
  2         4  
  2         135  
22 2     2   10 use Scalar::Util qw( refaddr looks_like_number );
  2         3  
  2         100  
23 2     2   9 use Games::Go::AGA::DataObjects::Game;
  2         7  
  2         40  
24 2     2   6 use Games::Go::AGA::DataObjects::Player;
  2         2  
  2         48  
25 2     2   8 use Games::Go::AGA::DataObjects::Types qw( is_Int isa_Int isa_CodeRef);
  2         2  
  2         100  
26 2     2   8 use Games::Go::AGA::Parse::Util qw( normalize_ID Rank_to_Rating );
  2         2  
  2         4610  
27              
28             our $VERSION = '0.152'; # VERSION
29              
30             our $deprecate = 0;
31             # has 'games' => (
32             # isa => 'ArrayRef[Games::Go::AGA::DataObjects::Game]',
33             # is => 'rw',
34             # default => sub { [] },
35             # );
36             # has 'byes' => (
37             # isa => 'ArrayRef[Games::Go::AGA::DataObjects::Player]',
38             # is => 'rw',
39             # default => sub { [] },
40             # );
41             has filename => ( # store a filename here
42             is => 'rw',
43             );
44             has table_number => ( # assign table numbers from how many tables used so far
45             isa => \&isa_Int,
46             is => 'rw',
47             lazy => 1,
48             default => sub { 1 },
49             );
50             has change_callback => (
51             isa => \&isa_CodeRef,
52             is => 'rw',
53             lazy => 1,
54             default => sub { sub {} },
55             );
56             has suppress_changes => ( # don't call change_callback if set
57             is => 'rw',
58             lazy => 1,
59             default => sub { 0 }
60             );
61             has fprint_pending => ( # set when changed called, cleared after fprint done
62             is => 'rw',
63             lazy => 1,
64             default => sub { 0 }
65             );
66             has round_num => ( # which round is this?
67             isa => sub {
68             die defined $_[0] ? "Invalid round index $_[0]" : "round index not defined" if (not is_Int($_[0]) or $_[0] < 1);
69             },
70             is => 'rw',
71             lazy => 1,
72             default => sub { 0 },
73             );
74             has adj_ratings_change => ( # set when player->adj_ratings need recalculation
75             is => 'rw',
76             lazy => 1,
77             default => sub { 1 },
78             );
79             has id => (
80             is => 'rw',
81             );
82             has id_len => (
83             is => 'rw',
84             );
85             has handi_len => (
86             is => 'rw',
87             );
88             has komi_len => (
89             is => 'rw',
90             );
91             has name_len => (
92             is => 'rw',
93             );
94              
95             our $idxx = 0;
96              
97             sub BUILD {
98 2     2 0 55 my ($self) = @_;
99 2         10 $self->{games} = [];
100 2         8 $self->{byes} = [];
101 2         129 $self->id($idxx++);
102             }
103              
104             sub changed {
105 16     16 0 36 my ($self, @args) = @_;
106              
107 16         362 $self->fprint_pending(1);
108 16         1585 $self->adj_ratings_change(1);
109 16 50       2164 &{$self->change_callback}(@_) if (not $self->suppress_changes);
  16         481  
110             }
111              
112             sub games {
113 21     21 1 16906 my ($self) = @_;
114              
115             return wantarray
116 0         0 ? @{$self->{games}}
117 21 50       503 : $self->{games};
118             }
119              
120             # find game by index or by 1 or two IDs
121             sub game {
122 3     3 0 9 my ($self, $which, $which2) = @_;
123              
124 3 50 66     48 if (@_ <= 2 and
      33        
125             looks_like_number($which) and
126 0         0 $which < @{$self->{games}} ) {
127 0         0 return $self->{games}[$which]; # as index
128             }
129 3         14 $which = normalize_ID($which);
130 3   66     48 $which2 ||= $which;
131 3         9 $which2 = normalize_ID($which2);
132 3         39 for my $game (@{$self->{games}}) {
  3         15  
133 4         101 my $wid = $game->white->id;
134 4         1097 my $bid = $game->black->id;
135 4 50 66     1077 if (($wid eq $which or $bid eq $which) and
      66        
      66        
136             ($wid eq $which2 or $bid eq $which2)) {
137 3         26 return $game;
138             }
139             }
140 0         0 return; # not found
141             }
142              
143             sub byes {
144 0 0   0 1 0 croak("Rounds->byes is deprecated") if ($deprecate > 0);
145 0         0 my ($self) = @_;
146              
147             return wantarray
148 0         0 ? @{$self->{byes}}
149 0 0       0 : $self->{byes};
150             }
151              
152             sub add_game {
153 5     5 1 57 my ($self, $game) = @_;
154              
155 5         135 my $prev_callback = $game->change_callback;
156             $game->change_callback( # add to game callback
157             sub {
158 11     11   131 $prev_callback->(@_); # whatever happened before, and
159 11         40 $self->changed; # our status changes
160             }
161 5         186 );
162 5         60 push (@{$self->{games}}, $game);
  5         24  
163 5         99 $game->table_number($self->table_number);
164 5         139 $self->table_number($self->table_number + 1);
165 5         64 $self->changed;
166 5         16 return $self;
167             }
168              
169             sub clear_table_number {
170 0     0 1   my ($self) = @_;
171              
172 0           $self->table_number(1);
173             }
174              
175             sub remove_game {
176 0     0 1   my ($self, $game) = @_;
177              
178 0           my $games = $self->{games};
179 0 0         if (ref $game) { # if game is a ref, find index of that ref
180 0           my $raddr = refaddr($game);
181 0           for (my $idx = 0; $idx < @{$games}; $idx++) {
  0            
182 0 0         if (refaddr($games->[$idx]) == $raddr) {
183 0           $game = $idx;
184 0           last;
185             }
186             }
187 0 0         if (ref $game) {
188 0           croak "Game not found";
189             }
190             }
191 0           $game = splice @{$games}, $game, 1; # remove from our list
  0            
192             # $self->add_bye($game->white);
193             # $self->add_bye($game->black);
194 0           $self->changed; # add_bye already calls this
195 0           return $game;
196             }
197              
198             sub add_bye {
199 0 0   0 1   croak("Rounds->add_bye is deprecated") if ($deprecate > 0);
200 0           my ($self, $player) = @_;
201              
202             # check for duplicate IDs
203 0 0         return $self if (grep { $_->id eq $player->id } @{$self->{byes}});
  0            
  0            
204 0           push (@{$self->{byes}}, $player);
  0            
205 0           $self->changed;
206 0           return $self;
207             }
208              
209             sub remove_bye {
210 0 0   0 1   croak("Rounds->remove_bye is deprecated") if ($deprecate > 0);
211 0           my ($self, $player) = @_;
212              
213 0           my $idx = $self->_find_bye_idx($player); # convert to index
214 0           my $removed = splice @{$self->{byes}}, $idx, 1;
  0            
215 0           $self->changed;
216 0           return $removed;
217             }
218              
219             sub replace_bye {
220 0 0   0 1   croak("Rounds->replace_bye is deprecated") if ($deprecate > 0);
221 0           my ($self, $old_bye, $new_bye) = @_;
222              
223 0           my $idx = $self->_find_bye_idx($old_bye); # convert to index
224 0           my $removed = $self->{byes}[$idx];
225 0           $self->{byes}[$idx] = $new_bye;
226 0           $self->changed;
227 0           return $removed;
228             }
229              
230             sub swap {
231 0 0   0 1   croak("Rounds->swap is deprecated") if ($deprecate > 0);
232 0           my ($self, $id_0, $id_1) = @_;
233              
234 0           my ($p0, $p1, $opp0, $opp1, $item0, $item1);
235              
236 0           for my $player (@{$self->{byes}}) {
  0            
237 0 0         if ($player->id eq $id_0) {
238 0           $item0 = $player;
239 0           $p0 = $player;
240             }
241 0 0         if ($player->id eq $id_1) {
242 0           $item1 = $player;
243 0           $p1 = $player;
244             }
245             }
246              
247 0           for my $game (@{$self->{games}}) {
  0            
248 0 0         if ($game->white->id eq $id_0) {
    0          
249 0           $item0 = $game;
250 0           $p0 = $game->white;
251 0           $opp0 = $game->black;
252             }
253             elsif ($game->black->id eq $id_0) {
254 0           $item0 = $game;
255 0           $p0 = $game->black;
256 0           $opp0 = $game->white;
257             }
258 0 0         if ($game->white->id eq $id_1) {
    0          
259 0           $item1 = $game;
260 0           $p1 = $game->white;
261 0           $opp1 = $game->black;
262             }
263             elsif ($game->black->id eq $id_1) {
264 0           $item1 = $game;
265 0           $p1 = $game->black;
266 0           $opp1 = $game->white;
267             }
268 0 0 0       last if (defined $item0 and defined $item1);
269             };
270 0 0         if (not defined $item0) {
271 0           croak "ID $id_0 not found in games or Byes lists\n";
272             }
273 0 0         if (not defined $item1) {
274 0           croak "ID $id_1 not found in games or Byes lists\n";
275             }
276             # no-op if both are Player IDs from Byes list
277 0 0 0       return if ($item0->can('id') and $item1->can('id'));
278 0 0 0       if ($item0->can('white') and $item1->can('white')) {
    0          
    0          
279             # both items are Games.
280 0 0 0       if ($item0->white->id eq $item1->white->id and
281             $item0->black->id eq $item1->black->id) { # same game?
282 0           $item0->swap; # just swap black and white players
283             }
284             else {
285             # swap players between two games
286 0 0         if ($p0->id eq $item0->white->id) {
287 0           $item0->white($p1);
288             }
289             else {
290 0           $item0->black($p1);
291             }
292 0 0         if ($p1->id eq $item1->white->id) {
293 0           $item1->white($p0);
294             }
295             else {
296 0           $item1->black($p0);
297             }
298             }
299             }
300             elsif ($item0->can('id')) {
301             # first item is a Bye Player, second is a Game
302 0 0         if ($p1->id eq $item1->white->id) {
303 0           $item1->white($p0);
304             }
305             else {
306 0           $item1->black($p0);
307             }
308 0           $self->replace_bye($p0, $p1);
309             }
310             elsif ($item1->can('id')) {
311             # swap players between game and Byes list
312 0 0         if ($p0->id eq $item0->white->id) {
313 0           $item0->white($p1);
314             }
315             else {
316 0           $item0->black($p1);
317             }
318 0           $self->replace_bye($p1, $p0);
319             }
320 0 0         $item0->handicap if ($item0->can('handicap'));
321 0 0         $item1->handicap if ($item1->can('handicap'));
322             }
323              
324             # find player in BYEs list
325             sub _find_bye_idx {
326 0 0   0     croak("Rounds->_find_bye_idx is deprecated") if ($deprecate > 0);
327 0           my ($self, $idx) = @_;
328              
329 0           my $players = $self->{byes};
330 0 0         if (looks_like_number($idx)) {
    0          
331             # already what we need
332             }
333             elsif (ref $idx) { # must be a Player dataobject
334             # find Player object with matching ID
335             FIND_REFADDR : {
336 0           my $player = $idx;
  0            
337 0           my $id = $player->id;
338 0           for my $ii (0 .. $#{$players}) {
  0            
339 0 0         if ($players->[$ii]->id eq $id) {
340 0           $idx = $ii;
341 0           last FIND_REFADDR;
342             }
343             }
344 0           croak "can't find BYE player with ID $id\n";
345             }
346             }
347             else {
348             # find Player with matching ID
349             FIND_ID : {
350 0           my $id = $idx;
  0            
351 0           for my $ii (0 .. $#{$players}) {
  0            
352 0 0         if ($players->[$ii]->id eq $id) {
353 0           $idx = $ii;
354 0           last FIND_ID;
355             }
356             }
357 0           croak "can't find player matching ID $id\n";
358             }
359             }
360 0 0 0       if ($idx < 0 or
361 0           $idx > $#{$players}) {
362 0           croak "index=$idx is out of bounds\n";
363             }
364 0           return $idx;
365             }
366              
367             # format a string representing the player's rating adjustment this round
368             sub rating_adjustment {
369 0     0 0   my ($self, $player, $round_num) = @_;
370              
371 0           my $rating = sprintf '% 7.3f', Rank_to_Rating($player->rank);
372 0           my $adj_rating = $player->adj_rating($round_num);
373 0 0         $adj_rating = sprintf '% 7.3f', $adj_rating if ($adj_rating);
374 0 0 0       if ($adj_rating and $adj_rating ne $rating) {
375 0           return "$rating->$adj_rating";
376             }
377 0           return $rating;
378             }
379              
380             sub measure_player_field_lengths {
381 0     0 0   my ($self, $player) = @_;
382              
383 0 0         $self->id_len (length $player->id) if (length $player->id > $self->id_len);
384 0 0         $self->name_len(length $player->full_name) if (length $player->full_name > $self->name_len);
385             }
386              
387             sub measure_field_lengths {
388 0     0 0   my ($self) = @_;
389              
390 0           $self->id_len(0);
391 0           $self->handi_len(0);
392 0           $self->komi_len(0);
393 0           $self->name_len(0);
394 0           for my $game (@{$self->{games}}) {
  0            
395 0           $self->measure_player_field_lengths($game->white);
396 0           $self->measure_player_field_lengths($game->black);
397 0 0         $self->handi_len(length $game->handi) if (length $game->handi > $self->handi_len);
398 0 0         $self->komi_len (length $game->komi) if (length $game->komi > $self->komi_len);
399             }
400 0 0         if ($deprecate == 0) {
401 0           for my $bye (@{$self->{byes}}) {
  0            
402 0           $self->measure_player_field_lengths($bye);
403             }
404             }
405             }
406              
407             sub fprint {
408 0     0 0   my ($self, $fh) = @_;
409              
410 0           my $round_num = $self->round_num;
411 0           $fh->print("# Round $round_num\n\n");
412              
413 0           $self->measure_field_lengths;
414 0           for my $game (@{$self->{games}}) {
  0            
415 0           my $w = $game->white;
416 0           my $b = $game->black;
417 0           my $result = '?';
418 0           my $winner = $game->winner;
419 0 0         if ($winner) {
420 0 0         $result = 'w' if ($winner->id eq $w->id);
421 0 0         $result = 'b' if ($winner->id eq $b->id);
422             }
423              
424 0           $fh->printf("%*s %*s %s %*s %*s # %*s (%s) vs (%s) %*s\n",
425             $self->id_len, $w->id,
426             $self->id_len, $b->id,
427             $result,
428             $self->handi_len, $game->handi,
429             $self->komi_len, $game->komi,
430             $self->name_len, $w->full_name,
431             $self->rating_adjustment($w, $round_num),
432             $self->rating_adjustment($b, $round_num),
433             $self->name_len, $b->full_name,
434             );
435             }
436 0           for my $bye (@{$self->{byes}}) {
  0            
437 0           $fh->printf("# BYE: %s %s, %s\n",
438             $bye->id,
439             $bye->last_name,
440             $bye->first_name,
441             );
442             }
443 0           $self->fprint_pending(0);
444             }
445              
446             1;
447              
448             __END__