Bridge double dummy solver
Find a file
2026-06-09 17:07:13 +02:00
build/libs touch jar 2026-02-13 15:00:50 +01:00
gradle/wrapper updated README and added pre-built jar 2026-06-09 17:06:19 +02:00
src updated README and added pre-built jar 2026-06-09 17:06:19 +02:00
.gitignore initial commit 2026-02-13 14:33:17 +01:00
build.gradle updated README and added pre-built jar 2026-06-09 17:06:19 +02:00
CLAUDE.md initial commit 2026-02-13 14:33:17 +01:00
gradlew updated README and added pre-built jar 2026-06-09 17:06:19 +02:00
gradlew.bat updated README and added pre-built jar 2026-06-09 17:06:19 +02:00
Opening1M.lin initial commit 2026-02-13 14:33:17 +01:00
README.md updated README and added pre-built jar 2026-06-09 17:06:19 +02:00
settings.gradle initial commit 2026-02-13 14:33:17 +01:00
test.lin add test file 2026-06-09 17:07:13 +02:00

BridgeAnalyzer

A Java double-dummy analyzer for the card game of bridge. Given a complete deal (all 52 cards distributed among North, East, South and West), it computes the optimal number of tricks each side can take for every trump suit and every declarer — assuming perfect play by all four hands.

The result is the classic double-dummy table: for each trump strain (S, H, D, C, NT) and each declarer (N, E, S, W) it reports how many tricks the declaring side can take. When North and South get the same result the row is effectively the same, but East and West may differ depending on who is declarer — e.g. East can take 8 tricks in no-trump while West only takes 7.

Requirements

  • JDK 17 or later (the code uses records, switch expressions and Duration.toXxxPart()). It builds and runs fine on modern JDKs such as 21 or 25.
  • No other dependencies — everything is fetched by the Gradle wrapper.

Building

The project uses Gradle 9.2.1 via the included wrapper, so you do not need a local Gradle install.

# Compile and run the tests
./gradlew build

# Compile only
./gradlew compileJava

# Run the test suite
./gradlew test

# Run a single test
./gradlew test --tests "com.hoddmimes.bridgeanalyzer.SolverTest.testMethodName"

# Remove build output
./gradlew clean

On Windows use gradlew.bat instead of ./gradlew.

Running

The main class is com.hoddmimes.bridgeanalyzer.cli.Main.

Via the Gradle application plugin

Pass program arguments with --args:

# Analyze a freshly generated random deal
./gradlew run

# Analyze board 1 from a LIN file, NT only, with West as declarer
./gradlew run --args="--file=test.lin --board=1 --trump=NT --declarer=W"

Via a runnable JAR

The jar task produces an executable JAR (the manifest's Main-Class is set):

./gradlew jar
java -jar build/libs/BridgeAnalyzer-1.0-SNAPSHOT.jar --file=test.lin

Via a self-contained "fat" JAR

The fatJar task bundles the compiled classes together with everything on the runtime classpath into a single executable JAR (classifier -all). The project currently has no runtime dependencies, so today this contains the same classes as the plain jar, but it will automatically include any dependency you add later:

./gradlew fatJar
java -jar build/libs/BridgeAnalyzer-1.0-SNAPSHOT-all.jar --file=test.lin

Program parameters

All options are passed as --name=value flags. Unknown flags are ignored; any flag given without =value is treated as a boolean true. If no input file is given, a single random deal is generated and analyzed.

Option Values Default Description
--file=PATH path to a .lin file (none) Read one or more deals from a LIN file. If omitted, a random deal is generated.
--board=N integer (all boards) Only analyze the board with this number. Requires --file. Exits with an error if the board is not found.
--trump=STRAIN S, H, D, C, NT (all strains) Restrict analysis to a single trump strain instead of all five.
--declarer=DIR N, E, S, W (all four) Restrict analysis to a single declarer instead of all four.
--solver=NAME alphabeta, bruteforce alphabeta Choose the search engine. alphabeta uses alpha-beta pruning (fast); bruteforce searches exhaustively (slow, mainly for verification).

Examples

# Full double-dummy table for a random deal (all strains, all declarers)
./gradlew run

# Every board in a file, all strains/declarers, alpha-beta solver
./gradlew run --args="--file=test.lin"

# Just board 3, hearts as trump
./gradlew run --args="--file=test.lin --board=3 --trump=H"

# Verify the fast solver against the brute-force solver on one board
./gradlew run --args="--file=test.lin --board=1 --solver=bruteforce"

Input format (LIN)

Deals are read from LIN files, the format used by sites such as Bridge Base Online. Each non-empty line is a pipe-separated list of tag|value pairs. The parser understands the following tags:

  • qx — board identifier (e.g. o1, c3); the trailing number sets the board number
  • md — the deal itself: a leading dealer digit (1=S, 2=W, 3=N, 4=E) followed by three comma-separated hands (South, West, North); East is inferred as the remaining cards
  • ah — board name / title
  • sv — vulnerability

Other tags are ignored. A sample file, test.lin, is included in the repository:

qx|o1|md|3SA74HKJ975DT8C842,ST853HT4DJ9CAKQJ5,SKQJ9HQ862DQ54C97|rh||ah|Bricka 1|sv|0|pg||

Output

For each analyzed deal the program prints:

  1. The board number and name.
  2. The four hands.
  3. A table with one column per trump strain and one row per declarer, showing the number of tricks the declaring side can take with optimal play.
  4. The wall-clock solve time for that board.

Project layout

src/main/java/com/hoddmimes/bridgeanalyzer/
  model/    Card, Rank, Suit, Direction, Trump
  game/     Hand, Deal, Trick, GameState
  solver/   Solver, SolverFactory, AlphaBetaSolver, BruteForceSolver, TranspositionTable
  cli/      Main, LinParser, DealGenerator
src/test/java/...   JUnit 5 tests