diff options
author | Michał Klichowicz <emkael@tlen.pl> | 2023-09-30 12:40:40 +0200 |
---|---|---|
committer | Michał Klichowicz <emkael@tlen.pl> | 2023-09-30 12:40:40 +0200 |
commit | 518bdba985a913044e84e82713a8e76f5ddd3301 (patch) | |
tree | 6757ec4d785c07e2c3f34e70b5bd7326fe7c7a2c /src/bcdd/PBNBoard.py |
Initial import script
Diffstat (limited to 'src/bcdd/PBNBoard.py')
-rw-r--r-- | src/bcdd/PBNBoard.py | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/src/bcdd/PBNBoard.py b/src/bcdd/PBNBoard.py new file mode 100644 index 0000000..87105f2 --- /dev/null +++ b/src/bcdd/PBNBoard.py @@ -0,0 +1,242 @@ +import re + +from .BCalcWrapper import BCalcWrapper +from .Exceptions import FieldNotFoundException, DDTableInvalidException + + +class PBNField(object): + + def __init__(self, key=None, value=None, raw_data=None): + self.key = key + self.value = value + self.raw_field = '[%s "%s"]' % (self.key, str(self.value)) \ + if self.key is not None else raw_data + +class PBNBoard(object): + + line_pattern = re.compile(r'\[(.*) "(.*)"\]') + ability_pattern = re.compile(r'\b([NESW]):([0-9A-D]{5})\b') + optimum_result_table_pattern = re.compile( + r'^([NESW])\s+([CDHSN])T?\s+(\d+)$') + + def __init__(self, lines, line_no=None): + self.line_number = line_no + self._has_optimum_result_table = None + self._has_ability = None + self.fields = [] + for line in lines: + field = PBNField(raw_data=line) + line_parse = self.line_pattern.match(line) + if line_parse: + field.key = line_parse.group(1) + field.value = line_parse.group(2) + self.fields.append(field) + + def has_field(self, key): + for field in self.fields: + if key == field.key: + return True + return False + + def get_field(self, key): + for field in self.fields: + if key == field.key: + return field.value + raise FieldNotFoundException(key + ' field not found') + + def delete_field(self, key): + to_remove = [] + for field in self.fields: + if key == field.key: + to_remove.append(field) + for remove in to_remove: + self.fields.remove(remove) + + def get_event(self): + return self.get_field('Event') + + def write_event(self, name): + for i in range(0, len(self.fields)): + if 'Board' == self.fields[i].key: + self.fields.insert(i, PBNField(key='Event', value=name)) + break + + def get_layout(self): + return self.get_field('Deal') + + def get_number(self): + return self.get_field('Board') + + def get_vulnerable(self): + return self.get_field('Vulnerable') + + def get_dealer(self): + return self.get_field('Dealer') + + def validate_ability(self, ability): + matches = self.ability_pattern.findall(ability) + if not len(matches): + self._has_ability = False + raise DDTableInvalidException('Invalid Ability line: ' + ability) + players = [] + for match in matches: + if match[0] in players: + self._has_ability = False + raise DDTableInvalidException( + 'Duplicate entry in Ability: ' + match[0]) + else: + players.append(match[1]) + self._has_ability = False + return matches + + def get_ability(self): + return self.get_field('Ability') + + def delete_ability(self): + self.delete_field('Ability') + + def write_ability(self, dd_table): + sb = '' + for i in range(0, 4): + sb += BCalcWrapper.PLAYERS[i] + sb += ':' + sb += ''.join(['%X' % (j) for j in dd_table[i]])[::-1] + sb += ' ' + self.fields.append(PBNField(key='Ability', value=sb.strip())) + + def get_minimax(self): + return self.get_field('Minimax') + + def delete_minimax(self): + self.delete_field('Minimax') + + def write_minimax(self, contract): + minimax = '7NS0' if contract.score == 0 \ + else '%d%s%s%s%d' % ( + contract.level, + contract.denomination, + 'D' if contract.doubled else '', + contract.declarer, + contract.score) + self.fields.append(PBNField(key='Minimax', value=minimax)) + + def get_optimum_score(self): + return self.get_field('OptimumScore') + + def delete_optimum_score(self): + self.delete_field('OptimumScore') + + def write_optimum_score(self, contract): + self.fields.append( + PBNField(key='OptimumScore', + value='NS %d' % (contract.score))) + + def get_optimum_result(self): + return self.get_field('OptimumResult') + + def validate_optimum_result_table(self, table): + matches = [] + duplicates = [] + for line in table: + match = self.optimum_result_table_pattern.match(line) + if not match: + self._has_optimum_result_table = False + raise DDTableInvalidException( + 'Invalid OptimumResultTable line: ' + line) + position = match.group(1) + ' - ' + match.group(2) + if position in duplicates: + self._has_optimum_result_table = False + raise DDTableInvalidException( + 'Duplicate OptimumResultTable line: ' + line) + else: + duplicates.append(position) + matches.append(match) + self._has_optimum_result_table = True + return matches + + def get_optimum_result_table(self): + try: + return self.get_extended_raw_field('OptimumResultTable') + except FieldNotFoundException: + self._has_optimum_result_table = False + raise + + def get_auction(self): + return self.get_extended_raw_field('Auction', with_header=True) + + def get_extended_raw_field(self, field_name, with_header=False): + field_found = False + result = [] + for field in self.fields: + if field_name == field.key: + field_found = True + if with_header: + result.append(field.value) + else: + if field_found: + if field.key is None: + result.append(field.raw_field) + else: + break + if not field_found: + raise FieldNotFoundException('%s field not found' % (field_name)) + return result + + def delete_optimum_result_table(self): + field_found = False + to_remove = [] + for field in self.fields: + if 'OptimumResultTable' == field.key: + field_found = True + to_remove.append(field) + else: + if field_found: + if field.key is None: + to_remove.append(field) + else: + break + for remove in to_remove: + self.fields.remove(remove) + + def write_optimum_result_table(self, dd_table): + self.fields.append(PBNField( + key='OptimumResultTable', + value=r'Declarer;Denomination\2R;Result\2R')) + for i in range(0, 4): + for j in range(0, 5): + self.fields.append(PBNField( + raw_data='%s %s%s %d' % ( + BCalcWrapper.PLAYERS[i], + BCalcWrapper.DENOMINATIONS[j], + 'T' if BCalcWrapper.DENOMINATIONS[j] == 'N' else '', + dd_table[i][j]))) + + def save_par_contract(self, contract, jfr_only=False): + if not jfr_only: + self.delete_optimum_score() + self.write_optimum_score(contract) + self.delete_minimax() + self.write_minimax(contract) + + def save_dd_table(self, dd_table, jfr_only=False): + if not jfr_only: + if self._has_optimum_result_table is None: + try: + optimum_result_table = self.validate_optimum_result_table( + self.get_optimum_result_table()) + self._has_optimum_result_table = True + except FieldNotFoundException: + self._has_optimum_result_table = False + if not self._has_optimum_result_table: + self.delete_optimum_result_table() + self.write_optimum_result_table(dd_table) + if self._has_ability is None: + try: + ability = self.validate_ability( + self.get_ability()) + self._has_ability = True + except FieldNotFoundException: + self._has_ability = False + if not self._has_ability: + self.delete_ability() + self.write_ability(dd_table) |