File Coverage

blib/lib/Games/Cards/Bridge/Contract.pm
Criterion Covered Total %
statement 9 9 100.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 12 12 100.0


line stmt bran cond sub pod time code
1             package Games::Cards::Bridge::Contract;
2              
3 4     4   108943 use strict;
  4         11  
  4         188  
4 4     4   21 use warnings;
  4         8  
  4         138  
5 4     4   24 use base qw(Class::Accessor);
  4         11  
  4         7134  
6             use Carp;
7              
8             our $VERSION = '0.02';
9              
10             __PACKAGE__->mk_accessors(
11             'declarer', # N E S W
12             'trump', # C D H S N P
13             'vul', # boolean
14             'penalty', # 0=none 1=X 2=XX
15             'bid', # 1..7
16             'made', # bid..7 undef
17             'down', # undef 1..bid
18             );
19              
20             sub new {
21             my $self = shift;
22             my $class = ref($self) || $self;
23             my $p = { @_ };
24             my $obj = bless {}, $class;
25             while( my($k,$v) = each %$p ){
26             next unless $obj->can($k);
27             $obj->set($k, $v);
28             }
29             $obj->set('vul', $obj->vul ? 1 : 0 ); # force boolean
30             $obj->set('penalty', 0 ) if ! $obj->penalty; # force 0 for false
31             $obj->__validate;
32             return $obj;
33             }
34              
35             sub __validate {
36             my $self = shift;
37             return if $self->trump eq 'P';
38             croak 'declarer must be one of (N,E,S,W)' unless $self->declarer =~ /^[NESW]$/;
39             croak 'trump must be one of (C,D,H,S,N,P)' unless $self->trump =~ /^[CDHSN]$/;
40             croak 'vul must be true or false' unless $self->vul =~ /^[01]$/;
41             croak 'penalty must be one of (0,1,2)' unless $self->penalty =~ /^[012]$/;
42             croak 'bid must be one of (1..7)' unless $self->bid =~ /^[1234567]$/;
43             if( defined $self->made ){
44             croak 'made must be one of (1..7) and >= bid' unless $self->made =~ /^[1234567]$/ && $self->made >= $self->bid;
45             croak 'down must be unset' if defined $self->down;
46             }else{
47             croak 'down must be one of (1..13) and <= bid+6' unless $self->down =~ /^([123456789]|1[0123])$/ && $self->down <= $self->bid + 6;
48             croak 'made must be unset' if defined $self->made;
49             }
50             }
51              
52             sub minor { return shift->trump =~ /^[CD]$/ ? 1 : 0; }
53             sub major { return shift->trump =~ /^[HS]$/ ? 1 : 0; }
54             sub notrump { return shift->trump eq 'N' ? 1 : 0; }
55             sub passout { return shift->trump eq 'P' ? 1 : 0; }
56             sub slam { return shift->bid >= 6 ? 1 : 0; }
57             sub small_slam { return shift->bid == 6 ? 1 : 0; }
58             sub grand_slam { return shift->bid == 7 ? 1 : 0; }
59             sub game {
60             my $self = shift;
61             my $tricks = shift;
62             $tricks = $self->bid unless defined $tricks;
63             return $tricks >= 3 && ($self->notrump || ($self->major && $tricks >= 4) || ($self->minor && $tricks >= 5));
64             }
65             sub overtricks {
66             my $self = shift;
67             return unless $self->made;
68             return $self->made - $self->bid;
69             }
70              
71             sub rubber_score {
72             my $self = shift;
73             my $score = $self->__calc_score;
74             return ( $score->{overtricks} + $score->{slam} + $score->{insult}, $score->{tricks}, $score->{undertricks} );
75             }
76              
77             sub duplicate_score {
78             my $self = shift;
79             my $score = $self->__calc_score;
80             return $score->{tricks} + $score->{overtricks} + $score->{partscore} + $score->{game} + $score->{slam} + $score->{insult} - $score->{undertricks};
81             }
82              
83             sub __calc_score {
84             my $self = shift;
85             my %score = map { $_ => 0 } qw/ undertricks tricks overtricks partscore game slam insult /;
86             if( $self->passout ){
87             # do nothing
88             }elsif( $self->made ){
89             my ($n, $over) = ($self->bid, $self->overtricks);
90             my ($minor, $major, $nt) = ($self->minor, $self->major, $self->notrump);
91             $score{tricks} += 30*$n+10 if $nt; # notrump: 40 first, 30 each after
92             $score{tricks} += 30*$n if $major; # major: 30 each
93             $score{tricks} += 20*$n if $minor; # minor: 20 each
94             $score{tricks} *= 2*$self->penalty if $self->penalty; # multiply 2x or 4x if X or XX
95             $n *= 2*$self->penalty if $self->penalty; # change the effective tricks based on X/XX
96             if( $self->game($n) ){
97             $score{game} += $self->vul ? 500 : 300; # game bonus
98             }else{
99             $score{partscore} += 50; # partscore bonus
100             }
101             if( $self->grand_slam ){
102             $score{slam} += ($self->vul ? 1500 : 1000); # grand slam bonus
103             }elsif( $self->small_slam ){
104             $score{slam} += ($self->vul ? 750 : 500); # small slam bonus
105             }
106             if( $self->penalty ){
107             $score{overtricks} += $over * 100 * $self->penalty * ($self->vul?2:1); # overtricks: 100 each; x2/x4 for X/XX; x2 for vul
108             $score{insult} += 50*$self->penalty; # plus 50 or 100 for X or XX
109             }else{
110             $score{overtricks} += $over * ($minor ? 20 : 30); # minor/major/notrump: 20/30/30 per overtrick
111             }
112             }else{ #down
113             my $n = $self->down;
114             if( ! $self->penalty ){
115             $score{undertricks} += $n * ( $self->vul ? 100 : 50 );
116             }else{
117             if( $self->vul ){
118             $score{undertricks} += 300*$n-100;
119             }else{
120             $score{undertricks} += 300*$n-400 + ($n==1?200:0) + ($n==2?100:0);
121             }
122             $score{undertricks} *= $self->penalty; # x2 if XX
123             }
124             }
125             return \%score;
126             }
127              
128             1;
129             __END__