File Coverage

blib/lib/Games/Go/AGA/DataObjects/Game.pm
Criterion Covered Total %
statement 47 91 51.6
branch 12 36 33.3
condition 3 6 50.0
subroutine 15 20 75.0
pod 4 9 44.4
total 81 162 50.0


line stmt bran cond sub pod time code
1             #===============================================================================
2             #
3             # FILE: Games::Go::AGA::DataObjects::Game.pm
4             #
5             # USAGE: use Games::Go::AGA::DataObjects::Game;
6             #
7             # PODNAME: Games::Go::AGA::DataObjects::Game
8             # ABSTRACT: model an AGA game
9             #
10             # AUTHOR: Reid Augustin (REID),
11             # CREATED: 11/19/2010 03:13:05 PM PST
12             #===============================================================================
13              
14 3     3   3619 use strict;
  3         4  
  3         95  
15 3     3   13 use warnings;
  3         5  
  3         112  
16              
17             # the Game class is useful for tournament pairing
18             package Games::Go::AGA::DataObjects::Game;
19 3     3   19 use Moo;
  3         4  
  3         28  
20 3     3   788 use namespace::clean;
  3         5  
  3         25  
21              
22 3     3   580 use Carp;
  3         3  
  3         233  
23 3     3   13 use Scalar::Util qw(refaddr weaken);
  3         5  
  3         138  
24 3     3   13 use Try::Tiny;
  3         4  
  3         166  
25 3     3   17 use Games::Go::AGA::Parse::Util qw( Rank_to_Rating );
  3         3  
  3         142  
26 3     3   13 use Games::Go::AGA::DataObjects::Types qw( isa_Int isa_CodeRef isa_Handicap isa_Komi );
  3         3  
  3         3586  
27              
28             our $VERSION = '0.152'; # VERSION
29              
30 13 50   13 0 14474 sub isa_Player { die("$_[0] is not a Games::Go::AGA::DataObjects::Player\n") if (ref $_[0] ne 'Games::Go::AGA::DataObjects::Player') }
31             has black => (
32             is => 'rw',
33             isa => \&isa_Player,
34             weak_ref => 1, # Players have Games, Games have Players, so weaken
35             trigger => sub
36             {
37             my $self = shift;
38             $self->_set_player('black', @_);
39             },
40             );
41             has white => (
42             is => 'rw',
43             isa => \&isa_Player,
44             weak_ref => 1,
45             trigger => sub
46             {
47             my $self = shift;
48             $self->_set_player('white', @_);
49             },
50             );
51             has table_number => (
52             is => 'rw',
53             isa => \&isa_Int,
54             lazy => 1,
55             default => sub { 0 },
56             trigger => sub { shift->changed; },
57             );
58             has handi => (
59             is => 'rw',
60             isa => \&isa_Handicap,
61             lazy => 1,
62             default => sub { 0 },
63             trigger => sub { shift->changed; },
64             # alias => 'handicap',
65             );
66             has komi => (
67             is => 'rw',
68             isa => \&isa_Komi,
69             lazy => 1,
70             default => sub { 5.5 },
71             trigger => sub { shift->changed; },
72             );
73             has result => (
74             is => 'rw',
75             lazy => 1,
76             default => '?',
77             trigger => sub {
78             my ($self, $new) = @_;
79              
80             if (ref $new) { # better be a Games::Go::AGA::DataObjects::Player
81             my $id = $new->id;
82             if ($id eq $self->white->id) { $self->result('w'); return }
83             if ($id eq $self->black->id) { $self->result('b'); return }
84             }
85             $new ||= '?';
86             $new = lc $new;
87             if ($new ne '?' and $new ne 'w' and $new ne 'b') {
88             croak("result must be '?' (or false), 'w', 'b', or one of the players\n");
89             }
90             $self->{result} = $new;
91             $self->changed;
92             },
93             );
94             has change_callback => (
95             isa => \&isa_CodeRef,
96             is => 'rw',
97             lazy => 1,
98             default => sub { sub { } }
99             );
100             has built => (
101             is => 'rw',
102             );
103              
104             sub BUILD {
105 6     6 0 53 my ($self, $args) = @_;
106              
107 6 50 33     42 if (defined $args and exists $args->{winner}) {
108 0         0 $self->result(delete $args->{winner});
109             }
110 6         177 $self->built(1);
111             }
112              
113             sub changed {
114 38     38 0 66 my ($self) = @_;
115              
116 38         52 &{$self->change_callback}(@_);
  38         921  
117             }
118              
119             sub _set_player {
120 13     13   29 my ($self, $color, $new) = @_;
121              
122 13 50 66     150 if ( $self->built # after object is built,
123             and $self->result ne '?') { # can't change players if result is set
124 0         0 $self->{$color} = $self->{"prev_$color"}; # restore
125 0         0 croak 'Result already set, cannot change players';
126             }
127 13         827 $self->{"prev_$color"} = $new;
128 13         43 $self->changed;
129             }
130              
131             sub winner {
132 28     28 1 8997 my ($self, $new) = @_;
133              
134 28 100       94 if (@_ > 1) {
135 8         187 $self->result($new);
136             }
137 27 100       707 return $self->white if ($self->result eq 'w');
138 19 100       1055 return $self->black if ($self->result eq 'b');
139 4         52 return; # undef
140             }
141              
142             sub loser {
143 8     8 1 14 my ($self, $new) = @_;
144              
145 8 100       217 return $self->black if ($self->result eq 'w');
146 6 50       215 return $self->white if ($self->result eq 'b');
147 0           return; # undef
148             }
149              
150             sub opponent {
151 0     0 0   my ($self, $player) = @_;
152              
153 0           my $me = $player->id;
154 0 0         return $self->white if ($self->black->id eq $me);
155 0 0         return $self->black if ($self->white->id eq $me);
156 0           croak "ID $me is not in this game";
157             }
158              
159             sub swap {
160 0     0 0   my ($self) = @_;
161              
162 0           my $white = $self->white;
163 0           $self->{white} = $self->black;
164 0           $self->{black} = $white;
165 0           $self->changed;
166             }
167              
168             sub handicap {
169 0     0 1   my ($self, $default_komi) = @_;
170              
171 0 0         if (defined $self->winner) {
172 0           croak 'Winner already set, cannot change players';
173             }
174              
175 0 0         $default_komi = 7.5 if (not defined $default_komi);
176 0           my $white = $self->white;
177 0           my $black = $self->black;
178 0           my $rankDiff = $self->_rank_to_level($white) - $self->_rank_to_level($black);
179 0 0         if ($rankDiff < 0.5) {
    0          
    0          
180 0           $self->handi(0);
181 0           $self->komi($default_komi); # normal komi game
182             }
183             elsif ($rankDiff < 1.0) {
184 0           $self->handi(0);
185 0           $self->komi(0.5); # no komi game, white wins ties
186             }
187             elsif ($rankDiff < 1.5) {
188 0           $self->handi(0);
189 0           $self->komi(-$default_komi); # reverse komi game
190             }
191             else {
192 0           $self->handi(int $rankDiff + 0.5); # handicap game
193 0           $self->komi(0.5); # white wins ties
194             }
195             # TODO handi/komi have different relationship in AGA vs ING rules...
196 0           $self->changed;
197             }
198              
199             sub auto_handicap {
200 0     0 1   my ($self, $default_komi) = @_;
201              
202 0 0         if (defined $self->winner) {
203 0           croak 'Winner already set, cannot change players';
204             }
205              
206 0           my $white = $self->white;
207 0           my $black = $self->black;
208 0           my $rankDiff = $self->_rank_to_level($white) - $self->_rank_to_level($black);
209 0 0         if ($rankDiff < 0.1) { # black is significantly stronger than white - swap
210 0           $self->{white} = $black;
211 0           $self->{black} = $white;
212             };
213 0           $self->handicap($default_komi);
214             }
215              
216             # AGA ratings have a hole between +1 and -1 which messes up
217             # handicap/komi calculations. Collapse that hole to make a 'level'
218             sub _rank_to_level {
219 0     0     my ($self, $player) = @_;
220              
221 0           my $level = $player->adj_rating(-1);
222 0 0         return $level + (($level > 0) ? -1 : 1);
223             }
224              
225             1;
226              
227             __END__