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__ |