vincentlui commited on
Commit
de49ebf
1 Parent(s): 1175a09
Files changed (5) hide show
  1. app.py +6 -5
  2. dds_util.py +120 -0
  3. hand_record.py +139 -67
  4. libdds.so +3 -0
  5. pbn_util.py +101 -6
app.py CHANGED
@@ -36,7 +36,6 @@ css = ".output_img {display:block; margin-left: auto; margin-right: auto}"
36
  model = CardDetectionModel()
37
 
38
  def predict(image_path, top_hand_idx):
39
- print(top_hand_idx)
40
  start = timer()
41
  df = None
42
  try:
@@ -84,8 +83,8 @@ def save_file(df, cache_dir, files, board_no):
84
  files.append(file_path)
85
  return files, files, d
86
 
87
- def create_hand_record(files):
88
- file_path = create_hand_record_pdf(files)
89
  return file_path
90
 
91
  with gr.Blocks(css=custom_css) as demo:
@@ -144,12 +143,14 @@ with gr.Blocks(css=custom_css) as demo:
144
 
145
  with gr.Tab('Hand Record'):
146
  with gr.Row():
147
- with gr.Group():
148
  tab2_upload_file = gr.Files(interactive=True)
 
 
149
  tab2_submit_button = gr.Button('Create Hand Record',variant="primary")
150
  tab2_download_file = gr.File(interactive=False)
151
 
152
- tab2_submit_button.click(create_hand_record, tab2_upload_file, tab2_download_file)
153
 
154
  a2.add([a1,b1])
155
  a3.click(predict, [a1, a_top], [b1])
 
36
  model = CardDetectionModel()
37
 
38
  def predict(image_path, top_hand_idx):
 
39
  start = timer()
40
  df = None
41
  try:
 
83
  files.append(file_path)
84
  return files, files, d
85
 
86
+ def create_hand_record(files, event, site):
87
+ file_path = create_hand_record_pdf(files, event=event, site=site)
88
  return file_path
89
 
90
  with gr.Blocks(css=custom_css) as demo:
 
143
 
144
  with gr.Tab('Hand Record'):
145
  with gr.Row():
146
+ with gr.Column():
147
  tab2_upload_file = gr.Files(interactive=True)
148
+ tab2_event = gr.Textbox(max_lines=1, placeholder='Event name', label='Event')
149
+ tab2_site = gr.Textbox(max_lines=1, placeholder='Site name', label='Site')
150
  tab2_submit_button = gr.Button('Create Hand Record',variant="primary")
151
  tab2_download_file = gr.File(interactive=False)
152
 
153
+ tab2_submit_button.click(create_hand_record, [tab2_upload_file, tab2_event, tab2_site], tab2_download_file)
154
 
155
  a2.add([a1,b1])
156
  a3.click(predict, [a1, a_top], [b1])
dds_util.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ from ctypes import POINTER, Structure, byref, c_char, c_int, c_uint
3
+ import os
4
+ import sys
5
+
6
+
7
+ """
8
+ Adopted from Peter Ellington's PBNCreator https://github.com/Britwizard/PBNCreator
9
+ """
10
+ DDS_HANDS = 4
11
+ DDS_SUITS = 5
12
+ DDS_STRAINS = 5
13
+
14
+ dcardSuit = ["S", "H", "D", "C", "NT"]
15
+ convert_table1={'a':'10','b':'11','c':'12','d':'13'}
16
+ convert_table2={'10':'a','11':'b','12':'c','13':'d'}
17
+ vulnerability= ["None","NS","EW","All","NS","EW","All","None","EW","All","None","NS","All","None","NS","EW"]
18
+ dealerlist=["N","E","S","W"]
19
+ dd_dealer_point = ["N","S","E","W"]
20
+ dd_suit=["NT","S","H","D","C"]
21
+ dd_suit_wide=["NT"," S"," H"," D"," C"]
22
+
23
+
24
+ class ddTableDeal(Structure):
25
+ _fields_ = [("cards", c_uint * DDS_HANDS * DDS_SUITS)]
26
+
27
+ class ddTableResults(Structure):
28
+ # _fields_ = [("resTable", c_int * DDS_STRAINS * DDS_HANDS)]
29
+ _fields_ = [("resTable", c_int * DDS_HANDS * DDS_STRAINS)]
30
+
31
+ class ddTableDealPBN(Structure):
32
+ _fields_ = [("cards", c_char * 80)]
33
+
34
+ class ddTableResults(Structure):
35
+ # _fields_ = [("resTable", c_int * DDS_STRAINS * DDS_HANDS)]
36
+ _fields_ = [("resTable", c_int * DDS_HANDS * DDS_STRAINS)]
37
+
38
+ tableDealPBN = ddTableDealPBN()
39
+ table = ddTableResults()
40
+
41
+ dll_name = DLL = None
42
+ if os.name == "posix":
43
+ dll_name = "libdds.so"
44
+ DLL = ctypes.CDLL
45
+
46
+ if dll_name:
47
+ #dll_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),dll_name)
48
+ dll_path = os.path.join(os.getcwd(),dll_name)
49
+ #dll_path='C:\\Users\\peter\\Python\\PBNGenerator\\dds-64.dll'
50
+ if dll_name and os.path.exists(dll_path):
51
+ dll = DLL(dll_path)
52
+ dll.CalcDDtable.argtypes = [ddTableDeal, POINTER(ddTableResults)]
53
+ dll.ErrorMessage.argtypes = [c_int, POINTER(c_char)]
54
+
55
+ if os.name == "posix":
56
+ dll.SetMaxThreads(0)
57
+ def _check_dll(name):
58
+ return
59
+
60
+ else:
61
+ def _check_dll(name):
62
+ raise Exception(f"Unable to load DDS; {name} is not available")
63
+
64
+
65
+ def errorMessage(res):
66
+ msg = ctypes.create_string_buffer(80)
67
+ dll.ErrorMessage(res, msg)
68
+ result_len = ctypes.c_size_t(len(msg))
69
+ return msg[:result_len.value]
70
+
71
+ def calcDDtablePBN(tableDealPBN):
72
+ myTable = ctypes.pointer(table)
73
+ res = dll.CalcDDtablePBN(tableDealPBN, myTable)
74
+ if res != 1:
75
+ line = errorMessage(res)
76
+ raise Exception("DDS error: {}".format(line.decode("utf-8")))
77
+ return myTable
78
+
79
+ def get_ddstable(pbn):
80
+ tableDealPBN.cards = pbn
81
+ table = calcDDtablePBN(tableDealPBN)
82
+ all = { "N" : {}, "S" : {}, "E" : {}, "W" : {} }
83
+ # below doesn't work, why?
84
+ #all = dict.fromkeys(["N","S","E","W"], {})
85
+ # print(all)
86
+ for suit in range(0, DDS_SUITS):
87
+ all["N"][dcardSuit[suit]] = table.contents.resTable[suit][0]
88
+ all["S"][dcardSuit[suit]] = table.contents.resTable[suit][2]
89
+ all["E"][dcardSuit[suit]] = table.contents.resTable[suit][1]
90
+ all["W"][dcardSuit[suit]] = table.contents.resTable[suit][3]
91
+ return all
92
+
93
+ def get_dd_tricks(deal):
94
+ byte_board=deal.encode('utf-8') # ddstable requires the board description to be in byte format
95
+ dd_array = get_ddstable(byte_board)
96
+ double_dummy_tricks=''
97
+ for point in dd_array:
98
+ for suit in dd_suit:
99
+ temp = dd_array[point][suit]
100
+ value=str(temp)
101
+ if temp > 9:
102
+ value=convert_table2[value]
103
+ double_dummy_tricks = double_dummy_tricks + value
104
+ return(double_dummy_tricks)
105
+
106
+ """
107
+ End
108
+ """
109
+
110
+ DD_DIRECTIONS = ['N', 'S', 'E', 'W']
111
+ DD_SUITS = ['NT', 'S', 'H', 'D', 'C']
112
+ def get_result_table(pbn_deal_string):
113
+ dd_tricks_string = get_dd_tricks(pbn_deal_string)
114
+ s = '[OptimumResultTable "Declarer;Denomination\\2R;Result\\2R"]\n'
115
+ for i, char in enumerate(dd_tricks_string):
116
+ direction = DD_DIRECTIONS[i//5]
117
+ suit = DD_SUITS[i%5]
118
+ tricks = int(char, 16)
119
+ s += f'{direction} {suit} {tricks}\n'
120
+ return s
hand_record.py CHANGED
@@ -2,84 +2,156 @@ from fpdf import FPDF
2
  import tempfile
3
  import os
4
  import bridgebots
5
- from pbn_util import merge_pbn, parse_pbn
6
 
7
  DEALER_LIST = ['N', 'E', 'S', 'W']
8
  VULNERABILITY_LIST = ["None","NS","EW","All","NS","EW","All","None","EW","All","None","NS","All","None","NS","EW"]
9
  SUITS = [bridgebots.Suit.SPADES, bridgebots.Suit.HEARTS, bridgebots.Suit.DIAMONDS, bridgebots.Suit.CLUBS]
10
  SUIT_SYMBOLS = ['♠','♥','♦','♣']
11
 
12
- def create_hand_record_pdf(pbn_paths):
13
- filepath_merged_pbn = merge_pbn(pbn_paths)
14
- results = parse_pbn(filepath_merged_pbn)
15
- fd,fn = tempfile.mkstemp(".pdf")
16
- pdf = FPDF()
17
-
18
- pdf.add_page()
19
- pdf.add_font('times2', style='', fname='times.ttf')
20
- pdf.set_font("times2", "", 8)
21
- pdf.c_margin = 0.1
22
- table_config = {
23
- 'borders_layout':'NONE',
24
- 'col_widths':3,
25
- 'line_height':pdf.font_size + 0.5,
26
- 'align': 'L',
27
- 'text_align': 'L',
28
- 'first_row_as_headings': False,
29
- }
30
-
31
- start_x,start_y = 10,10
32
- table_size = 45, 48
33
- table_margin = 2
34
- for i, result in enumerate(results):
35
- deal = result[0]
36
- board_no = int(result[1][0]['Board'])
37
- page_i = i % 20
38
- if (i % 20 == 0) and (i != 0):
39
- pdf.add_page()
40
- row_idx = page_i // 4
41
- col_idx = page_i % 4
42
- x = start_x + (table_size[0] + 2 * table_margin + 1) * col_idx
43
- y = start_y + (table_size[1] + 2 * table_margin + 1) * row_idx
44
-
45
- top_left = x - table_margin, y - table_margin
46
- top_right = x + table_size[0] + table_margin, y - table_margin
47
- bottom_left = x - table_margin, y + table_size[1] + table_margin
48
- bottom_right = x + table_size[0] + table_margin, y + table_size[1] + table_margin
49
- pdf.set_xy(x,y)
50
-
51
- pdf.line(*top_left, *bottom_left)
52
- pdf.line(*top_left, * top_right)
53
- pdf.line(*top_right, *bottom_right)
54
- pdf.line(*bottom_left, *bottom_right)
55
-
56
- dealer = DEALER_LIST[(board_no-1) % 4]
57
- vul = VULNERABILITY_LIST[(board_no-1) % 16]
58
- with pdf.table(**table_config) as table:
59
- row = table.row()
60
- row.cell(f'{board_no}\n{dealer}/{vul}', colspan=6, rowspan=4, align='C', v_align='C')
61
- for i, (suit, values) in enumerate(zip(SUIT_SYMBOLS, get_values(deal, 'N'))):
62
- if i!=0:
63
- row = table.row()
64
- print_suit_values(pdf,row,suit,values)
65
 
66
- row = table.row()
67
- # row = table.row()
68
- for i, (suit, values1, values2) in enumerate(zip(SUIT_SYMBOLS, get_values(deal, 'W'), get_values(deal, 'E'))):
69
- row = table.row()
70
- print_suit_values(pdf, row, suit, values1)
71
- row.cell('',colspan=5)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
- print_suit_values(pdf, row, suit, values2)
 
74
 
75
- row = table.row()
76
- row = table.row()
77
- row.cell('', colspan=6, rowspan=4)
78
- for i, (suit, values) in enumerate(zip(SUIT_SYMBOLS, get_values(deal, 'S'))):
79
- if i!=0:
 
 
 
 
 
 
 
 
 
 
 
80
  row = table.row()
81
- print_suit_values(pdf, row, suit, values)
 
 
 
 
82
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  pdf.output(fn)
84
  return fn
85
 
 
2
  import tempfile
3
  import os
4
  import bridgebots
5
+ from pbn_util import merge_pbn, parse_pbn, parse_dds_table
6
 
7
  DEALER_LIST = ['N', 'E', 'S', 'W']
8
  VULNERABILITY_LIST = ["None","NS","EW","All","NS","EW","All","None","EW","All","None","NS","All","None","NS","EW"]
9
  SUITS = [bridgebots.Suit.SPADES, bridgebots.Suit.HEARTS, bridgebots.Suit.DIAMONDS, bridgebots.Suit.CLUBS]
10
  SUIT_SYMBOLS = ['♠','♥','♦','♣']
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ suit_symbols = ['♠','♥','♦','♣']
14
+
15
+ class PDF(FPDF):
16
+ def __init__(self, event, site, *args, **kwargs):
17
+ super().__init__(*args, **kwargs)
18
+ self.event = event
19
+ self.site = site
20
+
21
+ def header(self) -> None:
22
+ # Setting font: helvetica bold 15
23
+ self.set_font("helvetica", "B", 12)
24
+ # Moving cursor to the right:
25
+ self.cell(80, 5, self.event)
26
+ # Printing title:
27
+ self.cell(80, 5, self.site, align="C", new_x="LMARGIN",
28
+ new_y="NEXT",)
29
+ # Performing a line break:
30
+ self.ln(5)
31
+
32
+ def footer(self) -> None:
33
+ return super().footer()
34
+
35
+ def print_boards(self, results):
36
+ self.add_page()
37
+ self.add_font('times2', style='', fname='times.ttf')
38
+ self.set_font("times2", "", 10)
39
+ self.c_margin = 0.1
40
+ table_config = {
41
+ 'borders_layout':'NONE',
42
+ 'col_widths':3,
43
+ 'line_height':self.font_size + 0.4,
44
+ 'align': 'L',
45
+ 'text_align': 'L',
46
+ 'first_row_as_headings': False,
47
+ }
48
+
49
+ start_x,start_y = 9, self.y
50
+ table_size = 45, 46
51
+ table_margin = 2
52
+ for i, result in enumerate(results):
53
+ deal = result[0]
54
+ board_no = int(result[1][0]['Board'])
55
+ dds_tricks_table = result[1][0]['dds_tricks_table']
56
+
57
+ page_i = i % 20
58
+ if (i % 20 == 0) and (i != 0):
59
+ self.add_page()
60
+ row_idx = page_i // 4
61
+ col_idx = page_i % 4
62
+ x = start_x + (table_size[0] + 2 * table_margin + 1) * col_idx
63
+ y = start_y + (table_size[1] + 2 * table_margin + 1) * row_idx
64
+
65
+ top_left = x - table_margin, y - table_margin
66
+ top_right = x + table_size[0] + table_margin, y - table_margin
67
+ bottom_left = x - table_margin, y + table_size[1] + table_margin
68
+ bottom_right = x + table_size[0] + table_margin, y + table_size[1] + table_margin
69
+ self.set_xy(x,y)
70
+
71
+ self.line(*top_left, *bottom_left)
72
+ self.line(*top_left, * top_right)
73
+ self.line(*top_right, *bottom_right)
74
+ self.line(*bottom_left, *bottom_right)
75
+
76
+ dds_top_left = x - table_margin, y + 35 - 1
77
+ dds_top_right = x - table_margin + 18, y + 35 - 1
78
+ dds_bottom_right = x - table_margin + 18, y + table_size[1] + table_margin
79
+ self.line(*dds_top_left, *dds_top_right)
80
+ self.line(*dds_top_right, *dds_bottom_right)
81
 
82
+ dealer = DEALER_LIST[(board_no-1) % 4]
83
+ vul = VULNERABILITY_LIST[(board_no-1) % 16]
84
 
85
+ self.set_font_size(10)
86
+ with self.table(**table_config) as table:
87
+ row = table.row()
88
+ self.set_font_size(16)
89
+ row.cell(str(board_no), colspan=6, rowspan=2, align='C', v_align='C', padding=(2,0,0,0))
90
+ self.set_font_size(10)
91
+ for i, (suit, values) in enumerate(zip(suit_symbols, get_values(deal, 'N'))):
92
+ if i!=0:
93
+ row = table.row()
94
+ if i == 2:
95
+ row.cell(f'{dealer} / {vul}', colspan=6, rowspan=2, align='C', v_align='C')
96
+ print_suit_values(self,row,suit,values)
97
+
98
+ # row = table.row()
99
+ # row = table.row()
100
+ for i, (suit, values1, values2) in enumerate(zip(suit_symbols, get_values(deal, 'W'), get_values(deal, 'E'))):
101
  row = table.row()
102
+ row.cell('', colspan=1, rowspan=1)
103
+ print_suit_values(self, row, suit, values1)
104
+ row.cell('',colspan=3)
105
+
106
+ print_suit_values(self, row, suit, values2)
107
 
108
+ # row = table.row()
109
+ row = table.row()
110
+ row.cell('', colspan=6, rowspan=4)
111
+ for i, (suit, values) in enumerate(zip(suit_symbols, get_values(deal, 'S'))):
112
+ if i!=0:
113
+ row = table.row()
114
+ print_suit_values(self, row, suit, values)
115
+
116
+ self.set_xy(x-1,y+34)
117
+ self.set_font_size(7)
118
+ dds_table_config = {
119
+ 'borders_layout':'NONE',
120
+ 'col_widths':2.8,
121
+ 'line_height':self.font_size + 0.1,
122
+ 'align': 'L',
123
+ 'text_align': 'L',
124
+ 'first_row_as_headings': False,
125
+ 'padding': 0.1
126
+ }
127
+ with self.table(**dds_table_config) as table:
128
+ row = table.row()
129
+ row.cell('')
130
+ self.set_font_size(5)
131
+ row.cell('NT', align='C')
132
+ self.set_font_size(7)
133
+ row.cell(suit_symbols[0], align='C')
134
+ row.cell(suit_symbols[1], align='C')
135
+ row.cell(suit_symbols[2], align='C')
136
+ row.cell(suit_symbols[3], align='C')
137
+ row = table.row()
138
+ for i in range(4):
139
+ if i!=0:
140
+ row = table.row()
141
+ row.cell(dds_tricks_table[i*5][0], align='C')
142
+ row.cell(dds_tricks_table[i*5][2], align='C')
143
+ row.cell(dds_tricks_table[i*5+1][2], align='C')
144
+ row.cell(dds_tricks_table[i*5+2][2], align='C')
145
+ row.cell(dds_tricks_table[i*5+3][2], align='C')
146
+ row.cell(dds_tricks_table[i*5+4][2], align='C')
147
+
148
+
149
+ def create_hand_record_pdf(pbn_paths, event, site):
150
+ filepath_merged_pbn = merge_pbn(pbn_paths)
151
+ results = parse_pbn(filepath_merged_pbn)
152
+ fd,fn = tempfile.mkstemp(".pdf")
153
+ pdf = PDF(event, site)
154
+ pdf.print_boards(results)
155
  pdf.output(fn)
156
  return fn
157
 
libdds.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f38db5d391d6d1f9046af221e517b6359bbe536ad55907eb37253ef7eccfaaa0
3
+ size 495992
pbn_util.py CHANGED
@@ -6,18 +6,95 @@ from datetime import datetime
6
  import pandas as pd
7
  import tempfile
8
  from collections import Counter
 
 
9
 
10
 
11
  DEALER_LIST = ['N', 'E', 'S', 'W']
12
  VULNERABILITY_LIST = ["None","NS","EW","All","NS","EW","All","None","EW","All","None","NS","All","None","NS","EW"]
13
  SUITS = [bridgebots.Suit.SPADES, bridgebots.Suit.HEARTS, bridgebots.Suit.DIAMONDS, bridgebots.Suit.CLUBS]
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  def parse_single_pbn_record(record_strings):
16
  """
17
  :param record_strings: One string per line of a single PBN deal record
18
  :return: Deal and BoardRecord corresponding to the PBN record
19
  """
20
- record_dict = bridgebots.pbn._build_record_dict(record_strings)
21
  try:
22
  deal = bridgebots.pbn.from_pbn_deal(record_dict["Dealer"], record_dict["Vulnerable"], record_dict["Deal"])
23
  except KeyError as e:
@@ -69,9 +146,10 @@ def create_single_pbn_string(
69
  deal += ' '.join(
70
  ['.'.join(data[col]) for col in data.columns[1:]]
71
  ) # sss.hhh.ddd.ccc sss.hhh.ddd.ccc......
 
72
 
73
  file = ''
74
- file += ("%This pbn was generated by Bridge Hand Scanner\n")
75
  file += f'[Event "{event}"]\n'
76
  file += f'[Site "{site}"]\n'
77
  file += f'[Date "{date_print}"]\n'
@@ -79,7 +157,8 @@ def create_single_pbn_string(
79
  file += f'[Dealer "{dealer}"]\n'
80
  file += f'[Vulnerable "{vulnerability}"]\n'
81
  file += f'[Deal "{deal}"]\n'
82
-
 
83
  return file
84
 
85
  def merge_pbn(pbn_paths):
@@ -96,7 +175,7 @@ def merge_pbn(pbn_paths):
96
  with open(fd, 'w') as f:
97
  for i, (k,v) in enumerate(ordered_board_dict.items()):
98
  if i != 0:
99
- f.write('\n*\n')
100
  f.write(v)
101
  return fn
102
 
@@ -107,7 +186,6 @@ def validate_pbn(pbn_string):
107
  except AssertionError:
108
  raise ValueError('Everyone should have 13 cards')
109
  except Exception as e:
110
- print('test')
111
  raise Exception(e)
112
  hands = deal.hands
113
  duplicated = set()
@@ -134,4 +212,21 @@ def validate_pbn(pbn_string):
134
  err_msg += '{direction.name} has {num_cards} cards. '
135
 
136
  if err_msg:
137
- raise ValueError(err_msg)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  import pandas as pd
7
  import tempfile
8
  from collections import Counter
9
+ from dds_util import get_result_table
10
+ from typing import List, Dict
11
 
12
 
13
  DEALER_LIST = ['N', 'E', 'S', 'W']
14
  VULNERABILITY_LIST = ["None","NS","EW","All","NS","EW","All","None","EW","All","None","NS","All","None","NS","EW"]
15
  SUITS = [bridgebots.Suit.SPADES, bridgebots.Suit.HEARTS, bridgebots.Suit.DIAMONDS, bridgebots.Suit.CLUBS]
16
 
17
+
18
+ def _build_record_dict(record_strings: List[str]) -> Dict:
19
+ """
20
+ Parse the pbn line by line. When a block section like "Auction" or "Play" is encountered, collect all the content of
21
+ the block into a single entry
22
+ :param record_strings: List of string lines for a single board
23
+ :return: A dictionary mapping keys from the pbn to strings or other useful values (e.g. list of strings for the
24
+ bidding record)
25
+ """
26
+ record_dict = {}
27
+ # Janky while loop to handle non bracketed lines
28
+ i = 0
29
+ while i < len(record_strings):
30
+ record_string = record_strings[i]
31
+ if not (record_string.startswith("[") or record_string.startswith("{")):
32
+ i += 1
33
+ continue
34
+ if record_string.startswith("{"):
35
+ commentary = ""
36
+ while i < len(record_strings):
37
+ record_string = record_strings[i]
38
+ if record_string.startswith("["):
39
+ break
40
+ commentary += record_string + " "
41
+ i += 1
42
+ record_dict["Commentary"] = commentary.strip()
43
+ continue
44
+ if record_string.startswith("[") and "]" not in record_string:
45
+ while "]" not in record_string:
46
+ i += 1
47
+ record_string = record_string + record_strings[i]
48
+ record_string = record_string.replace("[", "").replace("]", "")
49
+ key, value = record_string.split(maxsplit=1)
50
+ value = value.replace('"', "")
51
+ if key == "Note":
52
+ number, message = value.split(":", maxsplit=1)
53
+ key = key + "_" + number
54
+ value = message
55
+ record_dict[key] = value
56
+ if key == "Auction":
57
+ auction_record = []
58
+ i += 1
59
+ while i < len(record_strings):
60
+ auction_str = record_strings[i]
61
+ if "[" in auction_str:
62
+ break
63
+ auction_record.extend(auction_str.split())
64
+ i += 1
65
+ record_dict["bidding_record"] = auction_record
66
+
67
+ elif key == "Play":
68
+ play_record = []
69
+ i += 1
70
+ while i < len(record_strings):
71
+ play_str = record_strings[i]
72
+ if "[" in play_str or play_str == "*":
73
+ break
74
+ play_record.append(play_str.split())
75
+ i += 1
76
+ record_dict["play_record"] = play_record
77
+ elif key == "OptimumResultTable":
78
+ dds_tricks_table = []
79
+ i += 1
80
+ while i < len(record_strings):
81
+ dds_str = record_strings[i]
82
+ if "[" in dds_str or dds_str == "*":
83
+ break
84
+ dds_tricks_table.append(dds_str.split())
85
+ i += 1
86
+ print(dds_tricks_table)
87
+ record_dict["dds_tricks_table"] = dds_tricks_table
88
+ else:
89
+ i += 1
90
+ return record_dict
91
+
92
  def parse_single_pbn_record(record_strings):
93
  """
94
  :param record_strings: One string per line of a single PBN deal record
95
  :return: Deal and BoardRecord corresponding to the PBN record
96
  """
97
+ record_dict = _build_record_dict(record_strings)
98
  try:
99
  deal = bridgebots.pbn.from_pbn_deal(record_dict["Dealer"], record_dict["Vulnerable"], record_dict["Deal"])
100
  except KeyError as e:
 
146
  deal += ' '.join(
147
  ['.'.join(data[col]) for col in data.columns[1:]]
148
  ) # sss.hhh.ddd.ccc sss.hhh.ddd.ccc......
149
+ dd_tricks = get_result_table(deal)
150
 
151
  file = ''
152
+ file += ("%This PBN was generated by Bridge Hand Scanner\n")
153
  file += f'[Event "{event}"]\n'
154
  file += f'[Site "{site}"]\n'
155
  file += f'[Date "{date_print}"]\n'
 
157
  file += f'[Dealer "{dealer}"]\n'
158
  file += f'[Vulnerable "{vulnerability}"]\n'
159
  file += f'[Deal "{deal}"]\n'
160
+ file += dd_tricks
161
+ file += '\n'
162
  return file
163
 
164
  def merge_pbn(pbn_paths):
 
175
  with open(fd, 'w') as f:
176
  for i, (k,v) in enumerate(ordered_board_dict.items()):
177
  if i != 0:
178
+ f.write('\n')
179
  f.write(v)
180
  return fn
181
 
 
186
  except AssertionError:
187
  raise ValueError('Everyone should have 13 cards')
188
  except Exception as e:
 
189
  raise Exception(e)
190
  hands = deal.hands
191
  duplicated = set()
 
212
  err_msg += '{direction.name} has {num_cards} cards. '
213
 
214
  if err_msg:
215
+ raise ValueError(err_msg)
216
+
217
+ def parse_dds_table(raw_list):
218
+ table = []
219
+ row = []
220
+ tempchar = ''
221
+ for x in raw_list:
222
+ if len(x) > 0:
223
+ tempchar += x[0]
224
+ else:
225
+ if len(tempchar) > 0:
226
+ row.append(tempchar)
227
+ tempchar = ''
228
+
229
+ if len(row) == 3:
230
+ table.append(row)
231
+ row = []
232
+ return table