About bdeal

The bdeal is a program that generates (randomly) card distributions that meets given requirements.

bdeal is developed by Piotr Beling and it is a part of the Bridge Calculator.

The main futures of the program are:
  • it is scriptable in the Lua,
  • it uses multiple threads to generates and filters distributions of cards,
  • it has built-in, fast double-dummy solver,
  • it has console-based, shell-script-friendly user interface.

Quick start examples

The chance to win 3NT

Suppose that your partner (S) opened 1NT (classical, 16-18 hcp) and you (N) have ♠K42 ♥QT2 ♦K98 ♣J653.

To check how many trick, in average, S can take in NT and what is a chance to win 3NT, call:

$ bdeal nt_script.lua

Where nt_script.lua is a name of file with Lua script from which bdeal will read all details. It may have the following content:

conf = {
   N="K42.QT2.K98.J653",    -- fix N hand
   num=100                  -- finish after accepting 100 deals
}

-- accept only the deals in which S has 1NT opening:
function filter()
    return S:nt(16, 18)
end

-- for each accepted deal, calculate number of tricks in NT by S:
function stats()
    local t = tricks(S, "NT")
    count("NT by S, number of tricks", t)
    count("chance to win 3NT by S", t >= 9)
end

See Lua scripts for details about writing scripts.

No one can make 7 tricks

This script accept only deals in which it is impossible to win any contract (after optimal defence):

conf = { num = 1 }  -- we want to find only one distribution

function filter()
    for i1, s in pairs({ 'c', 'd', 'h', 's', 'nt' }) do
        for i2, p in pairs({ 'n', 'e', 'w', 's' }) do
            if cantake(7, p, s) then
                return false
            end
        end
    end
    return true
end

Warning

It may take a long time (even few hours) before it accepts any distribution.

The best lead against 3NT

Supposed that N opened 1NT (15-18 hcp) and S bid 3NT (10-12 hcp, without major 4). E is on lead and has ♠K873 ♥J1076 ♦A87 ♣Q7.

This script measures the average numbers of tricks E-W can take and the chances to defeat the contract (3NT), after each lead:

conf = { num=2000, E="K873.J1076.A87.Q7" }

function filter()
    return N:nt(15, 18) and S:nt(10, 12) and S:spades() < 4 and S:hearts() < 4
end

function stats()
    for c in E:cards() do  -- for each card c in the hand E:
        local t = tricks(N, "NT", c) -- tricks to take by N-S
        count("E-W tricks after " .. tostring(c), 13 - t)
        count("chance to defeat after " .. tostring(c), t < 9)
    end
end

Invocation

bdeal is called like this:

$ bdeal [options] <file names...>

where <file names...> are the (zero or more) names of files with Lua scripts.

The bdeal has several options:

-r, --noremarks

Don’t print remarks provided by calls of remark function.

-s, --nostats

Don’t print statistics for individual deals.

-f <none|NESW|short|full|PBN>, --format <none|NESW|short|full|PBN>

Output format, one of: none (do not print), NESW, short (used by default), full, PBN.

-W <cards>, --west <cards>

Fixed cards in WEST hand.

<cards> should has a format like in examples:
“AKQJ.AK65.642.94”
(AKQJ of spades, AK65 of hearts, 642 of diamonds, 94 of clubs)
“63.6.9.AQ”
(only 6 cards are fixed, rest will be chosen randomly)
“Q7.8543..94”
(8 cards are fixed, 0 in diamonds)
”..K.”
(the king of diamonds + 12 random cards)
-S <cards>, --south <cards>

Fixed cards in SOUTH hand.

-E <cards>, --east <cards>

Fixed cards in EAST hand.

-N <cards>, --north <cards>

Fixed cards in NORTH hand.

-n <number of hands>, --num <number of hands>

Number of hands to deal (10 by default).

-j <number of threads>, --jobs <number of threads>

Number of threads to use (by default equals to number of available CPU cores).

If argument is non-positive, it will be increased by number of available CPU cores (0 to use all cores, -1 to use all cores except one).

-h, --help

Displays usage information and exits.

--, --ignore_rest

Ignores the rest of the labeled arguments following this flag.

--version

Displays version information and exits.

Lua scripts

About

Bdeal accepts scripts in Lua language.

The scripts can include (each element is optional):

If more than one script are given, they are read sequentially (from left to right), and each can overwrite abovementioned elements (i.e. if two scripts provide filter() function, the one from script given later will be used).

Configuration

Some configuration options can be put in Lua script file, in conf table or in conf function that returns a table, for instance:

-- N has king of diamond, W has AQ of spades and 9 of clubs, use 2 threads:
conf = { N="..K.", W="AQ...9", jobs=2 }

The following options (keys in the conf table) are allowed: N, E, S, W, format, num, jobs. See Invocation for description of the options.

Filtering

Script can contain filter function which describes deal constraints. The function can accept or refuse deal described by the variables N, E, S, W and should return:
  • true to accept the deal,
  • false to refuse it,
  • a number in range [0, 1], which will be interpreted as probability of accepting the deal.

Each of the N, E, S, W represents the sets of cards held by the respective hands.

Functions and operators described in next sections can be used to examine the properties of the hands.

If all given scripts do not include filter() function, all distributions are accepted.

Examples:
  • accept deals in which E has 12 or more honour points, and more than 2 points in diamonds:

    function filter()
        return E:hcp() >= 12 and E:D():hcp() > 2
    end
    
  • accept deals in which W opens 1 spade and should have 5 or more spades, and 11 or more honour points (but we know that W sometimes, with 10% probability, opens with 4 strong spades – but only if he has less than 5 hearts):

    function filter()
        if W:hcp() < 11 then    -- W does not have 11 points?
            return false        -- refuse
        end
        -- here, W has 11 or more points
        local s = W:spades()    -- number of spades
        if s >= 5 then  -- has 5 spades?
            return true -- accept
        end
        if s == 4 and W:S():hcp() >= 6 and W:hearts() < 5 then  -- 4 strong spades without 5 hearts?
            return 0.10     -- accept with 10% probability
        end
        return false    -- everything else is refused, this line can be omitted (if filter returns no value, the deal will be refused)
    end
    
  • see also Quick start examples

Statistics

Script can contain stats function which is called for each deal accepted by filter function. The stats function also has access to N, E, S and W variables.

Typically, this function calls one or more time count function, which can collect series of numbers and calculates some statistics (average and others).

Examples:
  • checking how many spades and honour points does W have in average:

    function stats()
        count("spades in W's hand", W:spades())
        count("honour points in W's hand", W:hcp())
    end
    
  • see also Quick start examples

Cards set operations (operators)

Let s1 and s2 be sets of cards. The following operators are available:

s1 + s2
Sum of the sets, for example: E:S() + E:H() represents E majors.
s1 * s2
Product of the sets, for example: W * C.new("AS AH") represents W major aces.
s1 - s2
Difference of the sets, for example: N - N:S() represents N cards without spades.
-s1
Complement of the set, for example: -S represents all 52-13 cards not included in S’s hand.
#s1

Number of cards in s1, same as count(s1) or s1:count().

For example: #N:S() – a number of spades cards in N’s hand.

s1 ^ s2
xor, the set of cards which are in exactly one set s1 or s2.
s1 == s2
true only if sets s1 and s2 are equals.
s1 ~= s2
true only if sets s1 and s2 are not equals.
s1 <= s2
true only if set s1 is included in s2.
s1 < s2
true only if set s1 is included in but not equal to s2.

Functions which operates on sets of cards

Note

Each function, excluding new, can be called as C.function_name(arg1, arg2, ..., argN) or arg1:function_name(arg2, ..., argN).

For example C.hcp(N) is equal to N:hcp().

For sets of cards c, c1, c2, ..., a function f, and a string str:

C.new(str)

Construct a set of cards described by the string str (note that there is upper-case C here). Accepts many formats.

Example usage (all represent the same set of 10 cards): C.new("AK87.975..QT9"), C.new("AK87 975 - QT9"), C.new("SAK87 H975 CQT9"), C.new("AK87s 975h QT9c")

count(c)

A number of cards in the given cards set c, same as #c.

spades(c)
Scount(c)

A number of spades cards in set of cards c.

hearts(c)
Hcount(c)

A number of hearts cards in set of cards c.

diamonds(c)
Dcount(c)

A number of diamonds cards in set of cards c.

clubs(c)
Ccount(c)

A number of clubs cards in set of cards c.

S(c)

Subset of c which includes only spades.

H(c)

Subset of c which includes only hearts.

D(c)

Subset of c which includes only diamonds.

C(c)

Subset of c which includes only clubs.

get(c, arg)

If arg is a string, return the subset of c which includes only cards of suit pointed by arg (which can equal to “S”, “H”, “D” or “C”).

If arg is a positive integer, return arg-th lowest card from c. Cards are numbered from 0 to #c-1, clubs have number from 0 to #c:C()-1, diamonds from #c:C() to #c:C()+#c:D()-1, and so on. If arg is out of range, an empty set is returned.

If arg is a negative integer, return #c+arg lowest (-arg highest) card from c.

cards(c)

Iterator over cards included in c. Each card is represented as a one-element set of cards.

Example:

for x in N:cards() do   -- for each card x handled by N:
        -- do something with card x
end
hcp(c)
miltons(c)

Honour points (4 for Ace, 3 for King, 2 for Queen, 1 for Jack) in the given cards set c.

controls(c)

Controls (2 for Ace, 1 for King) in the given cards set c.

points(c, wAce, wKing, ...)

Calculate wAce * Aces + wKing * Kings + ..., where Aces, Kings, ... are the numbers of aces, kings, ... in c.

Examples:
  • points(N, 4, 3, 2, 1) equals to hcp(N),
  • points(E, 1, 1, 1, 1) equals to the number of figures owned by E.
balanced(c)

Logic value which is true only if c is balanced.

semiBalanced(c)

Logic value which is true only if c includes minimum 2 cards in all suits, maximum 5 cards in each major and maximum 6 cards in each minor.

union(c1, c2, ...)

Union of the sets, same as c1 + c2 + ...

product(c1, c2, ...)

Product of the sets, same as c1 * c2 * ...

complement(c)

Complement of the set c, same as -c.

xor(c1, c2, ...)

Same as c1 ^ c2 ^ ...

shape(c)

Return 4 numbers: spades(c), hearts(c), diamonds(c), clubs(c) (in the given order).

shape(c, f)

Calculate and return f(spades(c), hearts(c), diamonds(c), clubs(c)).

For example shape(S, function(s, h, d, c) return s == 5 and h == 5; end) gives true only if S has 5-5 in majors.

pattern(c)

Return 4 numbers: spades(c), hearts(c), diamonds(c), clubs(c) (in non-increasing order, from highest to lowest).

pattern(c, f)

Return f(c1, c2, c3, c4), where c1, c2, c3, c4 = pattern(c).

For example pattern(S, function(a, b, c, d) return a == 5 and b == 5; end) gives true only if S has any 5-5.

hcp_in_range(c, from, to)

Return true if hcp(c) is in the range [from, to], and false if it is not.

nt(c, from, to)

Calculate logic value which is true only if c is balanced and hcp(c) is in the range [from, to].

Tip

Most of the commands can be called with more than one argument and than they return more than one result (one per argument). In such case command(a, b, ...) is equal to command(a), command(b), .... For example you can write:EpS, EpH = C.hcp(E:S(), E:H()) to obtain number of E honour points in spades and hearts.

Functions useful for doing statistical research

count(str, n1, n2, ...)

Add to the counter named str values n1, n2, ...

Each value (n1, n2, ...) can be a number, a boolean (1 is added if the value is true and 0 when it is false) or nil (then value is ignored).

Example:

count("number of spades in E's hand", E:spades())
remark(arg1, arg2, ...)

Attach a remark to the deal considered. The remark consists with concatenation of arguments, which are converted to strings. Remarks are printed next to each deal.

Example:

remark("E can take ", tricks(E, "NT"), " tricks in NT.")
note(arg1, arg2, ...)

Similar to remark(arg1, arg2, ...) but puts spaces between each pair of arguments.

Example:

note("E can take", tricks(E, "NT"), "tricks in NT.")

Functions that use double dummy solver

tricks(declarer, game[, cmds])

Return the number of tricks that declarer can take when a trump suit is given by game, and a beginning of play is described by cmds. If game cannot begin from cmds, return nil.

Parameters:
  • declarer – one of N (or 'N'), E (or 'E'), S (or 'S'), W (or 'W'), lower cases are also allowed
  • game – shows type of the game, is one of: 'NT' (no trump), 'S' (spades), 'H' (hearts), 'D' (diamonds), 'C' (clubs), lower cases are also allowed.
  • cmds – (optional) fixes beginning of the play; if it is not possible, tricks returns nil
Return type:

number or nil (if the game cannot begin from cmds)

Example:

tricks(N, "NT", "AD x QD")

It returns number of tricks that can be taken by N in no-trump game, after ace of diamond lead, discarding the smallest diamond by the dummy and queen of diamond by the right hand opponent. nil will be returned if: N has no ace of diamond, the dummy has no diamonds at all, or RHO has no queen of diamond.

Since count ignores nil, it is save to write:

count("NT by N after AD x QD", tricks(N, "NT", "AD x QD"))
cantake(target, declarer, game[, cmds])

Check if declarer can take target tricks when a trump suit is given by game, and a beginning of play is described by cmds.

Parameters:
  • target – number of tricks to take
  • declarer – one of N (or 'N'), E (or 'E'), S (or 'S'), W (or 'W'), lower cases are also allowed
  • game – shows type of the game, is one of: 'NT' (no trump), 'S' (spades), 'H' (hearts), 'D' (diamonds), 'C' (clubs), lower cases are also allowed.
  • cmds – (optional) fixes beginning of the play, if it is not possible, cantake returns nil
Return type:

boolean or nil (if the game cannot begin from cmds)

Example:

count("chance to win 3NT by N", cantake(9, N, "NT"))

Indices and tables