File Coverage

blib/lib/Games/Go/AGA/DataObjects/Game.pm
Criterion Covered Total %
statement 34 78 43.5
branch 2 24 8.3
condition n/a
subroutine 11 17 64.7
pod 3 6 50.0
total 50 125 40.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             # COMPANY: LucidPort Technology, Inc.
12             # CREATED: 11/19/2010 03:13:05 PM PST
13             #===============================================================================
14              
15 2     2   2362 use strict;
  2         3  
  2         77  
16 2     2   8 use warnings;
  2         2  
  2         62  
17              
18             # the Game class is useful for tournament pairing
19             package Games::Go::AGA::DataObjects::Game;
20              
21 2     2   6 use Mouse;
  2         3  
  2         9  
22 2     2   522 use Carp;
  2         3  
  2         130  
23 2     2   9 use Scalar::Util qw(refaddr weaken);
  2         2  
  2         86  
24 2     2   491 use Try::Tiny;
  2         1057  
  2         97  
25 2     2   9 use Games::Go::AGA::Parse::Util qw( Rank_to_Rating );
  2         2  
  2         77  
26 2     2   8 use Games::Go::AGA::DataObjects::Types;
  2         2  
  2         1834  
27              
28             our $VERSION = '0.107'; # VERSION
29              
30             has 'black' => (
31             is => 'rw',
32             isa => 'Games::Go::AGA::DataObjects::Player',
33             weak_ref => 1, # Players have Games, Games have Players, so weaken
34             trigger => sub
35             {
36             my $self = shift;
37             $self->_set_player('black', @_);
38             },
39             );
40             has 'white' => (
41             is => 'rw',
42             isa => 'Games::Go::AGA::DataObjects::Player',
43             weak_ref => 1,
44             trigger => sub
45             {
46             my $self = shift;
47             $self->_set_player('white', @_);
48             },
49             );
50             has 'table_number' => (
51             is => 'rw',
52             isa => 'Int',
53             default => 0,
54             trigger => sub { shift->changed; },
55             );
56             has 'handi' => (
57             is => 'rw',
58             isa => 'Handicap',
59             default => 0,
60             trigger => sub { shift->changed; },
61             # alias => 'handicap',
62             );
63             has 'komi' => (
64             is => 'rw',
65             isa => 'Komi',
66             default => 5.5,
67             trigger => sub { shift->changed; },
68             );
69             has 'winner' => (
70             is => 'rw',
71             default => undef,
72             trigger => sub {
73             my ($self, $new) = @_;
74             #printf("winner(%d args: %s)\n", scalar @_, join(q{, }, @_));
75             if (defined $new) {
76             if (
77             (refaddr($new) != refaddr($self->black)) and
78             (refaddr($new) != refaddr($self->white))) {
79             $self->winner($self->{prev_winner}); # change-back
80             croak("winner must be either undef, or one of the players\n");
81             }
82             $self->{loser} = (refaddr($new) == refaddr($self->black)) ?
83             $self->{white} : $self->{black}; # not the winner
84             $self->{prev_winner} = $new; # save for change-back (above)
85             }
86             else {
87             delete $self->{loser}; # remove winner and loser
88             }
89             $self->changed;
90             }
91             );
92             has 'change_callback' => (
93             isa => 'Maybe[CodeRef]',
94             is => 'rw',
95             default => sub { sub { } }
96             );
97              
98             sub changed {
99 26     26 0 24 my ($self) = @_;
100              
101 26 50       45 &{$self->change_callback}($self) if ($self->{change_callback});
  26         44  
102             }
103              
104             sub _set_player {
105 9     9   14 my ($self, $color, $new) = @_;
106              
107 9 50       25 if (defined $self->winner) {
108 0         0 $self->{$color} = $self->{"prev_$color"}; # restore
109 0         0 croak 'Winner already set, cannot change players';
110             }
111 9         29 $self->{"prev_$color"} = $new;
112 9         13 $self->changed;
113             }
114              
115             sub loser {
116 0     0 1   my ($self) = @_;
117              
118 0           return $self->{loser};
119             }
120              
121             sub opponent {
122 0     0 0   my ($self, $player) = @_;
123              
124 0           my $me = $player->id;
125 0 0         return $self->white if ($self->black->id eq $me);
126 0 0         return $self->black if ($self->white->id eq $me);
127 0           croak "ID $me is not in this game";
128             }
129              
130             sub swap {
131 0     0 0   my ($self) = @_;
132              
133 0           my $white = $self->white;
134 0           $self->{white} = $self->black;
135 0           $self->{black} = $white;
136 0           $self->changed;
137             }
138              
139             sub handicap {
140 0     0 1   my ($self, $default_komi) = @_;
141              
142 0 0         if (defined $self->winner) {
143 0           croak 'Winner already set, cannot change players';
144             }
145              
146 0 0         $default_komi = 7.5 if (not defined $default_komi);
147 0           my $white = $self->white;
148 0           my $black = $self->black;
149 0           my $rankDiff = $self->_rank_to_level($white) - $self->_rank_to_level($black);
150 0 0         if ($rankDiff < 0.5) {
    0          
    0          
151 0           $self->handi(0);
152 0           $self->komi($default_komi); # normal komi game
153             }
154             elsif ($rankDiff < 1.0) {
155 0           $self->handi(0);
156 0           $self->komi(0.5); # no komi game, white wins ties
157             }
158             elsif ($rankDiff < 1.5) {
159 0           $self->handi(0);
160 0           $self->komi(-$default_komi); # reverse komi game
161             }
162             else {
163 0           $self->handi(int $rankDiff + 0.5); # handicap game
164 0           $self->komi(0.5); # white wins ties
165             }
166             # TODO handi/komi have different relationship in AGA vs ING rules...
167 0           $self->changed;
168             }
169              
170             sub auto_handicap {
171 0     0 1   my ($self, $default_komi) = @_;
172              
173 0 0         if (defined $self->winner) {
174 0           croak 'Winner already set, cannot change players';
175             }
176              
177 0           my $white = $self->white;
178 0           my $black = $self->black;
179 0           my $rankDiff = $self->_rank_to_level($white) - $self->_rank_to_level($black);
180 0 0         if ($rankDiff < 0.1) { # black is significantly stronger than white - swap
181 0           $self->{white} = $black;
182 0           $self->{black} = $white;
183             };
184 0           $self->handicap($default_komi);
185             }
186              
187             # AGA ratings have a hole between +1 and -1 which messes up
188             # handicap/komi calculations. Collapse that hole to make a 'level'
189             sub _rank_to_level {
190 0     0     my ($self, $player) = @_;
191              
192 0           my $level = $player->adj_rating;
193 0 0         return $level + (($level > 0) ? -1 : 1);
194             }
195              
196 2     2   12 no Mouse;
  2         4  
  2         12  
197             __PACKAGE__->meta->make_immutable;
198              
199             1;
200              
201             __END__