Spaces:
Sleeping
Sleeping
vincentlui
commited on
Commit
•
743c074
1
Parent(s):
0e339f1
update ui
Browse files- app.py +18 -7
- bridge_util.py +104 -0
- dds_util.py +1 -17
- hand_record.py +9 -10
- pbn_util.py +42 -60
app.py
CHANGED
@@ -5,7 +5,8 @@ import os
|
|
5 |
from tempfile import mkdtemp
|
6 |
from timeit import default_timer as timer
|
7 |
from hand_record import create_hand_record_pdf
|
8 |
-
from pbn_util import
|
|
|
9 |
|
10 |
# Download model and libraries from repo
|
11 |
try:
|
@@ -45,7 +46,8 @@ def predict(image_path, top_hand_idx):
|
|
45 |
df = default_df.copy(deep=True)#pd.DataFrame(['♠', '♥', '♦', '♣'], columns=[''])
|
46 |
for hand in hands:
|
47 |
df[hand.direction] = [''.join(c) for c in hand.cards]
|
48 |
-
except:
|
|
|
49 |
raise gr.Error('Cannot process image')
|
50 |
|
51 |
end = timer()
|
@@ -67,17 +69,15 @@ def save_file(df, cache_dir, files, board_no):
|
|
67 |
d = mkdtemp()
|
68 |
|
69 |
try:
|
70 |
-
|
71 |
-
validate_pbn(pbn_str)
|
72 |
except Exception as e:
|
73 |
print(e)
|
74 |
-
gr.Warning(f'Fail to save
|
75 |
return files, files, d
|
76 |
|
77 |
file_name = f'board_{board_no:03d}.pbn'
|
78 |
file_path = os.path.join(d,file_name)
|
79 |
-
|
80 |
-
f.write(pbn_str)
|
81 |
|
82 |
if not file_path in files:
|
83 |
files.append(file_path)
|
@@ -87,6 +87,13 @@ 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:
|
91 |
gr.Markdown(
|
92 |
"""
|
@@ -111,6 +118,7 @@ with gr.Blocks(css=custom_css) as demo:
|
|
111 |
total = gr.State(0)
|
112 |
gradio_cache_dir = gr.State()
|
113 |
files = gr.State([])
|
|
|
114 |
with gr.Tab('Scan Image'):
|
115 |
with gr.Row():
|
116 |
with gr.Column():
|
@@ -139,6 +147,7 @@ with gr.Blocks(css=custom_css) as demo:
|
|
139 |
headers=['', 'N', 'E', 'S', 'W'],
|
140 |
interactive=True, column_widths=['8%', '23%','23%','23%','23%'])
|
141 |
b2 = gr.Button('Save')
|
|
|
142 |
b3 = gr.File(interactive=False, file_count='multiple')
|
143 |
|
144 |
with gr.Tab('Hand Record'):
|
@@ -155,5 +164,7 @@ with gr.Blocks(css=custom_css) as demo:
|
|
155 |
a2.add([a1,b1])
|
156 |
a3.click(predict, [a1, a_top], [b1])
|
157 |
b2.click(save_file, [b1, gradio_cache_dir, files, a_board_no], [b3, tab2_upload_file, gradio_cache_dir])
|
|
|
|
|
158 |
|
159 |
demo.queue().launch()
|
|
|
5 |
from tempfile import mkdtemp
|
6 |
from timeit import default_timer as timer
|
7 |
from hand_record import create_hand_record_pdf
|
8 |
+
from pbn_util import create_pbn_file
|
9 |
+
from bridge_util import validate_dataframe, df_info, roll_direction
|
10 |
|
11 |
# Download model and libraries from repo
|
12 |
try:
|
|
|
46 |
df = default_df.copy(deep=True)#pd.DataFrame(['♠', '♥', '♦', '♣'], columns=[''])
|
47 |
for hand in hands:
|
48 |
df[hand.direction] = [''.join(c) for c in hand.cards]
|
49 |
+
except Exception as e:
|
50 |
+
print(e)
|
51 |
raise gr.Error('Cannot process image')
|
52 |
|
53 |
end = timer()
|
|
|
69 |
d = mkdtemp()
|
70 |
|
71 |
try:
|
72 |
+
validate_dataframe(df)
|
|
|
73 |
except Exception as e:
|
74 |
print(e)
|
75 |
+
gr.Warning(f'Fail to save pbn. Error in table entries. {e}')
|
76 |
return files, files, d
|
77 |
|
78 |
file_name = f'board_{board_no:03d}.pbn'
|
79 |
file_path = os.path.join(d,file_name)
|
80 |
+
create_pbn_file(df, file_path)
|
|
|
81 |
|
82 |
if not file_path in files:
|
83 |
files.append(file_path)
|
|
|
87 |
file_path = create_hand_record_pdf(files, event=event, site=site)
|
88 |
return file_path
|
89 |
|
90 |
+
def print_df_info(df):
|
91 |
+
return df_info(df)
|
92 |
+
|
93 |
+
def change_direction_in_df(df, top_direction_idx:int, current_top_idx:int):
|
94 |
+
roll_idx = current_top_idx - top_direction_idx
|
95 |
+
return roll_direction(df, roll_idx), top_direction_idx
|
96 |
+
|
97 |
with gr.Blocks(css=custom_css) as demo:
|
98 |
gr.Markdown(
|
99 |
"""
|
|
|
118 |
total = gr.State(0)
|
119 |
gradio_cache_dir = gr.State()
|
120 |
files = gr.State([])
|
121 |
+
current_top_idx = gr.State(0)
|
122 |
with gr.Tab('Scan Image'):
|
123 |
with gr.Row():
|
124 |
with gr.Column():
|
|
|
147 |
headers=['', 'N', 'E', 'S', 'W'],
|
148 |
interactive=True, column_widths=['8%', '23%','23%','23%','23%'])
|
149 |
b2 = gr.Button('Save')
|
150 |
+
b_info_panel = gr.TextArea(lines=5,show_label=False, interactive=False)
|
151 |
b3 = gr.File(interactive=False, file_count='multiple')
|
152 |
|
153 |
with gr.Tab('Hand Record'):
|
|
|
164 |
a2.add([a1,b1])
|
165 |
a3.click(predict, [a1, a_top], [b1])
|
166 |
b2.click(save_file, [b1, gradio_cache_dir, files, a_board_no], [b3, tab2_upload_file, gradio_cache_dir])
|
167 |
+
b1.change(print_df_info, b1, b_info_panel)
|
168 |
+
a_top.change(change_direction_in_df, [b1, a_top, current_top_idx], [b1, current_top_idx])
|
169 |
|
170 |
demo.queue().launch()
|
bridge_util.py
ADDED
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from bridgebots import Suit, Direction, Card, Rank
|
2 |
+
from bridgebots.pbn import from_pbn_deal
|
3 |
+
from pbn_util import pbn_deal_string
|
4 |
+
from collections import Counter
|
5 |
+
|
6 |
+
DEALER_LIST = ['N', 'E', 'S', 'W']
|
7 |
+
VULNERABILITY_LIST = ["None","NS","EW","All","NS","EW","All","None","EW","All","None","NS","All","None","NS","EW"]
|
8 |
+
SUITS = [Suit.SPADES, Suit.HEARTS, Suit.DIAMONDS, Suit.CLUBS]
|
9 |
+
RANKS = [r.abbreviation() for r in Rank]
|
10 |
+
|
11 |
+
def get_vulnerabilty_from_board_number(board_no: int) -> str:
|
12 |
+
# Get vulnerability from board number
|
13 |
+
return VULNERABILITY_LIST[(board_no-1) % 16]
|
14 |
+
|
15 |
+
def get_dealer_from_board_number(board_no: int) -> str:
|
16 |
+
return DEALER_LIST[(board_no-1) % 4]
|
17 |
+
|
18 |
+
def validate_dataframe(df) -> None:
|
19 |
+
""" validate submitted dataframe before saving pbn """
|
20 |
+
# Check column names to be NESW
|
21 |
+
missing_direction = [direction.abbreviation() for direction in Direction if not direction.abbreviation() in df.columns]
|
22 |
+
assert len(missing_direction) == 0, f"Expect directions in table to be {','.join([d.abbreviation() for d in Direction])}" + \
|
23 |
+
f" missing {','.join(missing_direction)}"
|
24 |
+
|
25 |
+
err_msg = ''
|
26 |
+
# Check each direction to have 13 cards
|
27 |
+
invalid_num_cards_direction = [direction.name for direction in Direction if len((''.join(df[direction.abbreviation()].tolist()))) != 13]
|
28 |
+
if invalid_num_cards_direction:
|
29 |
+
err_msg += f'Expect 13 cards in each hand, received invalid number of cards in {",".join(invalid_num_cards_direction)}. '
|
30 |
+
|
31 |
+
# Check card values in RANKS
|
32 |
+
cards = []
|
33 |
+
invalid_values = set()
|
34 |
+
for direction in Direction:
|
35 |
+
for ranks, suit in zip(df[direction.abbreviation()], SUITS):
|
36 |
+
for r in ranks:
|
37 |
+
try:
|
38 |
+
Rank.from_str(r)
|
39 |
+
except:
|
40 |
+
invalid_values.add(r)
|
41 |
+
assert not invalid_values, f'Expect card values in {",".join(RANKS)}, received {invalid_values}'
|
42 |
+
|
43 |
+
for direction in Direction:
|
44 |
+
for ranks, suit in zip(df[direction.abbreviation()], SUITS):
|
45 |
+
cards.extend([Card(rank=Rank.from_str(v), suit=suit) for v in ranks])
|
46 |
+
|
47 |
+
# Check duplicated and missing cards
|
48 |
+
duplicated, missing = get_invalid_cards(cards)
|
49 |
+
|
50 |
+
if len(duplicated) > 0:
|
51 |
+
err_msg += f'Duplicated cards: {duplicated}. '
|
52 |
+
if len(missing) > 0:
|
53 |
+
err_msg += f'Missing cards: {missing}. '
|
54 |
+
assert not err_msg, err_msg
|
55 |
+
|
56 |
+
def df_info(df):
|
57 |
+
# missing_direction = [direction.abbreviation() for direction in Direction if not direction.abbreviation() in df.columns]
|
58 |
+
num_cards_direction = {direction.name: len((''.join(df[direction.abbreviation()].tolist()))) for direction in Direction}
|
59 |
+
|
60 |
+
invalid_values = set()
|
61 |
+
for direction in Direction:
|
62 |
+
for ranks, suit in zip(df[direction.abbreviation()], SUITS):
|
63 |
+
for r in ranks:
|
64 |
+
try:
|
65 |
+
Rank.from_str(r)
|
66 |
+
except:
|
67 |
+
invalid_values.add(r)
|
68 |
+
|
69 |
+
if invalid_values:
|
70 |
+
return f'Expect card values in {",".join(RANKS)}, received {invalid_values}'
|
71 |
+
|
72 |
+
cards = []
|
73 |
+
for direction in Direction:
|
74 |
+
for ranks, suit in zip(df[direction.abbreviation()], SUITS):
|
75 |
+
cards.extend([Card(rank=Rank.from_str(v), suit=suit) for v in ranks])
|
76 |
+
|
77 |
+
# Check duplicated and missing cards
|
78 |
+
duplicated, missing = get_invalid_cards(cards)
|
79 |
+
|
80 |
+
if (not duplicated) and (not missing) and not [True for n in num_cards_direction.values() if n!=13]:
|
81 |
+
return f'It looks good!'
|
82 |
+
|
83 |
+
print_info = f'Number of cards: {num_cards_direction} \n'
|
84 |
+
print_info += f'Duplicated cards: {",".join(sorted(map(Card.__repr__,duplicated)))} \n'
|
85 |
+
print_info += f'Missing cards: {",".join(sorted(map(Card.__repr__,missing)))}'
|
86 |
+
return print_info
|
87 |
+
|
88 |
+
|
89 |
+
|
90 |
+
|
91 |
+
def get_invalid_cards(cards: list[Card]) -> tuple[set, set]:
|
92 |
+
"""Get duplicated and missing cards"""
|
93 |
+
counter_cards = Counter(cards)
|
94 |
+
duplicated = {c for c, cnt in counter_cards.items() if cnt > 1}
|
95 |
+
missing = {Card(s,r) for r in Rank for s in Suit if not Card(s,r) in counter_cards.keys()}
|
96 |
+
|
97 |
+
return duplicated, missing
|
98 |
+
|
99 |
+
def roll_direction(df, idx):
|
100 |
+
new_df = df.copy(deep=True)
|
101 |
+
for i, direction in enumerate(DEALER_LIST):
|
102 |
+
new_direction = DEALER_LIST[(idx+i)%4]
|
103 |
+
new_df[direction] = df[new_direction]
|
104 |
+
return new_df
|
dds_util.py
CHANGED
@@ -105,20 +105,4 @@ def get_dd_tricks(deal):
|
|
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 |
-
try:
|
114 |
-
dd_tricks_string = get_dd_tricks(pbn_deal_string)
|
115 |
-
except Exception as e:
|
116 |
-
print(e)
|
117 |
-
raise ValueError(e)
|
118 |
-
s = '[OptimumResultTable "Declarer;Denomination\\2R;Result\\2R"]\n'
|
119 |
-
for i, char in enumerate(dd_tricks_string):
|
120 |
-
direction = DD_DIRECTIONS[i//5]
|
121 |
-
suit = DD_SUITS[i%5]
|
122 |
-
tricks = int(char, 16)
|
123 |
-
s += f'{direction} {suit} {tricks}\n'
|
124 |
-
return s
|
|
|
105 |
|
106 |
"""
|
107 |
End
|
108 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hand_record.py
CHANGED
@@ -2,7 +2,7 @@ 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"]
|
@@ -10,8 +10,6 @@ SUITS = [bridgebots.Suit.SPADES, bridgebots.Suit.HEARTS, bridgebots.Suit.DIAMOND
|
|
10 |
SUIT_SYMBOLS = ['♠','♥','♦','♣']
|
11 |
|
12 |
|
13 |
-
suit_symbols = ['♠','♥','♦','♣']
|
14 |
-
|
15 |
class PDF(FPDF):
|
16 |
value_column_span = 7
|
17 |
def __init__(self, event, site, *args, **kwargs):
|
@@ -89,7 +87,7 @@ class PDF(FPDF):
|
|
89 |
self.set_font_size(16)
|
90 |
row.cell(str(board_no), colspan=6, rowspan=2, align='C', v_align='C', padding=(2,0,0,0))
|
91 |
self.set_font_size(10)
|
92 |
-
for i, (suit, values) in enumerate(zip(
|
93 |
if i!=0:
|
94 |
row = table.row()
|
95 |
if i == 2:
|
@@ -98,7 +96,7 @@ class PDF(FPDF):
|
|
98 |
|
99 |
# row = table.row()
|
100 |
# row = table.row()
|
101 |
-
for i, (suit, values1, values2) in enumerate(zip(
|
102 |
row = table.row()
|
103 |
row.cell('', colspan=1, rowspan=1)
|
104 |
print_suit_values(self, row, suit, values1)
|
@@ -109,7 +107,7 @@ class PDF(FPDF):
|
|
109 |
# row = table.row()
|
110 |
row = table.row()
|
111 |
row.cell('', colspan=6, rowspan=4)
|
112 |
-
for i, (suit, values) in enumerate(zip(
|
113 |
if i!=0:
|
114 |
row = table.row()
|
115 |
print_suit_values(self, row, suit, values)
|
@@ -131,10 +129,10 @@ class PDF(FPDF):
|
|
131 |
self.set_font_size(5)
|
132 |
row.cell('NT', align='C')
|
133 |
self.set_font_size(7)
|
134 |
-
row.cell(
|
135 |
-
row.cell(
|
136 |
-
row.cell(
|
137 |
-
row.cell(
|
138 |
row = table.row()
|
139 |
for i in range(4):
|
140 |
if i!=0:
|
@@ -154,6 +152,7 @@ def create_hand_record_pdf(pbn_paths, event, site):
|
|
154 |
pdf = PDF(event, site)
|
155 |
pdf.print_boards(results)
|
156 |
pdf.output(fn)
|
|
|
157 |
return fn
|
158 |
|
159 |
|
|
|
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"]
|
|
|
10 |
SUIT_SYMBOLS = ['♠','♥','♦','♣']
|
11 |
|
12 |
|
|
|
|
|
13 |
class PDF(FPDF):
|
14 |
value_column_span = 7
|
15 |
def __init__(self, event, site, *args, **kwargs):
|
|
|
87 |
self.set_font_size(16)
|
88 |
row.cell(str(board_no), colspan=6, rowspan=2, align='C', v_align='C', padding=(2,0,0,0))
|
89 |
self.set_font_size(10)
|
90 |
+
for i, (suit, values) in enumerate(zip(SUIT_SYMBOLS, get_values(deal, 'N'))):
|
91 |
if i!=0:
|
92 |
row = table.row()
|
93 |
if i == 2:
|
|
|
96 |
|
97 |
# row = table.row()
|
98 |
# row = table.row()
|
99 |
+
for i, (suit, values1, values2) in enumerate(zip(SUIT_SYMBOLS, get_values(deal, 'W'), get_values(deal, 'E'))):
|
100 |
row = table.row()
|
101 |
row.cell('', colspan=1, rowspan=1)
|
102 |
print_suit_values(self, row, suit, values1)
|
|
|
107 |
# row = table.row()
|
108 |
row = table.row()
|
109 |
row.cell('', colspan=6, rowspan=4)
|
110 |
+
for i, (suit, values) in enumerate(zip(SUIT_SYMBOLS, get_values(deal, 'S'))):
|
111 |
if i!=0:
|
112 |
row = table.row()
|
113 |
print_suit_values(self, row, suit, values)
|
|
|
129 |
self.set_font_size(5)
|
130 |
row.cell('NT', align='C')
|
131 |
self.set_font_size(7)
|
132 |
+
row.cell(SUIT_SYMBOLS[0], align='C')
|
133 |
+
row.cell(SUIT_SYMBOLS[1], align='C')
|
134 |
+
row.cell(SUIT_SYMBOLS[2], align='C')
|
135 |
+
row.cell(SUIT_SYMBOLS[3], align='C')
|
136 |
row = table.row()
|
137 |
for i in range(4):
|
138 |
if i!=0:
|
|
|
152 |
pdf = PDF(event, site)
|
153 |
pdf.print_boards(results)
|
154 |
pdf.output(fn)
|
155 |
+
os.close(fd)
|
156 |
return fn
|
157 |
|
158 |
|
pbn_util.py
CHANGED
@@ -1,13 +1,14 @@
|
|
1 |
from collections import defaultdict
|
2 |
import logging
|
3 |
import bridgebots
|
|
|
4 |
from pathlib import Path
|
5 |
from datetime import datetime
|
6 |
import pandas as pd
|
7 |
import tempfile
|
8 |
-
from
|
9 |
-
from dds_util import get_result_table
|
10 |
from typing import List, Dict
|
|
|
11 |
|
12 |
|
13 |
DEALER_LIST = ['N', 'E', 'S', 'W']
|
@@ -128,24 +129,25 @@ def parse_pbn(file_path):
|
|
128 |
return [(deal, board_records) for deal, board_records in records.items()]
|
129 |
|
130 |
|
131 |
-
def
|
132 |
data: pd.DataFrame,
|
|
|
133 |
date=datetime.today(),
|
134 |
board_no=1,
|
135 |
event='',
|
136 |
site='',
|
137 |
) -> str:
|
|
|
|
|
|
|
|
|
138 |
year = date.strftime("%y")
|
139 |
month = date.strftime("%m")
|
140 |
day = date.strftime("%d")
|
141 |
date_print = day + "." + month + "." + year
|
142 |
dealer = DEALER_LIST[(board_no-1) % 4]
|
143 |
vulnerability=VULNERABILITY_LIST[(board_no-1) % 16]
|
144 |
-
deal =
|
145 |
-
deal += ' '.join(
|
146 |
-
['.'.join(data[col]) for col in data.columns[1:]]
|
147 |
-
) # sss.hhh.ddd.ccc sss.hhh.ddd.ccc......
|
148 |
-
dd_tricks = get_result_table(deal)
|
149 |
|
150 |
file = ''
|
151 |
file += ("%This PBN was generated by Bridge Hand Scanner\n")
|
@@ -156,8 +158,12 @@ def create_single_pbn_string(
|
|
156 |
file += f'[Dealer "{dealer}"]\n'
|
157 |
file += f'[Vulnerable "{vulnerability}"]\n'
|
158 |
file += f'[Deal "{deal}"]\n'
|
159 |
-
file +=
|
160 |
-
file += '\n'
|
|
|
|
|
|
|
|
|
161 |
return file
|
162 |
|
163 |
def merge_pbn(pbn_paths):
|
@@ -178,55 +184,31 @@ def merge_pbn(pbn_paths):
|
|
178 |
f.write('\n')
|
179 |
f.write(v)
|
180 |
return fn
|
181 |
-
|
182 |
-
|
183 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
184 |
try:
|
185 |
-
|
186 |
-
except AssertionError:
|
187 |
-
raise ValueError('Everyone should have 13 cards')
|
188 |
except Exception as e:
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
duplicated.update([bridgebots.Card(suit,val) for val,cnt in Counter(cards).items() if cnt >1])
|
199 |
-
|
200 |
-
cards_set = set(cards)
|
201 |
-
missing.update([bridgebots.Card(suit,r) for r in bridgebots.Rank if not r in cards_set])
|
202 |
-
|
203 |
-
err_msg = ''
|
204 |
-
if len(duplicated) > 0:
|
205 |
-
err_msg += f'Duplicated cards: {duplicated}. '
|
206 |
-
if len(missing) > 0:
|
207 |
-
err_msg += f'Missing cards: {missing}. '
|
208 |
-
|
209 |
-
for direction in hands:
|
210 |
-
num_cards = len(hands[direction].cards)
|
211 |
-
if not num_cards == 13:
|
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
|
|
|
1 |
from collections import defaultdict
|
2 |
import logging
|
3 |
import bridgebots
|
4 |
+
from bridgebots import PlayerHand, Rank
|
5 |
from pathlib import Path
|
6 |
from datetime import datetime
|
7 |
import pandas as pd
|
8 |
import tempfile
|
9 |
+
from dds_util import get_dd_tricks
|
|
|
10 |
from typing import List, Dict
|
11 |
+
import os
|
12 |
|
13 |
|
14 |
DEALER_LIST = ['N', 'E', 'S', 'W']
|
|
|
129 |
return [(deal, board_records) for deal, board_records in records.items()]
|
130 |
|
131 |
|
132 |
+
def create_pbn_file(
|
133 |
data: pd.DataFrame,
|
134 |
+
save_path = None,
|
135 |
date=datetime.today(),
|
136 |
board_no=1,
|
137 |
event='',
|
138 |
site='',
|
139 |
) -> str:
|
140 |
+
if not save_path:
|
141 |
+
fd, save_path = tempfile.mkstemp(suffix='.pbn')
|
142 |
+
os.close(fd)
|
143 |
+
|
144 |
year = date.strftime("%y")
|
145 |
month = date.strftime("%m")
|
146 |
day = date.strftime("%d")
|
147 |
date_print = day + "." + month + "." + year
|
148 |
dealer = DEALER_LIST[(board_no-1) % 4]
|
149 |
vulnerability=VULNERABILITY_LIST[(board_no-1) % 16]
|
150 |
+
deal = pbn_deal_string(data, dealer) # sss.hhh.ddd.ccc sss.hhh.ddd.ccc......
|
|
|
|
|
|
|
|
|
151 |
|
152 |
file = ''
|
153 |
file += ("%This PBN was generated by Bridge Hand Scanner\n")
|
|
|
158 |
file += f'[Dealer "{dealer}"]\n'
|
159 |
file += f'[Vulnerable "{vulnerability}"]\n'
|
160 |
file += f'[Deal "{deal}"]\n'
|
161 |
+
file += pbn_optimum_table(deal)
|
162 |
+
file += '\n' # End of board
|
163 |
+
|
164 |
+
with open(save_path, 'w') as f:
|
165 |
+
f.write(file)
|
166 |
+
|
167 |
return file
|
168 |
|
169 |
def merge_pbn(pbn_paths):
|
|
|
184 |
f.write('\n')
|
185 |
f.write(v)
|
186 |
return fn
|
187 |
+
|
188 |
+
def pbn_deal_string(df, dealer):
|
189 |
+
assert dealer in DEALER_LIST, f'Dealer {dealer} is not valid'
|
190 |
+
dealer_idx = DEALER_LIST.index(dealer)
|
191 |
+
dealer_list_rolled = DEALER_LIST[dealer_idx:] + DEALER_LIST[:dealer_idx]
|
192 |
+
deal = f'{dealer}:'
|
193 |
+
hand_reprs = []
|
194 |
+
for direction in dealer_list_rolled:
|
195 |
+
hand = PlayerHand.from_string_lists(*df[direction])
|
196 |
+
hand_reprs.append('.'.join([''.join([r.abbreviation() for r in hand.suits[suit]]) for suit in SUITS]))
|
197 |
+
deal += ' '.join(hand_reprs)
|
198 |
+
return deal
|
199 |
+
|
200 |
+
DD_DIRECTIONS = ['N', 'S', 'E', 'W']
|
201 |
+
DD_SUITS = ['NT', 'S', 'H', 'D', 'C']
|
202 |
+
def pbn_optimum_table(pbn_deal_string):
|
203 |
try:
|
204 |
+
dd_tricks_string = get_dd_tricks(pbn_deal_string)
|
|
|
|
|
205 |
except Exception as e:
|
206 |
+
print(e)
|
207 |
+
raise ValueError(e)
|
208 |
+
s = '[OptimumResultTable "Declarer;Denomination\\2R;Result\\2R"]\n'
|
209 |
+
for i, char in enumerate(dd_tricks_string):
|
210 |
+
direction = DD_DIRECTIONS[i//5]
|
211 |
+
suit = DD_SUITS[i%5]
|
212 |
+
tricks = int(char, 16)
|
213 |
+
s += f'{direction} {suit} {tricks}\n'
|
214 |
+
return s
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|