As explained in my first bridge related post, before a round of bridge, firstly, the players bid to determine how many ticks they will make with what trump. Bridge players can define their own rules for how to bid, and as part of my effort to make a bridge bot, I want to design a little language in which we can express the rules of a given bidding system. This way, it will be easier to compare different bidding systems against each other, and also I really like making little languages.
About the bidding language
I am going to make this language an internal DSL in F#, and one of the goals is that it should be possible for people with knowledge of bridge but not programming to write their own bidding system in it, in order to run simulations with their favorite system. So I want to make it not too “programming languagy”.
Bidding system basics
The general idea of a bidding system is basically an ‘if statement’: if a certain condition holds, do a certain bid. A simple example in pseudo code is:
If nClubs >= 4 -> bid 1 Clubs
As I said before, the bidding system is not defined by the rules of the game, everyone can define their own system with their own rules on when to bid what. So, theoretically, you can define a bidding system where you always bid 1 of Spades on Wednesdays. That means the language need to be quite expressive 🙂
In practice of course, there are only a few conditions that lead to a bid, typically these three:
- The number of ‘points’ in your hand, where normally you evaluate your hand by giving points to honor cards: 4 points for every ace to 1 for Jack. This hand evaluation is used widely in many different systems, but again it is a choice you have.
- The number of cards in a certain suit, for example 4 Spades
- The ‘balancedness’ of your hands: i.e. if you have or do not have 0 (void) or 1 (singleton) of any suit
I could have studied a large number of different systems and made a list of all possible conditions, but lately I have figured out I am more of an example thinker, so I decided to start with implementing a well known bidding system: ACOL and co-evolve the bidding language.
It really took me a few attempts to come up with an elegant and simple system to express bids (and maybe it will even change after this post) I started as follows, just with a system that is a list of condition and bid pairs:
type BiddingSystem = list<Condition * Bid>
As prompted by the basic rules above, a condition can then be either having a min/max number of points or cards
type Condition = NCards of int * int * Suit | NPoints of int * int
Sometimes we want to combine more conditions, so we also support conjunction and disjunction of conditions:
| And of Condition * Condition | Or of Condition * Condition
Initially, I created conditions by using the constructor for And, for example for a simple rule from ACOL stating we bid 1 Spades in case of 12 points and 4 Spade cards:
let cond = And(NPoints (12,19, Spades), NCards (4,13,Spades))
But, this does not really look nice with all the brackets, which are unfortunately obligatory when using discriminators. Therefore I created a helper function. Initially I called then function “makeAnd” without really thinking, but of course what the function really means is that both should hold, so I called it ‘both’ later:
let both (p:Condition)(q: Condition):Condition = And (p,q)
I made similar helpers from NPoints (points) and NCards (cards). We can then write:
let cond = both (points 12 19) (cards 4 13 Spades)
The basics are there, but there are some things we cannot express now. More on that in a later edition!
The bidding function
So, how do we bid? That is pretty straight forward:
We first have to make a helper that calculates whether a hand matches with a given conditions:
let rec fits (cards:Hand) (c:Condition, b:Bid) = match c with | NCards (min, max, s) -> if cardsinHand(cards,s) > min && cardsinHand(cards,s) < max then Yes (b) else No | NPoints (min, max) -> if pointsinHand(cards) > min && pointsinHand(cards) < max then Yes (b) else No | And (p,q) -> if isYes(fits(cards) (p,b)) && isYes(fits (cards) (q,b)) then Yes (b) else No
Then, we can get a bid from the system by inspecting the first item in the list. If it fits, we return the bid, otherwise, we continue. Our fall back bid is ‘Pass’, if nothing matches anymore, we pass.
let rec getBid(cards:Hand, sys:BiddingSystem)= match sys with |  -> Pass | h :: t -> match fits cards (h) with | Yes (x) -> x | No -> getBid(cards, t)