Initial checkin.
[bridge-toys] / mason / .perl-lib / Deal.pm
CommitLineData
bba7b95e
MW
1### -*-perl-*-
2
3package Deal;
4
5use MIME::Base32;
6
7###--------------------------------------------------------------------------
8### Random numbers.
9
10package Deal::Random;
11
12sub init ($) { }
13
14sub range ($$) {
15 my ($me, $max) = @_;
16
17 my ($nby, $get, $r);
18 if ($max <= 255) { $nby = 1; $get = "C"; }
19 elsif ($max <= 65535) { $nby = 2; $get = "S"; }
20 else { $nby = 4; $get = "L"; }
21
22 my $lim = unpack $get, "\xff" x $nby;
23 my $m = int $lim/$max;
24 my $thresh = $m*$max;
25 do { $r = unpack $get, $me->bytes($nby); } while $r >= $thresh;
26 return $r%$max;
27}
28
29package Deal::BufferedRandom;
30
31@ISA = qw(Deal::Random);
32
33sub init ($) {
34 my ($me) = @_;
35 $me->{buf} = "";
36 $me->SUPER::init;
37}
38
39sub bytes ($$) {
40 my ($me, $n) = @_;
41
42 if ($n > length $me->{buf}) {
43 my $want = ($n + 4095 - length $me->{buf})&-4096;
44 while ($want > 0) {
45 my $more = $me->fetch($want);
46 $want -= length $more;
47 $me->{buf} .= $more;
48 }
49 }
50
51 my $chunk = substr $me->{buf}, 0, $n;
52 $me->{buf} = substr $me->{buf}, $n;
53 return $chunk;
54}
55
56package Deal::SysRandom;
57
58@ISA = qw(Deal::BufferedRandom);
59
60sub new ($) {
61 my ($pkg) = @_;
62 my $me = bless {};
63 $me->init;
64 return $me;
65}
66
67sub init ($) {
68 my ($me) = @_;
69 open my $rand, "/dev/urandom" or die "open (/dev/urandom): $!";
70 $me->{rand} = $rand;
71 $me->SUPER::init;
72}
73
74sub fetch ($$) {
75 my ($me, $want) = @_;
76 defined (read $me->{rand}, my $more, $want)
77 or die "read (/dev/urandom): $!";
78 return $more;
79}
80
81package Deal::SeedRandom;
82
83use Digest::SHA qw(sha256);
84
85@ISA = qw(Deal::BufferedRandom);
86
87sub new ($@) {
88 my ($pkg, @args) = @_;
89 my $me = bless {};
90 $me->init(@args);
91 return $me;
92}
93
94sub init ($$) {
95 my ($me, $seed) = @_;
96 $me->{seed} = $seed;
97 $me->{i} = 0;
98}
99
100sub fetch ($$) {
101 my ($me, $want) = @_;
102 return sha256(pack "La*", $me->{i}++, $me->{seed});
103}
104
105###--------------------------------------------------------------------------
106### General stuff.
107
108package Deal;
109
110our $RAND = Deal::SysRandom->new;
111
112sub shuffle ($@) {
113 my ($r, @x) = @_;
114 my $n = @x;
115 for (my $i = 0; $i < $n - 1; $i++) {
116 my $k = $i + $r->range($n - $i);
117 ($x[$i], $x[$k]) = ($x[$k], $x[$i]);
118 }
119 return @x;
120}
121
122our @LINE; $LINE[$_] = $_%4 + 1 for 0..51;
123
124sub line () { return shuffle $RAND, @LINE; }
125
126###--------------------------------------------------------------------------
127### Generating hands for study.
128
129our @RANK = ("A", "K", "Q", "J", "10", "9",
130 "8", "7", "6", "5", "4", "3", "2");
131our @SUIT = ("C", "D", "H", "S");
132our @DECK;
133for (my $r = 0; $r < @RANK; $r++)
134 { for my $s (@SUIT) { push @DECK, [$s, $r]; } }
135
136our @SEAT = ("N", "W", "S", "E");
137our @VULN = ("None", "N/S", "E/W", "All");
138
139our %HCP = ("A" => 4, "K" => 3, "Q" => 2, "J" => 1);
140
141sub hand (@) {
142 my (@cards) = @_;
143 my %card;
144
145 ## Work out the cards and sort them into suits.
146 for my $s (@SUIT) { $card{$s} = []; }
147 for my $c (@cards) { push @{$card{$c->[0]}}, $c->[1]; }
148 for my $s (@SUIT) {
149 @{$card{$s}} = map { $RANK[$_] } sort { $a <=> $b } @{$card{$s}};
150 }
151
152 ## Count the high-card points and losers.
153 my $hcp = 0;
154 my $ltc = 0;
155 for my $s (@SUIT) {
156 my @c = @{$card{$s}};
157 my $n = @c;
158 my %c = map { $_ => 1 } @c;
159 for my $i (keys %HCP) { $hcp += $HCP{$i} if $c{$i}; }
160 for my $i ("A", "K", "Q") {
161 last unless $n--;
162 $ltc++ unless $c{$i};
163 }
164 }
165
166 ## Done.
167 return { %card, ltc => $ltc, hcp => $hcp };
168}
169
170sub deal (%) {
171 my $deal = { @_ };
172 $deal->{seed} //= lc(MIME::Base32::encode($RAND->bytes(16)));
173 my $r = Deal::SeedRandom->new($deal->{seed});
174 my @cards = shuffle $r, @DECK;
175 for my $s (@SEAT) { $deal->{$s} = hand splice @cards, 0, 13; }
176 $deal->{dealer} //= $r->range(4);
177 $deal->{vuln} //= $r->range(4);
178
179 return $deal;
180}
181
182###----- That's all, folks --------------------------------------------------
183
1841;