Posts Introducing Bridgebots Part 3: LIN files and JSON
Post
Cancel

Introducing Bridgebots Part 3: LIN files and JSON

In my last post I covered how Bridgebots records the auction and the play of the hand, and how it can be used to import and analyze results from PBN files. Today we will expand that functionality to LIN and JSON files, and your long-winded introduction to Bridgebots will be complete!

LINsanity

Bridge Base Online(BBO) is a popular site for playing bridge both competitively and casually. One of its greatest features is the history it saves for each user and tournament. Here are some helpful places to find these:

BBO provides an interactive Movie viewing option or a Lin file for download. I don’t actually know what LIN stands for (if you do let me know!). It contains all the juicy details we need to record the play of a bridge hand, but it takes a little bit of puzzling to figure out what each part means. Here’s an example:

1
pn|SBlumlein,Forrest_,grychn48,granola357|st||md|2S9HJ872DQT64CQJ43,SAK2HA95DAJ875C92,SQ765HKQ64D32CAK6,|rh||ah|Board 12|sv|n|mb|1N|an|15-17|mb|p|mb|2H|an|spades |mb|p|mb|2S|mb|p|mb|p|mb|p|pg||pc|CA|pc|C5|pc|C4|pc|C2|pg||pc|CK|pc|C7|pc|C3|pc|C9|pg||pc|C6|pc|CT|pc|CJ|pc|S2|pg||pc|SA|pc|S5|pc|S3|pc|S9|pg||pc|SK|pc|S6|pc|S4|pc|D4|pg||pc|D5|pc|D3|pc|DK|pc|D6|pg||pc|D9|pc|DQ|pc|DA|pc|D2|pg||pc|DJ|pc|S7|pc|S8|pc|DT|pg||pc|H3|pc|H2|pc|HA|pc|H4|pg||pc|D8|pc|SQ|pc|HT|pc|H7|pg||pc|HK|pc|ST|pc|H8|pc|H5|pg||pc|SJ|pc|HJ|pc|H9|pc|H6|pg||pc|C8|pc|CQ|pc|D7|pc|HQ|pg||

BBO movie for reference

Parsing it visually we can see the player names, the hands (well three of them), the board number, the vulnerability, the auction, and the play of the hand. LIN files suffer from some of the same issues that afflict PBNs: they are fairly human-readable but they are non-trivial to consume programmatically. Luckily Bridgebots can help us out!

1
2
3
4
5
6
from pathlib import Path
from bridgebots import parse_multi_lin, parse_single_lin

lin_path = Path("path/to/above/example.lin")
results = parse_single_lin(lin_path)
print(results)
1
2
3
4
5
6
7
[DealRecord(deal=Deal(
	dealer=Direction.WEST, ns_vulnerable=True, ew_vulnerable=False
	North: PlayerHand(SQ S7 S6 S5 | HK HQ H6 H4 | D3 D2 | CA CK C6)
	South: PlayerHand(S9 | HJ H8 H7 H2 | DQ DT D6 D4 | CQ CJ C4 C3)
	East: PlayerHand(SJ ST S8 S4 S3 | HT H3 | DK D9 | CT C8 C7 C5)
	West: PlayerHand(SA SK S2 | HA H9 H5 | DA DJ D8 D7 D5 | C9 C2)
), board_records=[BoardRecord(bidding_record=['1NT', 'PASS', '2H', 'PASS', '2S', 'PASS', 'PASS', 'PASS'], raw_bidding_record=['1N', 'p', '2H', 'p', '2S', 'p', 'p', 'p'], play_record=[CA, C5, C4, C2, CK, C7, C3, C9, C6, CT, CJ, S2, SA, S5, S3, S9, SK, S6, S4, D4, D5, D3, DK, D6, D9, DQ, DA, D2, DJ, S7, S8, DT, H3, H2, HA, H4, D8, SQ, HT, H7, HK, ST, H8, H5, SJ, HJ, H9, H6, C8, CQ, D7, HQ], declarer=WEST, contract=Contract(level=2, suit=SPADES, doubled=0), tricks=9, scoring=None, names={SOUTH: 'SBlumlein', WEST: 'Forrest_', NORTH: 'grychn48', EAST: 'granola357'}, date=None, event=None, bidding_metadata=[BidMetadata(bid_index=0, bid='1NT', alerted=False, explanation='15-17'), BidMetadata(bid_index=2, bid='2H', alerted=False, explanation='spades ')], commentary=None, score=140)])]

As we can see, Bridgebots successfully parsed the deal, bidding, play, and even included the alerts and explanations!

Bridgebots can also handle multi-board LINs produced by teams matches. For example this segment of the 2010 USBF semi-finals can be downloaded here.

1
2
3
lin_multi_path = Path("path/to/sample_multi.lin")
results = parse_multi_lin(lin_multi_path)
print(len(results))
1
15
1
print(results[0].board_records[0])
1
BoardRecord(bidding_record=['1H', 'PASS', '3C', 'PASS', '4H', 'PASS', 'PASS', 'PASS'], raw_bidding_record=['1H', 'p', '3C!', 'p', '4H', 'p', 'p', 'p'], play_record=[C2, C3, CA, CJ, D7, D5, DA, D4, D6, DQ, D9, D3, C9, C4, CK, C5, H2, H9, HA, H5], declarer=EAST, contract=Contract(level=4, suit=HEARTS, doubled=0), tricks=10, scoring=None, names={SOUTH: 'Meckstroth', WEST: 'Levin', NORTH: 'Rodwell', EAST: 'Weinstein'}, date=None, event=None, bidding_metadata=[BidMetadata(bid_index=2, bid='3C', alerted=True, explanation='jacoby 2N')], commentary=[Commentary(bid_index=1, play_index=None, comment='caitlin: Hi all'), Commentary(bid_index=1, play_index=None, comment='vugraphzfk: Hi everyone and welcome back, this is the last segment for today'), Commentary(bid_index=1, play_index=None, comment='ahollan1: same boards played in both Semi-Final matches   use http://www.bbotv.com/vugraph/   to spy on all tables'), Commentary(bid_index=None, play_index=3, comment='ahollan1: ACBL Convention Cards for all Fleisher Team  http://usbf.org/index.php?option=com_content&task=view&id=659&Itemid=284'), Commentary(bid_index=None, play_index=3, comment='vdoubleu: Good ---1 quick hand down'), Commentary(bid_index=None, play_index=3, comment='ahollan1: ACBL CC and WBF CC from 2009 Bermuda Bowl for Nickell team at http://usbf.org/index.php?option=com_content&task=view&id=655&Itemid=284'), Commentary(bid_index=None, play_index=11, comment='vugraphzfk: you jinxed it, Val'), Commentary(bid_index=None, play_index=11, comment='vdoubleu: I noticed:-)')], score=420)

Bridgebots gives us all its usual goodness and also includes the expert commentary for your (and your computer’s) viewing pleasure.

Finally, Bridgebots also includes the ability to create LIN strings and URLs from DealRecords. Let’s load up our example Cavendish Pairs PBN from Part 2 and take a look at it with the BBO interactive viewer!

1
2
3
4
5
6
from pathlib import Path
from bridgebots import build_lin_str, build_lin_url, parse_pbn

pbn_path = Path("path/to/cavendish.pbn")
results = parse_pbn(pbn_path)
print(build_lin_str(results[0].deal, results[0].board_records[0]))
1
pn|,,,|st||md|4SA954HAT98DQ8C875,S63HK3DK9532CJ963,ST82H62DT764CKQ42,SKQJ7HQJ754DAJCAT|sv|b|mb|1H|mb|p|mb|1S|an|0-4 !ss|mb|p|mb|2C!|mb|p|mb|2H|an|less than 8 points|mb|p|mb|2S|an|17+ with 4 !S|mb|p|mb|3N|mb|p|mb|p|mb|p|pg||pc|CQ|pc|CA|pc|C8|pc|C3|pg||pc|H4|pc|HT|pc|HK|pc|H6|pg||pc|H3|pc|H2|pc|HQ|pc|HA|pg||pc|C5|pc|C6|pc|CK|pc|CT|pg||pc|D4|pc|DJ|pc|DQ|pc|DK|pg||pc|CJ|pc|C2|pc|S7|pc|C7|pg||pc|C9|pc|C4|pc|H5|pc|S4|pg||pc|S6|mc|9|pg||
1
print(build_lin_url(results[0].deal, results[0].board_records[0]))
1
https://www.bridgebase.com/tools/handviewer.html?lin=pn%7C%2C%2C%2C%7Cst%7C%7Cmd%7C4SA954HAT98DQ8C875%2CS63HK3DK9532CJ963%2CST82H62DT764CKQ42%2CSKQJ7HQJ754DAJCAT%7Csv%7Cb%7Cmb%7C1H%7Cmb%7Cp%7Cmb%7C1S%7Can%7C0-4+%21ss%7Cmb%7Cp%7Cmb%7C2C%21%7Cmb%7Cp%7Cmb%7C2H%7Can%7Cless+than+8+points%7Cmb%7Cp%7Cmb%7C2S%7Can%7C17%2B+with+4+%21S%7Cmb%7Cp%7Cmb%7C3N%7Cmb%7Cp%7Cmb%7Cp%7Cmb%7Cp%7Cpg%7C%7Cpc%7CCQ%7Cpc%7CCA%7Cpc%7CC8%7Cpc%7CC3%7Cpg%7C%7Cpc%7CH4%7Cpc%7CHT%7Cpc%7CHK%7Cpc%7CH6%7Cpg%7C%7Cpc%7CH3%7Cpc%7CH2%7Cpc%7CHQ%7Cpc%7CHA%7Cpg%7C%7Cpc%7CC5%7Cpc%7CC6%7Cpc%7CCK%7Cpc%7CCT%7Cpg%7C%7Cpc%7CD4%7Cpc%7CDJ%7Cpc%7CDQ%7Cpc%7CDK%7Cpg%7C%7Cpc%7CCJ%7Cpc%7CC2%7Cpc%7CS7%7Cpc%7CC7%7Cpg%7C%7Cpc%7CC9%7Cpc%7CC4%7Cpc%7CH5%7Cpc%7CS4%7Cpg%7C%7Cpc%7CS6%7Cmc%7C9%7Cpg%7C%7C

Click here to follow that link.

JSON

As we have seen, both PBNs and LINs require significant effort for programs to parse. I wanted the ability to export Bridgebots data in a format that would be easily consumable by other programs. To that end, Bridgebots includes Marshmallow schemas for serializing/deserializing its data to/from JSON. These may not be perfect for other programmers bridge needs, but my hope is they will be useful for consuming translated PBN and LIN records. Check out this example:

1
2
3
4
5
6
7
8
import json
from pathlib import Path
from bridgebots import DealRecordSchema, parse_single_lin

results = parse_single_lin(Path("path/to/sample.lin"))
deal_record_schema = DealRecordSchema(many=True)
deal_record_json = deal_record_schema.dump(results)
print(json.dumps(deal_record_json, indent=4))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
[
    {
        "deal": {
            "dealer": "W",
            "ns_vulnerable": true,
            "ew_vulnerable": false,
            "hands": {
                "S": [
                    "S9",
                    "HJ",
                    "H8",
                    "H7",
                    "H2",
                    "DQ",
                    "DT",
                    "D6",
                    "D4",
                    "CQ",
                    "CJ",
                    "C4",
                    "C3"
                ],
                "W": [
                    "SA",
                    "SK",
                    "S2",
                    "HA",
                    "H9",
                    "H5",
                    "DA",
                    "DJ",
                    "D8",
                    "D7",
                    "D5",
                    "C9",
                    "C2"
                ],
                "N": [
                    "SQ",
                    "S7",
                    "S6",
                    "S5",
                    "HK",
                    "HQ",
                    "H6",
                    "H4",
                    "D3",
                    "D2",
                    "CA",
                    "CK",
                    "C6"
                ],
                "E": [
                    "SJ",
                    "ST",
                    "S8",
                    "S4",
                    "S3",
                    "HT",
                    "H3",
                    "DK",
                    "D9",
                    "CT",
                    "C8",
                    "C7",
                    "C5"
                ]
            }
        },
        "board_records": [
            {
                "bidding_record": [
                    "1NT",
                    "PASS",
                    "2H",
                    "PASS",
                    "2S",
                    "PASS",
                    "PASS",
                    "PASS"
                ],
                "raw_bidding_record": [
                    "1N",
                    "p",
                    "2H",
                    "p",
                    "2S",
                    "p",
                    "p",
                    "p"
                ],
                "play_record": [
                    "CA",
                    "C5",
                    "C4",
                    "C2",
                    "CK",
                    "C7",
                    "C3",
                    "C9",
                    "C6",
                    "CT",
                    "CJ",
                    "S2",
                    "SA",
                    "S5",
                    "S3",
                    "S9",
                    "SK",
                    "S6",
                    "S4",
                    "D4",
                    "D5",
                    "D3",
                    "DK",
                    "D6",
                    "D9",
                    "DQ",
                    "DA",
                    "D2",
                    "DJ",
                    "S7",
                    "S8",
                    "DT",
                    "H3",
                    "H2",
                    "HA",
                    "H4",
                    "D8",
                    "SQ",
                    "HT",
                    "H7",
                    "HK",
                    "ST",
                    "H8",
                    "H5",
                    "SJ",
                    "HJ",
                    "H9",
                    "H6",
                    "C8",
                    "CQ",
                    "D7",
                    "HQ"
                ],
                "declarer": "W",
                "contract": "2S",
                "tricks": 9,
                "score": 140,
                "scoring": null,
                "names": {
                    "S": "SBlumlein",
                    "W": "Forrest_",
                    "N": "grychn48",
                    "E": "granola357"
                },
                "date": null,
                "event": null,
                "bidding_metadata": [
                    {
                        "bid_index": 0,
                        "bid": "1NT",
                        "alerted": false,
                        "explanation": "15-17"
                    },
                    {
                        "bid_index": 2,
                        "bid": "2H",
                        "alerted": false,
                        "explanation": "spades "
                    }
                ],
                "commentary": null
            }
        ]
    }
]
1
2
reloaded_deal_record = deal_record_schema.load(deal_record_json)
print(f"{(results == reloaded_deal_record)=}")
1
(results == reloaded_deal_record)=True

Looking ahead

It has been great walking you through Bridgebots - thank you so much for reading if you made it this far. I put many hours of effort into its current capabilities, and I am proud of that work, but I hope that this is just the beginning. Here are some possible projects for the future:

  • Ability to create PBNs and to read/write PBNv2 files
  • Integration with redeal, another awesome open-source python bridge library. This would provide simulations and access to DDS, a powerful open-source double-dummy solver.
  • Data exploration with python tools like pandas and matplotlib. I did a little exploration of some bridge data here.
  • Creating ML models to analyze and predict bidding and play. I hope to make this the subject of my next post!
  • A web-viewer for Bridgebots data

If you have ideas for potential applications of or improvements to Bridgebots, feel free to reach out or submit a pull request.

Hope to see you at the bridge table!

-Forrest

This post is licensed under CC BY 4.0 by the author.

Trending Tags