Introducing RuleSets [Desiderius Part 7]

Blog

Introducing RuleSets [Desiderius Part 7]

Follow up of the Desi story, you might want to read the previous post first. In that post I outlined a few issues that I ran into when implementing a bidding system. In the first bid, you can just look at your cards and make a bid. But for the second bid (the answerer) you need to take your cards and the history of the other bids into account. So you end up with a very large tree of options for all possible cards plus hands, which I described in last week’s post.

What we would like is to create the answerer part of the system only after your partner has placed their opening bis, because then huge parts of the bidding tree can be pruned.

Attempt #4 ‘Factory methods’ to the rescue

For this delayed I introduction, I have introduced the concept of RuleSets

type RuleSet = (Condition * Bid) list

RuleSets model a list of Conditions and associated Bids. For example:

(both (points 15 17) (forAllSuits (cards 2 13)), Bid (1, SA)) :: 
(both (points 12 19) (cards 5 13 Spades)       , Bid (1, Spades)) :: ... etc

A bidding system then is a list of things in which you input a BidHistory (which is simply a list of bids) and results in a RuleSet.

type BiddingSystem = (BidHistory -> RuleSet) list

In other words: We create the RuleSets with something like a factory method that, here is the interesting step, takes the history of Bids into account.

The opening bid

The opening bid is simple, the create method has a parameter, but does not use it, and just outputs the basic opening rules as we had the earlier:

Acol1(hist:BidHistory):RuleSet =
  (both (points 15 17) (forAllSuits (cards 2 13)), Bid (1, SA)) :: 
  (both (points 12 19) (cards 5 13 Spades) , Bid (1, Spades)) ::
  (both (points 12 19) (cards 5 13 Clubs) , Bid (1, Clubs)) ::
  etc

The answerer’s bid though is created with a function that takes the history as input. The history is a list of bids, from the newest to the oldest, so our partner’s answer is the second in hist. We can get it out with some pattern matching.

let Acol2(hist:BidHistory):RuleSet =
  match hist with 
  | partnerBid :: x ->

We then continue and pick the tuple apart with another pattern match

match partnerBid with
 | Bid (partnerValue, partnerSuit) ->

What we can do now is create rules based on the bid of our partner, like this, where we bid 1 without trump when we have no support for partner’s color.

(both (points 6 9) (cards 0 3 partnerSuit) , Bid (1, SA))  //no trump support

We can even raise the bid of our partner with a little helper:

let raise (b:Bid) (i:int):Bid = 
 match b with 
 | Bid (v,s) -> Bid (v+1,s)
 //answer opening suit
 (both (points 6 9) (cards 4 13 partnerSuit) , (raise partnerBid 1))
 (both (points 10 11) (cards 4 13 partnerSuit) , (raise partnerBid 2))
 (both (points 12 15) (cards 4 13 partnerSuit) , (raise partnerBid 3))

Nice, heh?! A whole bidding system then is a list of those initialized RuleSets:

let createAcol:BiddingSystem = Acol1 :: [Acol2]

Getting the bids now is a little bit more involved than it was before as we have to create the RuleSet and then apply it. We callgetBid with 4 parameters:

getBid(westHand, Desi.createAcol, history, 0);
let rec getBid (cards:Hand) (sys:BiddingSystem) (history: BidHistory) (i:int):Bid =
 match sys with 
 | [] -> Pass //there is no ruleset given, we do not know what to do so Pass
 | l -> let f = List.nth sys i //get the ith ruleset, apply it to the history
 getBidRule cards (f history)

This method getBid creates the RuleSet based on the history, and then getBidRule tries all options within the RuleSet.

 let rec getBidRule (cards:Hand) (r:RuleSet):Bid =
 match r with
 | [] -> Pass // no more rules to apply, Pass
 | ((c,b) :: t) -> match fits cards c with
 | true -> b //the condition matches, so bid this bid 
 | false -> getBidRule cards (t) //try the remaining rules

Tada! Now I just need to type up the rest of ACOL. And implement and playing engine. And a bid system evaluator 🙂 Stay tuned!

Back To Top