File Coverage

blib/lib/Games/Go/AGA/DataObjects/Register.pm
Criterion Covered Total %
statement 114 174 65.5
branch 23 46 50.0
condition 6 16 37.5
subroutine 22 29 75.8
pod 11 15 73.3
total 176 280 62.8


line stmt bran cond sub pod time code
1             #===============================================================================
2             #
3             # FILE: Games::Go::AGA::DataObjects::Register.pm
4             #
5             # USAGE: use Games::Go::AGA::DataObjects::Register;
6             #
7             # PODNAME: Games::Go::AGA::DataObjects::Register
8             # ABSTRACT: models AGA register.tde file information
9             #
10             # AUTHOR: Reid Augustin (REID),
11             # COMPANY: LucidPort Technology, Inc.
12             # CREATED: 11/19/2010 03:13:05 PM PST
13             #===============================================================================
14              
15              
16 1     1   989 use strict;
  1         2  
  1         51  
17 1     1   7 use warnings;
  1         1  
  1         66  
18              
19             package Games::Go::AGA::DataObjects::Register;
20 1     1   7 use parent 'Games::Go::AGA::DataObjects::Directives';
  1         1  
  1         10  
21 1     1   89 use Mouse;
  1         2  
  1         4  
22              
23 1     1   275 use Carp;
  1         2  
  1         95  
24 1     1   7 use Readonly;
  1         2  
  1         71  
25 1     1   7 use Scalar::Util qw( refaddr looks_like_number );
  1         1  
  1         79  
26 1     1   764 use IO::File;
  1         11679  
  1         205  
27 1     1   10 use Games::Go::AGA::Parse::Util qw( normalize_ID );
  1         2  
  1         65  
28 1     1   7 use Games::Go::AGA::Parse::Util qw( Rank_to_Rating );
  1         2  
  1         53  
29 1     1   7 use Games::Go::AGA::DataObjects::Player;
  1         2  
  1         33  
30 1     1   8 use Games::Go::AGA::DataObjects::Directives;
  1         1  
  1         2012  
31              
32             our $VERSION = '0.107'; # VERSION
33              
34             # has 'directives' => (
35             # isa => 'Games::Go::AGA::DataObjects::Directives',
36             # is => 'rw',
37             # default => sub {
38             # Games::Go::AGA::DataObjects::Directives->new();
39             # },
40             # );
41             # has 'comments' => (
42             # isa => 'ArrayRef',
43             # is => 'ro',
44             # default => sub { [] }
45             # );
46             # has 'players' => (
47             # isa => 'ArrayRef',
48             # is => 'ro',
49             # default => sub { [] }
50             # );
51             has 'change_callback' => (
52             isa => 'Maybe[CodeRef]',
53             is => 'rw',
54             default => sub { sub { } }
55             );
56              
57             sub BUILD {
58 1     1 1 3 my ($self) = @_;
59 1         3 $self->{comments} = [];
60 1         4 $self->{players} = [];
61             }
62              
63             sub changed {
64 23     23 0 25 my ($self) = @_;
65              
66 23 50       49 &{$self->change_callback}($self) if ($self->{change_callback});
  23         54  
67             }
68              
69             sub comments {
70 0     0 1 0 my ($self) = @_;
71              
72 0 0       0 return wantarray ? @{$self->{comments}} : $self->{comments};
  0         0  
73             }
74              
75             sub add_comment {
76 0     0 0 0 my ($self, $comment) = @_;
77              
78 0         0 push @{$self->{comments}}, $comment;
  0         0  
79             }
80              
81             sub players {
82 2     2 1 6 my ($self) = @_;
83              
84 2 100       7 return wantarray ? @{$self->{players}} : $self->{players};
  1         4  
85             }
86              
87             sub id_is_duplicate {
88 21     21 1 935 my ($self, $id, $player) = @_;
89              
90             # normalize the ID first. IDs in Players are already normalised.
91 21         47 $id = normalize_ID($id);
92 21         179 my @matched = grep { $id eq $_->id } @{$self->{players}};
  207         322  
  21         45  
93 21   50     87 my $my_refaddr = refaddr $player || 0;
94 21         39 foreach my $p (@matched) {
95 2 50       6 if (refaddr $p != $my_refaddr) {
96 2         8 return 1
97             }
98             }
99 19         44 return 0;
100             }
101              
102             sub insert_player_at_idx {
103 19     19 1 919 my ($self, $idx, $player) = @_;
104              
105              
106 19         37 my $id = $player->id;
107 19 50       32 if (not $id) {
108 0         0 $id = 1;
109 0         0 $id++ while ($self->id_is_duplicate("TMP$id", undef));
110 0         0 $id = "TMP$id";
111 0         0 $player->id($id);
112             }
113 19 100       75 if ($self->id_is_duplicate($id, undef)) {
114 1         181 croak "duplicate ID: $id\n";
115             }
116             #print "insert_player ", $player->id, " at $idx\n";
117 18 50 33     47 if ($idx < 0 or
  0         0  
118             $idx > $#{$self->{players}}) {
119 18         12 push (@{$self->{players}}, $player);
  18         35  
120             }
121             else {
122 0         0 splice (@{$self->{players}}, $idx, 0, $player);
  0         0  
123             }
124 18         37 $self->changed;
125             }
126              
127             sub get_player_idx {
128 0     0 1 0 my ($self, $idx) = @_;
129              
130 0         0 return $self->_find_player_idx($idx);
131             }
132              
133             sub get_player {
134 0     0 1 0 my ($self, $idx) = @_;
135              
136 0         0 $idx = $self->_find_player_idx($idx);
137 0         0 return $self->{players}[$idx];
138             }
139              
140             sub delete_player {
141 0     0 1 0 my ($self, $idx) = @_;
142              
143 0         0 $idx = $self->_find_player_idx($idx);
144              
145 0         0 my $players = $self->{players};
146 0 0       0 if (@{$players->[$idx]->games}) {
  0         0  
147 0         0 my $id = $players->[$idx]->id;
148 0         0 croak "Games recorded for $id, can't delete\n";
149             }
150 0         0 my $player = splice(@{$players}, $idx, 1); # delete and return it
  0         0  
151             #print "delete ", $player->id, "\n";
152 0         0 $self->changed;
153 0         0 return $player;
154             }
155              
156             sub _find_player_idx {
157 0     0   0 my ($self, $idx) = @_;
158              
159 0         0 my $players = $self->{players};
160 0 0       0 if (looks_like_number($idx)) {
    0          
161             # already what we need
162             }
163             elsif (ref $idx) { # must be a Player dataobject
164             # find Player object with matching refaddr
165 0         0 FIND_REFADDR : {
166 0         0 my $player = $idx;
167 0         0 my $my_refaddr = refaddr($player);
168 0         0 for my $ii (0 .. $#{$players}) {
  0         0  
169 0 0       0 if (refaddr($players->[$ii]) == $my_refaddr) {
170 0         0 $idx = $ii;
171 0         0 last FIND_REFADDR;
172             }
173             }
174 0         0 my $id = $player->id;
175 0         0 croak "can't find player at refaddr = $my_refaddr (ID=$id)\n";
176             }
177             }
178             else {
179             # find Player with matching ID
180 0         0 FIND_ID : {
181 0         0 my $id = $idx;
182 0         0 for my $ii (0 .. $#{$players}) {
  0         0  
183 0 0       0 if ($players->[$ii]->id eq $id) {
184 0         0 $idx = $ii;
185 0         0 last FIND_ID;
186             }
187             }
188 0         0 croak "can't find player matching ID $id\n";
189             }
190             }
191 0 0 0     0 if ($idx < 0 or
  0         0  
192             $idx > $#{$players}) {
193 0         0 croak "index=$idx is out of bounds\n";
194             }
195 0         0 return $idx;
196             }
197              
198             # override some Directives methods so we can intercept
199             # BAND_BREAKS directives (which requires player info)
200             sub get_directive_value {
201 20     20 1 21 my $self = shift;
202 20         24 my ($key) = $_[0];
203              
204 20 100       44 if (uc $key eq 'BAND_BREAKS') {
205 19         25 return $self->break_bands;
206             }
207 1         4 return $self->SUPER::get_directive_value(@_);
208             }
209              
210             # break players into number of bands in BANDS directives
211             sub break_bands {
212 19     19 0 18 my ($self) = @_;
213              
214 19         62 my $band_breaks = $self->SUPER::get_directive_value('BAND_BREAKS');
215 19 100       117 return $band_breaks if ($band_breaks);
216              
217 1         4 my $num_bands = $self->get_directive_value('BANDS');
218 1 50 33     8 return if (not $num_bands # no BANDS directive and not BAND_BREAKS
219             or $num_bands == 1); # everyone is in same band
220              
221 1         3 my @players = $self->players;
222 1         2 my %entrants_per_rating;
223 1         2 for my $player (@players) {
224 18         227 $entrants_per_rating{int $player->rating}++; # count entrants of each rating
225             }
226 1         18 my @sorted_ranks = sort {$b <=> $a} keys %entrants_per_rating;
  11         12  
227 1         2 my $running_total = 0;
228 1         2 my $ii = 0;
229 1         1 my @band_breaks;
230 1         3 for my $band (1 .. $num_bands - 1) {
231 2         5 my $next_break = $band * (@players / $num_bands);
232 2         6 while ($running_total + $entrants_per_rating{$sorted_ranks[$ii]} < $next_break) {
233 3         6 $running_total += $entrants_per_rating{$sorted_ranks[$ii++]};
234             }
235 2 50       6 if (($running_total + ($entrants_per_rating{$sorted_ranks[$ii]} / 2) < $next_break)) {
236 2         3 $running_total += $entrants_per_rating{$sorted_ranks[$ii++]};
237             }
238             #print "$running_total to band $band=$sorted_ranks[$ii - 1]\n";
239 2         3 push(@band_breaks, $sorted_ranks[$ii - 1]);
240 2 50       10 last if ($ii >= @sorted_ranks);
241             }
242 1         3 $band_breaks = join ' ', @band_breaks;
243 1         4 $self->set_directive_value('BAND_BREAKS', $band_breaks);
244 1         12 return $band_breaks;
245             }
246              
247             sub bands {
248 18     18 1 15 my ($self) = @_;
249              
250 18   50     32 my @breaks = split /[^\d\.\-]+/, ($self->get_directive_value('BAND_BREAKS') || '');
251             return wantarray
252             ? @breaks
253 18 50       43 : \@breaks;
254             }
255              
256             sub which_band_is {
257 18     18 1 387 my ($self, $rating) = @_;
258              
259 18         26 my $bands = $self->bands;
260 18 50       17 return 0 if (@{$bands} < 1); # one or no bands defined, all players are in band 0
  18         27  
261              
262 18         34 $rating = Rank_to_Rating($rating); # make sure it's numeric
263 18         216 my $idx;
264 18         18 for ($idx = 0; $idx < @{$bands}; $idx++) { # go past the end
  35         52  
265 30         35 my $limit = $bands->[$idx];
266 30 100 66     73 if ($limit < 0 and
267             $limit == int $limit) { # -2.0 means entire 2K range down to -2.9999,
268 12         11 $limit = $limit - 1; # but -2.5 really means -2.5.
269 12 100       20 last if ($rating > $limit); # dan: -3.0 is a 3 kyu
270             }
271             else {
272 18 100       35 last if ($rating >= $limit); # dan: 3.0 is a 3 dan
273             }
274             }
275 18         86 return $idx;
276             }
277              
278             sub fprint {
279 0     0 0   my ($self, $fh) = @_;
280              
281 0           $self->SUPER::fprint($fh); # print the directives
282 0           foreach my $comment ($self->comments) {
283 0           $fh->print("#$comment\n");
284             }
285 0           foreach my $player ($self->players) {
286 0           $player->fprint_register($fh);
287             }
288             }
289              
290 1     1   9 no Mouse;
  1         2  
  1         9  
291             __PACKAGE__->meta->make_immutable;
292              
293             1;
294              
295             __END__