One thing which separates the great players from the rest is the ability to count the hand: after a few rounds of cards have been played and one or two people have failed to follow suit it is often possible to tell exactly how many cards are left in each suit, in each of the hidden hands. I can't do it, it takes too much effort and concentration. And if I tried it too often in a real game, people would tell me to get a move on before the director comes and takes the cards away.
So to get practice I thought I'd play against Bridge Baron. (Nowadays there are probably much better programs out there.) Didn't work. I am so impatient with the stupid program, and hate losing to it so much, that first I forget about counting and just try to win, then I get tired and start losing (my partner's fault) and eventually give up in disgust. Sigh.
So I set myself a homework assignment: write a program which plays the cards as if we were playing a hand but doesn't know anything about trying to win so it won't sucker me into thinking about that side of it. The program allows me to say at any point how many cards are in each hand and tells me how clever I am if I get it right.
I wrote that in Python and wrote a retro gui for it using curses. All good fun. Practiced for a while. Eventually it got boring and still I wasn't much better at counting the hands.
Something was missing. Mostly in real games you get a lot of help from the bidding: declarer opened 1S so he has at least 5 of them. That means partner has at most one, so play my ace and give partner a ruff. My program was missing out on this much more satisfying aspect of counting.
Homework assignment #2: write a simple-minded bit of code to bid the hands so I can make inferences about the distribution and get much more realistic counting practice.
Actually, it's hard. (I found it hard. If anyone has any smart ideas how to make it easy..)
Here is where I started heading: each hand has a shape (4 numbers with sum 13) and a number of high-card points (hcp). That's enough to do some simple bidding with. To select an opening bid you need some rules that look a bit like this:
3 < hcp < 10 -> open pre_empt(longest)
6 < hcp < 12 and suits_maxlen == 6 -> open weak_two(longest)
16 <= hcp <=18 and !5cardmajor -> open 1NT
12 <= hcp < 21 -> open one(5cardmajor ? longest : longest-minor)
...
Lovely. And that's just the opening bid. The rules for partner to respond are different and depend very much on what was opened. The rules for the people to the left or right of the opener are different again and equally sensitive to what bidding has gone before.
So let's take a step back and see what we need. It looks like we need shape and points for the hand we're bidding (a HandInfo class), information from the auction up to this point (an Auction class) and inferences from the auction as to the shape and strength of partner's hand and the opponents (if we trust their bidding!) so a HandInferences class maybe. (Theoretically that's all derivable from the current Auction object (and the bidding system being used) but it is going to involve significant work to calculate it, so we'll make it a separate class.)
Then we'll have expressions like the ones above to define the bidding system. At each point in the auction the program will have to evaluate various boolean valued expressions to determine what bid to choose. The decision tree selected for evaluation will depend on the bidder's CurrentRole, which would be something like "opener" before anyone has bid, "responder" for the partner of the opening bidder and so on.
In our expressions each (integer valued) symbol, other than a constant, will take its value from one of the three objects of type HandInfo, Auction and HandInferences. The hcp symbol comes from the HandInfo object; a partners_suit would come from the Auction object; partners_length(suit) (a function symbol) would need to use the HandInferences object.
One more thing about the HandInferences is that we have to get them by analysing the Auction and the bidding rules. For that reason we can't simply express the rules as code: we have to be able to traverse the expression tree in one way to evaluate it and in another way to infer from the selected bid what constraints apply to hand shape and high-card points. (Code as data sounds very lisp-like and perhaps I will find I have to go there to solve my homework assignment eventually.)
There's more but let's forget about bridge now: we have an expression tree representing arithmetic and boolean values and we have objects which form part of the evaluation environment. We want certain leaves of the expression tree to take values from objects in the environment.
My first stab at doing this was in Python. The expressions were just in strings and I used eval(expr_text, environment) to evaluate them. I had to do some text parsing to analyse the expressions for computing the hand inferences and it kind of worked but it was getting to the point where eval() on strings was becoming a liability. Then I got a job and had no spare time any more so it got shelved for a while.
Two years later. I've learned a lot of about C++, JavaScript and C#. I've lurked on Artima and played with fun stuff like Ruby and Erlang. The latest language to sound exciting is Scala and I decided to resurrect my bridge bidding project and rewrite it in Scala as a way to learn the language. There is a lot to like about the language and really, I've only scratched the surface, so don't come here for a deep critique. I did run into a curious an frustrating problem though which I'll describe in my next post.
No comments:
Post a Comment