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/ParScore.py |
Initial import script
Diffstat (limited to 'src/bcdd/ParScore.py')
-rw-r--r-- | src/bcdd/ParScore.py | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/src/bcdd/ParScore.py b/src/bcdd/ParScore.py new file mode 100644 index 0000000..e23a839 --- /dev/null +++ b/src/bcdd/ParScore.py @@ -0,0 +1,171 @@ +import re + +from .BCalcWrapper import BCalcWrapper as bcw +from .ParContract import ParContract +from .Exceptions import FieldNotFoundException + + +class ParScore(object): + _pbn_contract_pattern = re.compile(r'(\d)([CDHSN])(X?)\s+([NESW])') + _pbn_score_pattern = re.compile(r'(NS|EW)\s+(-?\d})') + _jfr_contract_pattern = re.compile(r'^(\d)([CDHSN])(D?)([NESW])(-?\d+)$') + + def __init__(self, board): + self._board = board + + def get_pbn_par_contract(self): + contract_field = self._board.get_optimum_result() + if 'Pass' == contract_field: + return ParContract() + contract_match = self._pbn_contract_pattern.match(contract_field) + if not contract_match: + raise ParScoreInvalidException( + 'Invalid format for OptimumResult field: ' + contract_field) + score_field = self._board.get_optimum_score() + score_match = self._pbn_score_pattern.match(score_field) + if not score_match: + raise ParScoreInvalidException( + 'Invalid format for OptimumScore field: ' + scoreField) + score = int(score_match.group(2)) + if 'EW' == score_match.group(1): + score = -score + contract = ParContract( + int(contract_match.group(1)), + contract_match.group(2)[0], + contract_match.group(4)[0], + 'X' == contract_match.group(3), + score) + return contract.validate() + + def get_jfr_par_contract(self): + par_string = self._board.get_minimax() + par_match = self._jfr_contract_pattern.match(par_string) + if not par_match: + raise ParScoreInvalidException( + 'Invalid format for Minimax field: ' + par_string) + if '0' == par_match.group(4): + return ParContract() # pass-out + contract = ParContract( + int(par_match.group(1)), + par_match.group(2)[0], + par_match.group(4)[0], + 'D' == par_match.group(3), + int(par_match.group(5))) + return contract.validate() + + def _determine_vulnerability(self, vulnerability, declarer): + vulnerability = vulnerability.upper() + return vulnerability in ['ALL', 'BOTH'] \ + or (vulnerability not in ['LOVE', 'NONE'] \ + and declarer in vulnerability) + + def _get_highest_makeable_contract(self, dd_table, + for_ns=True, for_ew=True): + contract = ParContract() + tricks = 0 + for i in range(3, -1, -1): + if ((i % 2 == 0) and for_ns) \ + or ((i % 2 == 1) and for_ew): + for j in range(0, 5): + level = dd_table[i][j] - 6 + denomination = bcw.DENOMINATIONS.index( + contract.denomination) \ + if contract.denomination in bcw.DENOMINATIONS \ + else -1 + if (level > contract.level) \ + or ((level == contract.level) \ + and (j > denomination)): + contract.level = level + contract.denomination = bcw.DENOMINATIONS[j] + contract.declarer = bcw.PLAYERS[i] + tricks = dd_table[i][j] + vulnerability = self._board.get_vulnerable().upper() + vulnerable = self._determine_vulnerability( + vulnerability, contract.declarer) + contract.score = contract.calculate_score(tricks, vulnerable) + return contract + + def get_dd_table_par_contract(self, dd_table): + dealer = self._board.get_dealer() + vulnerability = self._board.get_vulnerable().upper() + ns_highest = self._get_highest_makeable_contract( + dd_table, True, False); + ew_highest = self._get_highest_makeable_contract( + dd_table, False, True) + if ns_highest == ew_highest: + return ns_highest.validate() \ + if dealer in ['N', 'S'] else ew_highest.validate() + highest = max(ns_highest, ew_highest) + other_side_highest = min(ew_highest, ns_highest) + ns_playing = highest.declarer in ['N', 'S'] + defense_vulnerability = self._determine_vulnerability( + vulnerability, 'E' if ns_playing else 'N') + highest_defense = highest.get_defense(dd_table, defense_vulnerability) + if highest_defense is not None: + # Highest contract has profitable defense + return highest_defense.validate() + denomination_index = bcw.DENOMINATIONS.index(highest.denomination) \ + if highest.denomination in bcw.DENOMINATIONS \ + else -1 + declarer_index = bcw.PLAYERS.index(highest.declarer) \ + if highest.declarer in bcw.PLAYERS else -1 + player_indexes = [declarer_index, (declarer_index + 2) % 4] + vulnerable = self._determine_vulnerability( + vulnerability, highest.declarer) + score_squared = highest.score * highest.score + possible_optimums = [] + for i in range(0, 5): + for player in player_indexes: + level = highest.level + if i > denomination_index: + level -= 1 + while level > 0: + contract = ParContract( + level, + bcw.DENOMINATIONS[i], + bcw.PLAYERS[player], + False, 0) + contract.score = contract.calculate_score( + dd_table[player][i], vulnerable) + if other_side_highest > contract: + # Contract is lower than other side's contract + break + if (highest.score * contract.score) > 0: + # Contract makes + if abs(contract.score) >= abs(highest.score): + # Contract is profitable + defense = contract.get_defense( + dd_table, defense_vulnerability) + if defense is not None \ + and (contract.score * contract.score) \ + > (contract.score * defense.score): + # Contract has defense + possible_optimums.append(defense) + # So lower contracts will too. + break + else: + # Contract does not have defense + possible_optimums.append(contract) + else: + # Contract is not profitable + break + level -= 1 + for contract in possible_optimums: + if abs(contract.score) > abs(highest.score): + # Contract is more profitable + highest = contract + else: + if contract.score == highest.score: + if highest > contract: + # Equally profitable, but lower + highest = contract + return highest.validate() + + def get_par_contract(self, dd_table): + try: + return self.get_jfr_par_contract() + except FieldNotFoundException: + try: + return self.get_pbn_par_contract() + except FieldNotFoundException: + return self.get_dd_table_par_contract(dd_table) |