rec.puzzles Archive

Are there any programs for solving double-dummy Bridge?

competition/games/bridge

Problem

Are there any programs for solving double-dummy Bridge?

Show solution
I'll enclose my Double-Dummy solver for bridge.  I like this program
because it contains a wildly unstructured "goto" -- which I claim is the
most readable way to write the program.
        Included are two test problems for the bridge-solver: a 6-card
ending and a complete 13-card position.  The former should be very fast;
the latter about 20 minutes on Sparcstation-2.  Each is *very*
challenging for humans.

Regards, James

=============== clip; chmod +x; execute =============
#!/bin/sh
cat > bridge.c << 'EOF'
/*
 * DOUBLE_DUMMY
 * Copyright (c) 1990 by
 *        James D. Allen
 *        19785 Stanton Ave
 *        Castro Valley, CA
 * Permission granted to use for any non-commercial purpose
 *        as long as this notice is not removed.
 *
 * This program will solve double-dummy bridge problems.
 * The algorithm is trivial: brute-force alpha-beta search (also known
 *        as "backtracking").  The alpha-beta is trivial since we do not
 *        consider overtricks or extra undertricks.
 * The control flow is neat; this is a rare exception where software is
 *        more readable with a "goto".  (Although I've tersified this to
 *        the point where it is perhaps unreadable anyway :-)
 */

#define        NUMP        4        /* The Players:  N, E, S, W */
#define                NORTH        0
#define                IS_CONT(x)        (!((x) & 1))        /* Is x on N/S team? */
#define                LHO(x)                (((x) + 1) % NUMP)
#define                RHO(x)                (((x) + NUMP - 1) % NUMP)
char        *Playername[] = { "North", "East", "South", "West" };

#define        NUMS        4        /* The Suits:   S, H, D, C */
char        Suitname[] = "SHDC";
char        *Fullname[] = { "Spades\t", "Hearts\t", "Diamonds", "Clubs\t" };

/*
 * Rank indices are 2 (Ace), 3 (King), ... 14 (Deuce)
 * There is also a CARD Index which can be converted to from Rank and Suit.
 */
#define        CARD(Suit, Rank)        (((Suit) << 4) | (Rank))
#define        SUIT(Card)                ((Card) >> 4)
#define        RANK(Card)                ((Card) & 0xf)
char        Rankname[] = "??AKQJT98765432";
#define        INDEX(s, c)        ((char *)index(s, c) - (s))

/* A "SuitSet" is used to cope with more than one card at once: */
typedef        unsigned short SuitSet;
#define        MASK(Card)                (1 << RANK(Card))
#define        REMOVE(Set, Card)        ((Set) &= ~(MASK(Card)))

/* And a CardSet copes with one SuitSet for each suit: */
typedef struct cards {
        SuitSet cc[NUMS];
} CardSet;

/* Everything we wish to remember about a trick: */
struct status {
        CardSet st_hold[NUMP];        /* The players' holdings */
        CardSet st_lgl[NUMP];        /* The players' remaining legal plays */
        short        st_play[NUMP];        /* What the players played */
        SuitSet st_trick;        /* Led-suit Cards eligible to win trick */
        SuitSet st_trump;        /* Trump Cards eligible to win trick */
        short        st_leader;        /* Who led to the trick */
        short        st_suitled;        /* Which suit was led */
}
Status[14]; /* Status of 13 tricks and a red zone" */
#define        Hold        Statp->st_hold
#define        Resid        (Statp+1)->st_hold
#define        Lgl        Statp->st_lgl
#define        Play        Statp->st_play
#define        Trick        Statp->st_trick
#define        Trtrick        Statp->st_trump
#define        Leader        Statp->st_leader
#define        Suitled        Statp->st_suitled

/* Pick a card from the set and return its index */
pick(set)
        SuitSet set;
{
        return set & 0xff ? set &  1 ? 0 : set &  2 ? 1 : set &  4 ? 2
                          : set &  8 ? 3 : set & 16 ? 4 : set & 32 ? 5
                          : set & 64 ? 6 : 7 : pick(set >> 8) + 8;
}

#define        highcard(set)        pick(set) /* Pick happens to return the best card */

main()
{
        register struct status *Statp = Status;        /* Point to current status */
        int        tnum;        /* trick number */
        int        won;        /* Total tricks won by North/South */
        int        nc;        /* cards on trick */
        int        ohsize;        /* original size of hands */
        int        mask;
        int        trump;
        int        player;        /* player */
        int        pwin;        /* player who won trick */
        int        suit;        /* suit to play */
        int        wincard; /* card which won the trick */
        int        need;        /* Total tricks needed by North/South */
        int        cardx;        /* Index of a card under consideration */
        int        success; /* Was the trick or contract won by North/South ? */
        int        last_t;        /* Decisive trick number */
        char        asciicard[60];
        SuitSet inplay;        /* cards still in play for suit */
        SuitSet pr_set;        /* Temp for printing */

        /* Read in the problem */
        printf("Enter trump suit (0-S,1-H,2-D,3-C,4-NT): ");
        scanf("%d", &trump);
        printf("Enter how many tricks remain to be played: ");
        scanf("%d", &ohsize);
        printf("Enter how many tricks North/South need to win: ");
        scanf("%d", &need);
        printf("Enter who is on lead now (0=North,etc.): ");
        scanf("%d", &pwin);
        printf("Enter the %d cards beginning with North:\n", NUMP * ohsize);
        for (player = NORTH; player < NUMP; player++) {
                for (tnum = 0; tnum < ohsize; tnum++) {
                        scanf("%s", asciicard);
                        cardx = CARD(INDEX(Suitname, asciicard[1]),
                                        INDEX(Rankname, asciicard[0]));
                        Hold[player].cc[SUIT(cardx)] |= MASK(cardx);
                }
        }

        /* Handle the opening lead */
        printf("Enter the directed opening lead or XX if none:\n");
        Lgl[pwin] = Hold[pwin];
        scanf("%s", asciicard);
        if (asciicard[0] == 'X') {
                strcpy(asciicard, "anything");
        } else {
                cardx = CARD(INDEX(Suitname, asciicard[1]),
                                INDEX(Rankname, asciicard[0]));
                for (suit = 0; suit < NUMS; suit++)
                        if (suit != SUIT(cardx))
                                Lgl[pwin].cc[suit] = 0;
                        else if (!(Lgl[pwin].cc[suit] &= MASK(cardx))) {
                                printf("He can't lead card he doesn't have\n");
                                exit(1);
                        }
        }

        /* Print the problem */
        for (player = NORTH; player < NUMP; player++) {
                printf("\n---- %s Hand ----:\n", Playername[player]);
                for (suit = 0; suit < NUMS; suit++) {
                        printf("\t%s\t", Fullname[suit]);
                        for (pr_set = Hold[player].cc[suit]; pr_set;
                                        REMOVE(pr_set, pick(pr_set)))
                                printf("%c ", Rankname[RANK(pick(pr_set))]);
                        printf("\n");
                }
        }
        printf("\n%s%s Trumps; %s leads %s; N-S want %d tricks; E-W want %d\n",
                trump < NUMS ? Fullname[trump] : "",
                trump < NUMS ? " are" : "No",
                Playername[pwin], asciicard, need, ohsize + 1 - need);

      /* Loop to play tricks forward until the outcome is conclusive */
        for (tnum = won = success = 0;
                        success ? ++won < need : won + ohsize >= need + tnum;
                        tnum++, Statp++, success = IS_CONT(pwin)) {
                for (nc = 0, player = Leader = pwin; nc < NUMP;
                                        nc++, player = LHO(player)) {
                      /* Generate legal plays except opening lead */
                        if (nc || tnum)
                                Lgl[player] = Hold[player];
                      /* Must follow suit unless void */
                        if (nc && Lgl[player].cc[Suitled])
                                for (suit = 0; suit < NUMS; suit++)
                                        if (suit != Suitled)
                                                Lgl[player].cc[suit] = 0;
                        goto choose_suit; /* this goto is easily eliminated */
                      /* Comes back right away after choosing "suit"  */
                        make_play:
                        Play[player] = cardx =
                                CARD(suit, pick(Lgl[player].cc[suit]));
                        if (nc == 0) {
                                Suitled = suit;
                                Trick = Trtrick = 0;
                        }
                      /* Set the play into "Trick" if it is win-eligible */
                        if (suit == Suitled)
                                Trick |= MASK(cardx);
                        if (suit == trump)
                                Trtrick |= MASK(cardx);

                      /* Remove card played from player's holding */
                        Resid[player] = Hold[player];
                        REMOVE(Resid[player].cc[suit], cardx);
                }

              /* Finish processing the trick ... who won? */
                if (Trtrick)
                        wincard = CARD(trump, highcard(Trtrick));
                else
                        wincard = CARD(Suitled, highcard(Trick));
                for (pwin = 0; Play[pwin] != wincard; pwin++)
                        ;
        }

      /* Loop to back up and let the players try alternatives */
        for (last_t = tnum--, Statp--; tnum >= 0; tnum--, Statp--) {
                won -= IS_CONT(pwin);
                pwin = Leader;
                for (player = RHO(Leader), nc = NUMP-1; nc >= 0;
                                        player = RHO(player), nc--) {
                      /* What was the play? */
                        cardx = Play[player];
                        suit = SUIT(cardx);
                      /* Retract the played card */
                        if (suit == Suitled)
                                REMOVE(Trick, cardx);
                        if (suit == trump)
                                REMOVE(Trtrick, cardx);
                      /* We also want to remove any redundant adjacent plays */
                        inplay =  Hold[0].cc[suit] | Hold[1].cc[suit]
                                | Hold[2].cc[suit] | Hold[3].cc[suit];
                        for (mask = MASK(cardx); mask <= 0x8000; mask <<= 1)
                                if (Lgl[player].cc[suit] & mask)
                                        Lgl[player].cc[suit] &= ~mask;
                                else if (inplay & mask)
                                        break;
                      /* If the card was played by a loser, try again */
                        if (success ? !(IS_CONT(player)) : IS_CONT(player)) {
                                choose_suit:
                              /* Pick a suit if any untried plays remain */
                                for (suit = 0; suit < NUMS; suit++)
                                        if (Lgl[player].cc[suit])
                                                /* This goto is really nice!! */
                                                goto make_play;
                        }
                }
        }

        /*
         * We're done.  We know the answer.
         * We can't remember all the variations; fortunately the
         *  succeeders played correctly in the last variation examined,
         * so we'll just print it.
         */
        printf("Contract %s, for example:\n",
                        success ? "made" : "defeated");
        for (tnum = 0, Statp = Status; tnum < last_t; tnum++, Statp++) {
                printf("Trick %d:", tnum + 1);
                for (player = 0; player < Leader; player++)
                        printf("\t");
                for (nc = 0; nc < NUMP; nc++, player = LHO(player))
                        printf("\t%c of %c",
                                Rankname[RANK(Play[player])],
                                Suitname[SUIT(Play[player])]);
                printf("\n");
        }
        return 0;
}
EOF
cc -O -o bridge bridge.c
bridge << EOF
4 6 5 2
AS JS 4S QD 8D 2C
KS QS 9H 8H AD 2D
AH 2H KD 9D 7D AC
TS 3S 2S TH TD TC
XX
EOF
bridge << EOF
1 13 13 3
3C                  3H 2H                          AD KD 2D        AS KS 7S 6S 5S 4S 3S
8C 7C 6C 5C 4C    QH TH 8H 7H                  8D 7D 6D        2S
AC QC JC 9C          AH KH JH 9H 6H 5H          5D 4D 3D
KC TC 2C          4H                          QD JD TD 9D        QS JS TS 9S 8S
QS
EOF