summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoremkael <emkael@tlen.pl>2018-04-10 21:19:40 +0200
committeremkael <emkael@tlen.pl>2018-04-10 21:19:40 +0200
commit1afbf5f2c2286b00109cbfcfd4d4c661f33fcd27 (patch)
treef5cfe04ac2f4b507bd8890a1e92059d4418a73a7
parentaaab3f1022ad97cedf2134c7ef973d28a793af16 (diff)
Cdebase ported to Python
-rw-r--r--bcdd/BCalcWrapper.py32
-rw-r--r--bcdd/DDTable.py94
-rw-r--r--bcdd/Exceptions.py14
-rw-r--r--bcdd/PBNBoard.py229
-rw-r--r--bcdd/PBNFile.py41
-rw-r--r--bcdd/ParContract.py155
-rw-r--r--bcdd/ParScore.py171
-rw-r--r--pybcdd.cs116
-rw-r--r--pybcdd.py70
-rw-r--r--pybcdd/BCalcWrapper.cs44
-rw-r--r--pybcdd/DDTable.cs156
-rw-r--r--pybcdd/PBNBoard.cs390
-rw-r--r--pybcdd/PBNFile.cs75
-rw-r--r--pybcdd/ParContract.cs227
-rw-r--r--pybcdd/ParScore.cs233
15 files changed, 806 insertions, 1241 deletions
diff --git a/bcdd/BCalcWrapper.py b/bcdd/BCalcWrapper.py
new file mode 100644
index 0000000..9337809
--- /dev/null
+++ b/bcdd/BCalcWrapper.py
@@ -0,0 +1,32 @@
+'''
+Wrapper class for libbcalcDDS.dll
+'''
+
+from ctypes import cdll
+
+from .Exceptions import DllNotFoundException
+
+
+class BCalcWrapper(object):
+
+ DENOMINATIONS = [ 'C', 'D', 'H', 'S', 'N' ]
+ PLAYERS = [ 'N', 'E', 'S', 'W' ]
+
+ def __init__(self):
+ try:
+ self.libbcalcdds = cdll.LoadLibrary('./libbcalcdds.dll')
+ except OSError:
+ try:
+ self.libbcalcdds = cdll.LoadLibrary('./libbcalcdds.so')
+ except OSError:
+ self.libbcalcdds = None
+ if self.libbcalcdds is None:
+ raise DllNotFoundException()
+
+ def __getattr__(self, attrname):
+ def _dynamic_method(*args):
+ return getattr(self.libbcalcdds, 'bcalcDDS_' + attrname)(*args)
+ return _dynamic_method
+
+ def declarerToLeader(self, player):
+ return (player + 1) % 4
diff --git a/bcdd/DDTable.py b/bcdd/DDTable.py
new file mode 100644
index 0000000..cc08424
--- /dev/null
+++ b/bcdd/DDTable.py
@@ -0,0 +1,94 @@
+from ctypes import c_char_p
+from copy import copy
+
+from .BCalcWrapper import BCalcWrapper
+from .Exceptions import DDTableInvalidException, FieldNotFoundException
+
+
+class DDTable(object):
+
+ def _get_empty_table(self):
+ table = []
+ row = [-1] * 5
+ for col in range(0, 4):
+ table.append(copy(row))
+ return table
+
+ def _validate_table(self, table):
+ for row in table:
+ for t in row:
+ if (t > 13) or (t < 0):
+ raise DDTableInvalidException(
+ 'Invalid number of tricks: %d' % (t)
+ )
+ return table
+
+ _banner_displayed = False
+
+ def __init__(self, board):
+ self._board = board;
+ self._wrapper = BCalcWrapper()
+
+ def get_bcalc_table(self):
+ if not DDTable._banner_displayed:
+ print('Double dummy analysis provided by BCalc.')
+ print('BCalc is awesome, check it out: http://bcalc.w8.pl')
+ DDTable._banner_displayed = True
+ result = self._get_empty_table()
+ deal = self._board.get_layout()
+ solver = self._wrapper.new(b"PBN", deal.encode(), 0, 0)
+ for denom in range(0, 5):
+ self._wrapper.setTrumpAndReset(solver, denom)
+ for player in range(0, 4):
+ leader = self._wrapper.declarerToLeader(player)
+ self._wrapper.setPlayerOnLeadAndReset(solver, leader)
+ result[player][denom] = 13 - self._wrapper.getTricksToTake(
+ solver)
+ error = self._wrapper.getLastError(solver)
+ if error:
+ raise DDTableInvalidException(
+ 'BCalc error: ' + str(c_char_p(error)))
+ self._wrapper.delete(solver);
+ return self._validate_table(result)
+
+ def get_jfr_table(self):
+ result = self._get_empty_table()
+ ability = self._board.get_ability()
+ abilities = self._board.validate_ability(ability)
+ for player_ability in abilities:
+ player = player_ability.groups(1)[0]
+ player_id = BCalcWrapper.PLAYERS.index(player)
+ denom_id = 4
+ for tricks in player_ability.group(2):
+ result[player_id][denom_id] = int(tricks, 16)
+ denom_id -= 1
+ return self._validate_table(result)
+
+ def get_pbn_table(self):
+ table = self._board.get_optimum_result_table()
+ parsed_table = self._board.validate_optimum_result_table(table)
+ result = self._get_empty_table()
+ for line_match in parsed_table:
+ player = line_match.group(1)[0]
+ denom = line_match.group(2)[0]
+ tricks = int(line_match.group(3))
+ player_id = BCalcWrapper.PLAYERS.index(player)
+ denom_id = BCalcWrapper.DENOMINATIONS.index(denom)
+ result[player_id][denom_id] = tricks
+ return self._validate_table(result)
+
+ def get_dd_table(self):
+ try:
+ return self.get_jfr_table()
+ except FieldNotFoundException:
+ try:
+ return self.get_pbn_table()
+ except FieldNotFoundException:
+ return self.get_bcalc_table()
+
+ def print_table(self, dd_table):
+ print('\t' + '\t'.join(BCalcWrapper.DENOMINATIONS))
+ for i in range(0, 4):
+ print('%s%s' % (
+ self._wrapper.PLAYERS[i],
+ ''.join(['\t' + str(tricks) for tricks in dd_table[i]])))
diff --git a/bcdd/Exceptions.py b/bcdd/Exceptions.py
new file mode 100644
index 0000000..16970b5
--- /dev/null
+++ b/bcdd/Exceptions.py
@@ -0,0 +1,14 @@
+class DllNotFoundException(Exception):
+ pass
+
+
+class FieldNotFoundException(Exception):
+ pass
+
+
+class ParScoreInvalidException(FieldNotFoundException):
+ pass
+
+
+class DDTableInvalidException(FieldNotFoundException):
+ pass
diff --git a/bcdd/PBNBoard.py b/bcdd/PBNBoard.py
new file mode 100644
index 0000000..3dbb751
--- /dev/null
+++ b/bcdd/PBNBoard.py
@@ -0,0 +1,229 @@
+import re
+
+from .BCalcWrapper import BCalcWrapper
+from .Exceptions import FieldNotFoundException
+
+
+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):
+ 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.match(ability)
+ if len(match) != 4:
+ self._has_ability = False
+ raise DDTableInvalidException('Invalid Ability line: ' + ability)
+ players = []
+ for match in matches:
+ if match.group(1) in players:
+ self._has_ability = False
+ raise DDTableInvalidException(
+ 'Duplicate entry in Ability: ' + match.group(0))
+ else:
+ players.append(match.group(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):
+ field_found = False
+ result = []
+ for field in self.fields:
+ if 'OptimumResultTable' == field.key:
+ field_found = True
+ else:
+ if field_found:
+ if field.key is None:
+ result.append(field.raw_field)
+ else:
+ break
+ if not field_found:
+ self._has_optimum_result_table = False
+ raise FieldNotFoundException('OptimumResultTable field not found')
+ 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):
+ # we're not writing DDS custom fields, just parse them
+ self.delete_optimum_score()
+ self.write_optimum_score(contract)
+ self.delete_minimax()
+ self.write_minimax(contract)
+
+ def save_dd_table(self, dd_table):
+ 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)
diff --git a/bcdd/PBNFile.py b/bcdd/PBNFile.py
new file mode 100644
index 0000000..16f4d6d
--- /dev/null
+++ b/bcdd/PBNFile.py
@@ -0,0 +1,41 @@
+import shutil
+import tempfile
+
+from .PBNBoard import PBNBoard
+
+class PBNFile(object):
+
+ def __init__(self, filename):
+ self._filename = filename
+ self.output_file = None
+ self.boards = []
+ lines = []
+ with open(self._filename) as pbn_file:
+ contents = pbn_file.readlines()
+ for line in contents:
+ line = line.strip()
+ if not line:
+ if len(lines) > 0:
+ self.boards.append(PBNBoard(lines))
+ lines = []
+ else:
+ lines.append(line)
+ if len(lines) > 0:
+ self.boards.append(PBNBoard(lines))
+ if not self.boards[0].has_field('Event'):
+ self.boards[0].write_event('')
+
+ def write_board(self, board):
+ if self.output_file is None:
+ self.output_file = tempfile.NamedTemporaryFile(
+ mode='w', encoding='utf-8', delete=False)
+ for field in board.fields:
+ self.output_file.write(field.raw_field + '\n')
+ self.output_file.write('\n')
+
+ def save(self):
+ if self.output_file is None:
+ raise IOError('No boards written to PBN file, unable to save it.')
+ tmp_path = self.output_file.name
+ self.output_file.close()
+ shutil.move(tmp_path, self._filename)
diff --git a/bcdd/ParContract.py b/bcdd/ParContract.py
new file mode 100644
index 0000000..918efae
--- /dev/null
+++ b/bcdd/ParContract.py
@@ -0,0 +1,155 @@
+import functools
+
+from .BCalcWrapper import BCalcWrapper as bcw
+from .Exceptions import ParScoreInvalidException
+
+
+class ParContract(object):
+ def __init__(self, level=0, denom='', declarer='', doubled=False, score=0):
+ self.level = level
+ self.denomination = denom
+ self.declarer = declarer
+ self.doubled = doubled
+ self.score = score
+
+ def validate(self):
+ if self.score == 0:
+ return self
+ if (self.level < 1) or (self.level > 7):
+ raise ParScoreInvalidException(
+ 'Invalid par contract level: %d' % (self.level))
+ if self.denomination not in 'CDHSN':
+ raise ParScoreInvalidException(
+ 'Invalid par contract denomination: ' + self.denomination)
+ if self.declarer not in 'NESW':
+ raise ParScoreInvalidException(
+ 'Invalid par contract declarer: ' + self.declarer)
+ return self
+
+ def __repr__(self):
+ if self.score == 0:
+ return 'PASS'
+ return '%d%s%s %s %+d' % (
+ self.level,
+ self.denomination,
+ 'x' if self.doubled else '',
+ self.declarer,
+ self.score)
+
+ def __eq__(self, other):
+ return hash(self) == hash(other)
+
+ def __hash__(self):
+ return self.score + self.level + 10000 * (
+ ord(self.denomination[0]) if self.denomination else 0)
+
+ def calculate_score(self, tricks, vulnerable=False):
+ if self.level == 0:
+ return 0
+ score = 0
+ if self.level + 6 > tricks:
+ undertricks = self.level + 6 - tricks
+ if self.doubled:
+ while True:
+ if undertricks == 1:
+ # first undertrick: 100 non-vul, 200 vul
+ score -= 200 if vulnerable else 100
+ else:
+ if (undertricks <= 3) and not vulnerable:
+ # second non-vul undertrick: 200
+ score -= 200
+ else:
+ # further undertricks: 300
+ score -= 300
+ undertricks -= 1
+ if undertricks == 0:
+ break;
+ else:
+ score = -100 if vulnerable else -50
+ score *= undertricks
+ else:
+ par_tricks = self.level
+ while True:
+ if (self.denomination == 'N') and (par_tricks == 1):
+ # first non-trump trick: 40
+ score += 40
+ else:
+ # other tricks
+ score += 30 if self.denomination in 'NSH' else 20
+ par_tricks -= 1
+ if par_tricks == 0:
+ break
+ overtricks = tricks - self.level - 6
+ if self.doubled:
+ score *= 2
+ score += 50
+ # game premium
+ score += (500 if vulnerable else 300) if (score >= 100) else 50
+ if self.doubled:
+ score += overtricks * (200 if vulnerable else 100)
+ else:
+ score += overtricks * (20 if self.denomination in 'CD' else 30)
+ if self.level == 7:
+ # grand slam premium
+ score += 1500 if vulnerable else 1000
+ elif self.level == 6:
+ # small slam premium
+ score += 750 if vulnerable else 500
+ if self.declarer in 'EW':
+ score = -score
+ return score
+
+ def __gt__(self, other):
+ denomination = bcw.DENOMINATIONS.index(self.denomination) \
+ if self.denomination in bcw.DENOMINATIONS \
+ else -1
+ other_denomination = bcw.DENOMINATIONS.index(
+ other.denomination) \
+ if other.denomination in bcw.DENOMINATIONS else -1
+ return (self.level > other.level) \
+ or ((self.level == other.level) \
+ and (denomination > other_denomination))
+
+ def get_defense(self, dd_table, vulnerable):
+ declarer_index = bcw.PLAYERS.index(self.declarer) \
+ if self.declarer in bcw.PLAYERS else -1
+ denomination_index = bcw.DENOMINATIONS.index(self.denomination) \
+ if self.denomination in bcw.DENOMINATIONS else -1
+ if (self.level != 0) \
+ and (self.level + 6
+ <= dd_table[declarer_index][denomination_index]):
+ defenders_indexes = []
+ defenders_indexes.append((declarer_index + 1) % 4);
+ defenders_indexes.append((declarer_index + 3) % 4);
+ possible_defense = []
+ score_squared = self.score * self.score
+ for i in range(0, 5):
+ level = self.level
+ if i <= denomination_index:
+ level += 1
+ if level <= 7:
+ for defender in defenders_indexes:
+ if level + 6 > dd_table[defender][i]:
+ defense = ParContract(
+ level,
+ bcw.DENOMINATIONS[i],
+ bcw.PLAYERS[defender],
+ True,
+ 0)
+ defense.score = defense.calculate_score(
+ dd_table[defender][i],
+ vulnerable)
+ if score_squared > self.score * defense.score:
+ possible_defense.append(defense)
+ if possible_defense:
+ possible_defense.sort(
+ key=lambda x: abs(x.score - self.score))
+ optimum_defense = possible_defense[-1]
+ possible_defense = [defense for defense in possible_defense
+ if defense.score == optimum_defense.score]
+ for defense in possible_defense:
+ # Lowest from the most profitable sacrifices
+ if optimum_defense > defense:
+ optimum_defense = defense
+ return optimum_defense
+ return None
diff --git a/bcdd/ParScore.py b/bcdd/ParScore.py
new file mode 100644
index 0000000..b0c9674
--- /dev/null
+++ b/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.groups(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)
diff --git a/pybcdd.cs b/pybcdd.cs
deleted file mode 100644
index ea1eab3..0000000
--- a/pybcdd.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Windows.Forms;
-using System.IO;
-
-namespace BCDD
-{
- class Program
- {
- static List<String> getFiles(string[] args)
- {
- List<String> filenames = new List<String>();
- foreach (String arg in args)
- {
- if (File.Exists(arg))
- {
- filenames.Add(arg);
- }
- }
- if (filenames.Count == 0)
- {
- OpenFileDialog fd = new OpenFileDialog();
- fd.Multiselect = true;
- fd.Filter = "PBN files (*.pbn)|*.pbn|All files (*.*)|*.*";
- if (fd.ShowDialog() == DialogResult.OK)
- {
- filenames = new List<String>(fd.FileNames);
- }
- }
- return filenames;
- }
-
- [STAThread]
- static void Main(string[] args)
- {
- List<String> files = Program.getFiles(args);
- List<String> errors = new List<String>();
- if (files.Count > 0)
- {
- foreach (String filename in files)
- {
- try
- {
- Console.WriteLine("Analyzing " + filename);
- PBNFile file = new PBNFile(filename);
- foreach (PBNBoard board in file.Boards)
- {
- DDTable table = new DDTable(board);
- String boardNo;
- try
- {
- boardNo = board.GetNumber();
- }
- catch (FieldNotFoundException)
- {
- boardNo = "?";
- }
- try
- {
- int[,] ddTable = table.GetDDTable();
- if (ddTable != null)
- {
- Console.WriteLine("Board " + boardNo);
- DDTable.PrintTable(ddTable);
- ParScore par = new ParScore(board);
- ParContract contract = par.GetParContract(ddTable);
- Console.WriteLine(contract);
- Console.WriteLine();
- board.SaveDDTable(ddTable);
- board.SaveParContract(contract);
- file.WriteBoard(board);
- }
- else
- {
- String error = "unable to determine DD table for board " + boardNo;
- errors.Add(String.Format("[{0}] {1}", filename, error));
- Console.WriteLine("ERROR: " + error);
- }
- }
- catch (DllNotFoundException)
- {
- throw;
- }
- catch (Exception e)
- {
- errors.Add(String.Format("[{0}:{1}] {2}", filename, boardNo, e.Message));
- Console.WriteLine("ERROR: " + e.Message);
- }
- }
- file.Save();
- }
- catch (DllNotFoundException e)
- {
- errors.Add("libbcalcdds.dll could not be loaded - make sure it's present in application directory!");
- Console.WriteLine("ERROR: " + e.Message);
- break;
- }
- catch (Exception e)
- {
- errors.Add(e.Message);
- Console.WriteLine("ERROR: " + e.Message);
- }
- }
- if (errors.Count > 0) {
- Console.WriteLine("Following ERRORs occured:");
- foreach (String error in errors) {
- Console.WriteLine(error);
- }
- Console.WriteLine("Press any key to continue...");
- Console.ReadKey();
- }
- }
- }
- }
-}
diff --git a/pybcdd.py b/pybcdd.py
new file mode 100644
index 0000000..9925f8a
--- /dev/null
+++ b/pybcdd.py
@@ -0,0 +1,70 @@
+import os
+import sys
+
+from bcdd.DDTable import DDTable
+from bcdd.ParScore import ParScore
+from bcdd.Exceptions import DllNotFoundException, FieldNotFoundException
+from bcdd.PBNFile import PBNFile
+
+def get_files(args):
+ filenames = [name for name in args
+ if os.path.exists(name)
+ and (os.path.realpath(name) != os.path.realpath(__file__))]
+ if len(filenames) == 0:
+ raise FileNotFoundError('No valid filepaths provided!')
+ return filenames
+
+def main():
+ files = get_files(sys.argv)
+ errors = []
+ for filename in files:
+ try:
+ print('Analyzing %s' % (filename))
+ pbn_file = PBNFile(filename)
+ for board in pbn_file.boards:
+ table = DDTable(board)
+ board_no = ''
+ try:
+ board_no = board.get_number()
+ except FieldNotFoundException:
+ board_no = '?'
+ try:
+ dd_table = table.get_dd_table()
+ if dd_table is not None:
+ print('Board ' + board_no)
+ table.print_table(dd_table)
+ par = ParScore(board)
+ contract = par.get_par_contract(dd_table)
+ print(contract)
+ print('')
+ board.save_dd_table(dd_table)
+ board.save_par_contract(contract)
+ pbn_file.write_board(board)
+ else:
+ error = 'unable to determine DD table for board %s' \
+ % (board_no)
+ errors.append('[%s] %s' % (filename, error))
+ print('ERROR: ' + error)
+ except DllNotFoundException:
+ raise
+ except Exception as ex:
+ errors.append('[%s:%s] %s' % (filename, board_no, str(ex)))
+ print('ERROR: ' + str(ex))
+ raise
+ pbn_file.save()
+ except DllNotFoundException as ex:
+ errors.append("libbcalcdds library could not be loaded - make sure it's present in application directory!");
+ print('ERROR: ' + str(ex))
+ break
+ except Exception as ex:
+ errors.append(str(ex))
+ print('ERROR: ' + str(ex));
+ raise
+ if len(errors) > 0:
+ print('Following ERRORs occured:')
+ for error in errors:
+ print(error)
+ input('Press any key to continue...')
+
+if __name__ == '__main__':
+ main()
diff --git a/pybcdd/BCalcWrapper.cs b/pybcdd/BCalcWrapper.cs
deleted file mode 100644
index d2d0aa2..0000000
--- a/pybcdd/BCalcWrapper.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-
-namespace BCDD
-{
- /// <summary>
- /// Wrapper class for libbcalcDDS.ddl.
- /// </summary>
- class BCalcWrapper
- {
- public static char[] DENOMINATIONS = { 'C', 'D', 'H', 'S', 'N' };
- public static char[] PLAYERS = { 'N', 'E', 'S', 'W' };
-
- /// <remarks>http://bcalc.w8.pl/API_C/bcalcdds_8h.html#a8f522e85482fe383bebd963e873897f5</remarks>
- [DllImport(@"libbcalcdds.dll", CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr bcalcDDS_new(IntPtr format, IntPtr hands, Int32 trump, Int32 leader);
-
- /// <remarks>http://bcalc.w8.pl/API_C/bcalcdds_8h.html#a369ce661d027bef3f717967e42bf8b33</remarks>
- [DllImport(@"libbcalcdds.dll", CallingConvention = CallingConvention.Cdecl)]
- public static extern Int32 bcalcDDS_getTricksToTake(IntPtr solver);
-
- /// <remarks>http://bcalc.w8.pl/API_C/bcalcdds_8h.html#a8998a1eb1ca25de2e07448381ce63261</remarks>
- [DllImport(@"libbcalcdds.dll", CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr bcalcDDS_getLastError(IntPtr solver);
-
- /// <remarks>http://bcalc.w8.pl/API_C/bcalcdds_8h.html#a4a68da83bc7da4663e2257429539912d</remarks>
- [DllImport(@"libbcalcdds.dll", CallingConvention = CallingConvention.Cdecl)]
- public static extern void bcalcDDS_delete(IntPtr solver);
-
- /// <remarks>http://bcalc.w8.pl/API_C/bcalcdds_8h.html#a88fba3432e66efa5979bbc9e1f044164</remarks>
- [DllImport(@"libbcalcdds.dll", CallingConvention = CallingConvention.Cdecl)]
- public static extern void bcalcDDS_setTrumpAndReset(IntPtr solver, Int32 trump);
-
- /// <remarks>http://bcalc.w8.pl/API_C/bcalcdds_8h.html#a616031c1e1d856c4aac14390693adb4c</remarks>
- [DllImport(@"libbcalcdds.dll", CallingConvention = CallingConvention.Cdecl)]
- public static extern void bcalcDDS_setPlayerOnLeadAndReset(IntPtr solver, Int32 player);
-
- /// <remarks>http://bcalc.w8.pl/API_C/bcalcdds_8h.html#a6977a3b789bdf64eb2da9cbdb8b8fc39</remarks>
- public static Int32 bcalc_declarerToLeader(Int32 player)
- {
- return (player + 1) & 3;
- }
- }
-}
diff --git a/pybcdd/DDTable.cs b/pybcdd/DDTable.cs
deleted file mode 100644
index c2ffcd1..0000000
--- a/pybcdd/DDTable.cs
+++ /dev/null
@@ -1,156 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Runtime.InteropServices;
-using System.Text.RegularExpressions;
-
-namespace BCDD
-{
- class DDTableInvalidException : FieldNotFoundException
- {
- public DDTableInvalidException() : base() { }
- public DDTableInvalidException(String msg) : base(msg) { }
- }
-
- class DDTable
- {
- private PBNBoard board;
-
- private int[,] getEmptyTable()
- {
- int[,] result = new int[4, 5];
- for (int i = 0; i < 4; i++)
- {
- for (int j = 0; j < 5; j++)
- {
- result[i, j] = -1;
- }
- }
- return result;
- }
-
- private int[,] validateTable(int[,] table)
- {
- foreach (int t in table)
- {
- if (t > 13 || t < 0)
- {
- throw new DDTableInvalidException("Invalid number of tricks: " + t.ToString());
- }
- }
- return table;
- }
-
- public DDTable(PBNBoard board)
- {
- this.board = board;
- }
-
- private static bool bannerDisplayed = false;
-
- public int[,] GetBCalcTable()
- {
- if (!DDTable.bannerDisplayed)
- {
- Console.WriteLine("Double dummy analysis provided by BCalc.");
- Console.WriteLine("BCalc is awesome, check it out: http://bcalc.w8.pl");
- DDTable.bannerDisplayed = true;
- }
- int[,] result = this.getEmptyTable();
- String deal = this.board.GetLayout();
- IntPtr solver = BCalcWrapper.bcalcDDS_new(Marshal.StringToHGlobalAnsi("PBN"), Marshal.StringToHGlobalAnsi(deal), 0, 0);
- for (int denom = 0; denom < 5; denom++)
- {
- BCalcWrapper.bcalcDDS_setTrumpAndReset(solver, denom);
- for (int player = 0; player < 4; player++)
- {
- BCalcWrapper.bcalcDDS_setPlayerOnLeadAndReset(solver, BCalcWrapper.bcalc_declarerToLeader(player));
- result[player, denom] = 13 - BCalcWrapper.bcalcDDS_getTricksToTake(solver);
- String error = Marshal.PtrToStringAnsi(BCalcWrapper.bcalcDDS_getLastError(solver));
- if (error != null)
- {
- throw new DDTableInvalidException("BCalc error: " + error);
- }
- }
- }
- BCalcWrapper.bcalcDDS_delete(solver);
- return this.validateTable(result);
- }
-
- public int[,] GetJFRTable()
- {
- int[,] result = this.getEmptyTable();
- String ability = this.board.GetAbility();
- MatchCollection abilities = this.board.ValidateAbility(ability);
- foreach (Match playerAbility in abilities)
- {
- char player = playerAbility.Groups[1].Value[0];
- int playerID = Array.IndexOf(BCalcWrapper.PLAYERS, player);
- int denomID = 4;
- foreach (char tricks in playerAbility.Groups[2].Value.ToCharArray())
- {
- result[playerID, denomID] = (tricks > '9') ? (tricks - 'A' + 10) : (tricks - '0');
- denomID--;
- }
- }
- return this.validateTable(result);
- }
-
- public int[,] GetPBNTable()
- {
- List<String> table = this.board.GetOptimumResultTable();
- List<Match> parsedTable = this.board.ValidateOptimumResultTable(table);
- int[,] result = this.getEmptyTable();
- foreach (Match lineMatch in parsedTable)
- {
- char player = lineMatch.Groups[1].Value[0];
- char denom = lineMatch.Groups[2].Value[0];
- int tricks = Int16.Parse(lineMatch.Groups[3].Value);
- int playerID = Array.IndexOf(BCalcWrapper.PLAYERS, player);
- int denomID = Array.IndexOf(BCalcWrapper.DENOMINATIONS, denom);
- result[playerID, denomID] = tricks;
- }
- return this.validateTable(result);
- }
-
- public int[,] GetDDTable()
- {
- try
- {
- return this.GetJFRTable();
- }
- catch (FieldNotFoundException)
- {
- try
- {
- return this.GetPBNTable();
- }
- catch (FieldNotFoundException)
- {
- return this.GetBCalcTable();
- }
- }
- }
-
- public static void PrintTable(int[,] ddTable)
- {
- foreach (char header in BCalcWrapper.DENOMINATIONS)
- {
- Console.Write('\t');
- Console.Write(header);
- }
- Console.WriteLine();
- for (int i = 0; i < 4; i++)
- {
- Console.Write(BCalcWrapper.PLAYERS[i]);
- for (int j = 0; j < 5; j++)
- {
- Console.Write('\t');
- Console.Write(ddTable[i, j].ToString());
- }
- Console.WriteLine();
- }
- }
- }
-}
diff --git a/pybcdd/PBNBoard.cs b/pybcdd/PBNBoard.cs
deleted file mode 100644
index 7578fbe..0000000
--- a/pybcdd/PBNBoard.cs
+++ /dev/null
@@ -1,390 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Text.RegularExpressions;
-
-namespace BCDD
-{
- class PBNField
- {
- public String Key;
- public String Value;
- public String RawField;
-
- public PBNField() { }
-
- public PBNField(String key, String value)
- {
- this.Key = key;
- this.Value = value;
- this.RawField = String.Format("[{0} \"{1}\"]", this.Key, this.Value);
- }
-
- public PBNField(String rawData)
- {
- this.RawField = rawData;
- }
- }
-
- class FieldNotFoundException : Exception
- {
- public FieldNotFoundException() : base() { }
- public FieldNotFoundException(String msg) : base(msg) { }
- }
-
- class PBNBoard
- {
- public List<PBNField> Fields;
-
- private bool? hasOptimumResultTable = null;
- private bool? hasAbility = null;
-
- private static Regex linePattern = new Regex(@"\[(.*) ""(.*)""\]");
- private static Regex abilityPattern = new Regex(@"\b([NESW]):([0-9A-D]{5})\b");
- private static Regex optimumResultTablePattern = new Regex(@"^([NESW])\s+([CDHSN])T?\s+(\d+)$");
-
- public PBNBoard(List<string> lines)
- {
- this.Fields = new List<PBNField>();
- foreach (String line in lines)
- {
- PBNField field = new PBNField();
- field.RawField = line;
- Match lineParse = PBNBoard.linePattern.Match(line);
- if (lineParse.Success)
- {
- field.Key = lineParse.Groups[1].Value;
- field.Value = lineParse.Groups[2].Value;
- }
- this.Fields.Add(field);
- }
- }
-
- public bool HasField(String key)
- {
- foreach (PBNField field in this.Fields)
- {
- if (key.Equals(field.Key))
- {
- return true;
- }
- }
- return false;
- }
-
- public String GetField(String key)
- {
- foreach (PBNField field in this.Fields)
- {
- if (key.Equals(field.Key))
- {
- return field.Value;
- }
- }
- throw new FieldNotFoundException(key + " field not found");
- }
-
- public void DeleteField(String key)
- {
- List<PBNField> toRemove = new List<PBNField>();
- foreach (PBNField field in this.Fields)
- {
- if (key.Equals(field.Key))
- {
- toRemove.Add(field);
- }
- }
- foreach (PBNField remove in toRemove)
- {
- this.Fields.Remove(remove);
- }
- }
-
- public String GetEvent()
- {
- return this.GetField("Event");
- }
-
- public void WriteEvent(String name)
- {
- for (int i = 0; i < this.Fields.Count; i++)
- {
- if ("Board".Equals(this.Fields[i].Key))
- {
- this.Fields.Insert(i, new PBNField("Event", name));
- break;
- }
- }
- }
-
- public String GetLayout()
- {
- return this.GetField("Deal");
- }
-
- public String GetNumber()
- {
- return this.GetField("Board");
- }
-
- public String GetVulnerable()
- {
- return this.GetField("Vulnerable");
- }
-
- public String GetDealer()
- {
- return this.GetField("Dealer");
- }
-
- public MatchCollection ValidateAbility(String ability)
- {
- MatchCollection matches = PBNBoard.abilityPattern.Matches(ability);
- if (matches.Count != 4)
- {
- this.hasAbility = false;
- throw new DDTableInvalidException("Invalid Ability line: " + ability);
- }
- List<String> players = new List<String>();
- foreach (Match match in matches)
- {
- if (players.Contains(match.Groups[1].Value))
- {
- this.hasAbility = false;
- throw new DDTableInvalidException("Duplicate entry in Ability: " + match.Groups[0].Value);
- }
- else
- {
- players.Add(match.Groups[1].Value);
- }
- }
- this.hasAbility = true;
- return matches;
- }
-
- public String GetAbility()
- {
- return this.GetField("Ability");
- }
-
- public void DeleteAbility()
- {
- this.DeleteField("Ability");
- }
-
- public void WriteAbility(int[,] ddTable)
- {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < 4; i++)
- {
- sb.Append(BCalcWrapper.PLAYERS[i]);
- sb.Append(':');
- for (int j = 4; j >= 0; j--)
- {
- sb.Append((char)(ddTable[i, j] > 9 ? 'A' + ddTable[i, j] - 10 : ddTable[i, j] + '0'));
- }
- if (i < 3)
- {
- sb.Append(' ');
- }
- }
- String abilityStr = sb.ToString();
- this.Fields.Add(new PBNField("Ability", abilityStr));
- }
-
- public String GetMinimax()
- {
- return this.GetField("Minimax");
- }
-
- public void DeleteMinimax()
- {
- this.DeleteField("Minimax");
- }
-
- public void WriteMinimax(ParContract contract)
- {
- String minimax;
- if (contract.Score == 0)
- {
- minimax = "7NS0";
- }
- else
- {
- minimax = String.Format("{0}{1}{2}{3}{4}", contract.Level, contract.Denomination, contract.Doubled ? "D" : "", contract.Declarer, contract.Score);
- }
- this.Fields.Add(new PBNField("Minimax", minimax));
- }
-
- public String GetOptimumScore()
- {
- return this.GetField("OptimumScore");
- }
-
- public void DeleteOptimumScore()
- {
- this.DeleteField("OptimumScore");
- }
-
- public void WriteOptimumScore(ParContract contract)
- {
- this.Fields.Add(new PBNField("OptimumScore", String.Format("NS {0}", contract.Score)));
- }
-
- public String GetOptimumResult()
- {
- return this.GetField("OptimumResult");
- }
-
- public List<Match> ValidateOptimumResultTable(List<String> table)
- {
- List<Match> matches = new List<Match>();
- List<String> duplicates = new List<String>();
- foreach (String line in table)
- {
- Match match = PBNBoard.optimumResultTablePattern.Match(line);
- if (!match.Success)
- {
- this.hasOptimumResultTable = false;
- throw new DDTableInvalidException("Invalid OptimumResultTable line: " + line);
- }
- String position = match.Groups[1].Value + " - " + match.Groups[2].Value;
- if (duplicates.Contains(position))
- {
- this.hasOptimumResultTable = false;
- throw new DDTableInvalidException("Duplicate OptimumResultTable line: " + line);
- }
- else
- {
- duplicates.Add(position);
- }
- matches.Add(match);
- }
- this.hasOptimumResultTable = true;
- return matches;
- }
-
- public List<String> GetOptimumResultTable()
- {
- bool fieldFound = false;
- List<String> result = new List<String>();
- foreach (PBNField field in this.Fields)
- {
- if ("OptimumResultTable".Equals(field.Key))
- {
- fieldFound = true;
- }
- else
- {
- if (fieldFound)
- {
- if (field.Key == null)
- {
- result.Add(field.RawField);
- }
- else
- {
- break;
- }
- }
- }
- }
- if (!fieldFound)
- {
- this.hasOptimumResultTable = false;
- throw new FieldNotFoundException("OptimumResultTable field not found");
- }
- return result;
- }
-
- public void DeleteOptimumResultTable()
- {
- bool fieldFound = false;
- List<PBNField> toRemove = new List<PBNField>();
- foreach (PBNField field in this.Fields)
- {
- if ("OptimumResultTable".Equals(field.Key))
- {
- fieldFound = true;
- toRemove.Add(field);
- }
- else
- {
- if (fieldFound)
- {
- if (field.Key == null)
- {
- toRemove.Add(field);
- }
- else
- {
- break;
- }
- }
- }
- }
- foreach (PBNField remove in toRemove)
- {
- this.Fields.Remove(remove);
- }
- }
-
- public void WriteOptimumResultTable(int[,] ddTable)
- {
- this.Fields.Add(new PBNField("OptimumResultTable", @"Declarer;Denomination\2R;Result\2R"));
- for (int i = 0; i < 4; i++)
- {
- for (int j = 0; j < 5; j++)
- {
- this.Fields.Add(new PBNField(String.Format("{0} {1}{2} {3}", BCalcWrapper.PLAYERS[i], BCalcWrapper.DENOMINATIONS[j], (BCalcWrapper.DENOMINATIONS[j] == 'N') ? "T" : "", ddTable[i, j])));
- }
- }
- }
-
- public void SaveParContract(ParContract contract)
- {
- this.DeleteOptimumScore();
- this.WriteOptimumScore(contract); // we're not writing DDS custom fields, just parse them
- this.DeleteMinimax();
- this.WriteMinimax(contract);
- }
-
- public void SaveDDTable(int[,] ddTable)
- {
- if (this.hasOptimumResultTable == null)
- {
- try
- {
- List<Match> optimumResultTable = this.ValidateOptimumResultTable(this.GetOptimumResultTable());
- this.hasOptimumResultTable = true;
- }
- catch (FieldNotFoundException)
- {
- this.hasOptimumResultTable = false;
- }
- }
- if (this.hasOptimumResultTable == false)
- {
- this.DeleteOptimumResultTable();
- this.WriteOptimumResultTable(ddTable);
- }
- if (this.hasAbility == null)
- {
- try
- {
- MatchCollection ability = this.ValidateAbility(this.GetAbility());
- this.hasAbility = true;
- }
- catch (FieldNotFoundException)
- {
- this.hasAbility = false;
- }
- }
- if (this.hasAbility == false)
- {
- this.DeleteAbility();
- this.WriteAbility(ddTable);
- }
- }
- }
-}
diff --git a/pybcdd/PBNFile.cs b/pybcdd/PBNFile.cs
deleted file mode 100644
index b26f84c..0000000
--- a/pybcdd/PBNFile.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.IO;
-
-namespace BCDD
-{
- class PBNFile
- {
- public List<PBNBoard> Boards;
-
- private String filename;
- private String tmpFileName;
-
- StreamWriter outputFile;
-
- public PBNFile(String filename)
- {
- this.filename = filename;
- this.Boards = new List<PBNBoard>();
- String[] contents = File.ReadAllLines(this.filename).Select(l => l.Trim()).ToArray();
- List<String> lines = new List<String>();
- foreach (String line in contents)
- {
- if (line.Length == 0)
- {
- if (lines.Count > 0)
- {
- this.Boards.Add(new PBNBoard(lines));
- lines = new List<String>();
- }
- }
- else
- {
- lines.Add(line);
- }
- }
- if (lines.Count > 0)
- {
- this.Boards.Add(new PBNBoard(lines));
- }
- if (!this.Boards[0].HasField("Event"))
- {
- this.Boards[0].WriteEvent("");
- }
- }
-
- public void WriteBoard(PBNBoard board)
- {
- if (this.outputFile == null)
- {
- this.tmpFileName = Path.GetTempFileName();
- this.outputFile = new StreamWriter(new FileStream(this.tmpFileName, FileMode.Create), Encoding.UTF8);
- }
- foreach (PBNField field in board.Fields)
- {
- this.outputFile.WriteLine(field.RawField);
- }
- this.outputFile.WriteLine();
- }
-
- public void Save()
- {
- if (this.outputFile == null)
- {
- throw new IOException("No boards written to PBN file, unable to save it.");
- }
- this.outputFile.Flush();
- this.outputFile.Close();
- File.Delete(this.filename);
- File.Move(this.tmpFileName, this.filename);
- }
- }
-}
diff --git a/pybcdd/ParContract.cs b/pybcdd/ParContract.cs
deleted file mode 100644
index 9255ca5..0000000
--- a/pybcdd/ParContract.cs
+++ /dev/null
@@ -1,227 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace BCDD
-{
- class ParContract
- {
- public int Level = 0;
- public char Denomination;
- public char Declarer;
- public bool Doubled = false;
- public int Score = 0;
-
- public ParContract() { }
-
- public ParContract(int level, char denom, char declarer, bool doubled, int score)
- {
- this.Level = level;
- this.Denomination = denom;
- this.Declarer = declarer;
- this.Doubled = doubled;
- this.Score = score;
- }
-
- public ParContract Validate()
- {
- if (this.Score == 0)
- {
- return this;
- }
- if (this.Level < 1 || this.Level > 7)
- {
- throw new ParScoreInvalidException("Invalid par contract level: " + this.Level.ToString());
- }
- if (!"CDHSN".Contains(this.Denomination))
- {
- throw new ParScoreInvalidException("Invalid par contract denomination: " + this.Denomination);
- }
- if (!"NESW".Contains(this.Declarer))
- {
- throw new ParScoreInvalidException("Invalid par contract declarer: " + this.Declarer);
- }
- return this;
- }
-
- override public String ToString()
- {
- if (this.Score == 0)
- {
- return "PASS";
- }
- String contract = this.Level.ToString() + this.Denomination;
- String risk = this.Doubled ? "x" : "";
- String declarer = " " + this.Declarer;
- String result = " " + this.Score.ToString("+#;-#;0");
- return contract + risk + declarer + result;
- }
-
- public override bool Equals(object other)
- {
- ParContract obj = (ParContract)(other);
- return this.Level == obj.Level && this.Denomination == obj.Denomination && this.Score == obj.Score;
- }
-
- public override int GetHashCode()
- {
- return this.Score + this.Level + 10000 * this.Denomination;
- }
-
- public int CalculateScore(int tricks, bool vulnerable = false)
- {
- if (this.Level == 0)
- {
- return 0;
- }
- int score = 0;
- if (this.Level + 6 > tricks)
- {
- int undertricks = this.Level + 6 - tricks;
- if (this.Doubled)
- {
- do
- {
- if (undertricks == 1) // first undertrick: 100 non-vul, 200 vul
- {
- score -= vulnerable ? 200 : 100;
- }
- else
- {
- if (undertricks <= 3 && !vulnerable) // second non-vul undertrick: 200
- {
- score -= 200;
- }
- else // further undertricks: 300
- {
- score -= 300;
- }
- }
- undertricks--;
- }
- while (undertricks > 0);
- }
- else
- {
- score = vulnerable ? -100 : -50;
- score *= undertricks;
- }
- }
- else
- {
- int parTricks = this.Level;
- do
- {
- if (this.Denomination == 'N' && parTricks == 1) // first non-trump trick: 40
- {
- score += 40;
- }
- else // other tricks
- {
- switch (this.Denomination)
- {
- case 'N':
- case 'S':
- case 'H':
- score += 30;
- break;
- case 'D':
- case 'C':
- score += 20;
- break;
- }
- }
- parTricks--;
- }
- while (parTricks > 0);
- if (this.Doubled)
- {
- score *= 2;
- }
- score += (score >= 100) ? (vulnerable ? 500 : 300) : 50; // game premium
- if (this.Level == 7) // grand slam premium
- {
- score += vulnerable ? 1500 : 1000;
- }
- else if (this.Level == 6) // small slam premium
- {
- score += vulnerable ? 750 : 500;
- }
- if (this.Doubled)
- {
- score += 50;
- }
- int overtricks = tricks - this.Level - 6;
- score += this.Doubled
- ? (vulnerable ? 200 : 100) * overtricks // (re-)double overtricks: 100/200/200/400
- : overtricks * ((this.Denomination == 'C' || this.Denomination == 'D') ? 20 : 30); // undoubled overtricks
- }
- if (this.Declarer == 'E' || this.Declarer == 'W')
- {
- score = -score;
- }
- return score;
- }
-
- public bool Higher(ParContract obj)
- {
- return (this.Level > obj.Level
- || (this.Level == obj.Level
- && Array.IndexOf(BCalcWrapper.DENOMINATIONS, this.Denomination) > Array.IndexOf(BCalcWrapper.DENOMINATIONS, obj.Denomination)));
- }
-
- public ParContract GetDefense(int[,] ddTable, bool vulnerable)
- {
- int declarerIndex = Array.IndexOf(BCalcWrapper.PLAYERS, this.Declarer);
- int denominationIndex = Array.IndexOf(BCalcWrapper.DENOMINATIONS, this.Denomination);
- if (this.Level != 0 && this.Level + 6 <= ddTable[declarerIndex, denominationIndex])
- {
- List<int> defendersIndexes = new List<int>();
- defendersIndexes.Add((declarerIndex + 1) & 3);
- defendersIndexes.Add((declarerIndex + 3) & 3);
- List<ParContract> possibleDefense = new List<ParContract>();
- int scoreSquared = this.Score * this.Score;
- for (int i = 0; i < 5; i++)
- {
- int level = this.Level;
- if (i <= denominationIndex)
- {
- level++;
- }
- if (level <= 7)
- {
- foreach (int defender in defendersIndexes)
- {
- if (level + 6 > ddTable[defender, i])
- {
- ParContract defense = new ParContract(level, BCalcWrapper.DENOMINATIONS[i], BCalcWrapper.PLAYERS[defender], true, 0);
- defense.Score = defense.CalculateScore(ddTable[defender, i], vulnerable);
- if (scoreSquared > this.Score * defense.Score)
- {
- possibleDefense.Add(defense);
- }
- }
- }
- }
- }
- if (possibleDefense.Count > 0)
- {
- possibleDefense.Sort((x, y) => Math.Abs(x.Score - this.Score).CompareTo(Math.Abs(y.Score - this.Score)));
- ParContract optimumDefense = possibleDefense.Last();
- possibleDefense = possibleDefense.FindAll(x => x.Score == optimumDefense.Score);
- foreach (ParContract defense in possibleDefense)
- {
- // Lowest from the most profitable sacrifices
- if (optimumDefense.Higher(defense))
- {
- optimumDefense = defense;
- }
- }
- return optimumDefense;
- }
- }
- return null;
- }
- }
-}
diff --git a/pybcdd/ParScore.cs b/pybcdd/ParScore.cs
deleted file mode 100644
index 068be90..0000000
--- a/pybcdd/ParScore.cs
+++ /dev/null
@@ -1,233 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Text.RegularExpressions;
-
-namespace BCDD
-{
- class ParScoreInvalidException : FieldNotFoundException
- {
- public ParScoreInvalidException() : base() { }
- public ParScoreInvalidException(String msg) : base(msg) { }
- }
-
- class ParScore
- {
- private PBNBoard board;
- private static Regex pbnContractPattern = new Regex(@"(\d)([CDHSN])(X?)\s+([NESW])");
- private static Regex pbnScorePattern = new Regex(@"(NS|EW)\s+(-?\d})");
- private static Regex jfrContractPattern = new Regex(@"^(\d)([CDHSN])(D?)([NESW])(-?\d+)$");
-
- public ParScore(PBNBoard board)
- {
- this.board = board;
- }
-
- public ParContract GetPBNParContract()
- {
- String contractField = this.board.GetOptimumResult();
- if ("Pass".Equals(contractField))
- {
- return new ParContract();
- }
- Match contractMatch = ParScore.pbnContractPattern.Match(contractField);
- if (!contractMatch.Success)
- {
- throw new ParScoreInvalidException("Invalid format for OptimumResult field: " + contractField);
- }
- String scoreField = this.board.GetOptimumScore();
- Match scoreMatch = ParScore.pbnScorePattern.Match(scoreField);
- if (!scoreMatch.Success)
- {
- throw new ParScoreInvalidException("Invalid format for OptimumScore field: " + scoreField);
- }
- int score = Int16.Parse(scoreMatch.Groups[2].Value);
- if ("EW".Equals(scoreMatch.Groups[1].Value))
- {
- score = -score;
- }
- ParContract contract = new ParContract(Int16.Parse(contractMatch.Groups[1].Value),
- contractMatch.Groups[2].Value[0],
- contractMatch.Groups[4].Value[0],
- "X".Equals(contractMatch.Groups[3].Value),
- score);
- return contract.Validate();
- }
-
- public ParContract GetJFRParContract()
- {
- String parString = this.board.GetMinimax();
- Match parMatch = ParScore.jfrContractPattern.Match(parString);
- if (!parMatch.Success)
- {
- throw new ParScoreInvalidException("Invalid format for Minimax field: " + parString);
- }
- if ("0".Equals(parMatch.Groups[4].Value))
- {
- return new ParContract(); // pass-out
- }
- ParContract contract = new ParContract(Int16.Parse(parMatch.Groups[1].Value),
- parMatch.Groups[2].Value[0],
- parMatch.Groups[4].Value[0],
- "D".Equals(parMatch.Groups[3].Value),
- Int16.Parse(parMatch.Groups[5].Value));
- return contract.Validate();
- }
-
- private bool determineVulnerability(String vulnerability, char declarer)
- {
- vulnerability = vulnerability.ToUpper();
- return "ALL".Equals(vulnerability) || "BOTH".Equals(vulnerability)
- || (!"LOVE".Equals(vulnerability) && !"NONE".Equals(vulnerability) && vulnerability.Contains(declarer));
- }
-
- private ParContract getHighestMakeableContract(int[,] ddTable, bool forNS = true, bool forEW = true)
- {
- ParContract contract = new ParContract();
- int tricks = 0;
- for (int i = 3; i >= 0; i--)
- {
- if ((i % 2 == 0 && forNS)
- || (i % 2 == 1 && forEW))
- {
- for (int j = 0; j < 5; j++)
- {
- int level = ddTable[i, j] - 6;
- if (level > contract.Level
- || (level == contract.Level && j > Array.IndexOf(BCalcWrapper.DENOMINATIONS, contract.Denomination)))
- {
- contract.Level = level;
- contract.Denomination = BCalcWrapper.DENOMINATIONS[j];
- contract.Declarer = BCalcWrapper.PLAYERS[i];
- tricks = ddTable[i, j];
- }
- }
- }
- }
- String vulnerability = this.board.GetVulnerable().ToUpper();
- bool vulnerable = this.determineVulnerability(vulnerability, contract.Declarer);
- contract.Score = contract.CalculateScore(tricks, vulnerable);
- return contract;
- }
-
- public ParContract GetDDTableParContract(int[,] ddTable)
- {
- String dealer = this.board.GetDealer();
- String vulnerability = this.board.GetVulnerable().ToUpper();
- ParContract nsHighest = this.getHighestMakeableContract(ddTable, true, false);
- ParContract ewHighest = this.getHighestMakeableContract(ddTable, false, true);
- bool nsPlaying = ("N".Equals(dealer) || "S".Equals(dealer));
- if (nsHighest == ewHighest)
- {
- return nsPlaying ? nsHighest.Validate() : ewHighest.Validate();
- }
- ParContract highest = nsHighest.Higher(ewHighest) ? nsHighest : ewHighest;
- ParContract otherSideHighest = nsHighest.Higher(ewHighest) ? ewHighest : nsHighest;
- nsPlaying = ('N'.Equals(highest.Declarer) || 'S'.Equals(highest.Declarer));
- bool defenseVulnerability = this.determineVulnerability(vulnerability, nsPlaying ? 'E' : 'N');
- ParContract highestDefense = highest.GetDefense(ddTable, defenseVulnerability);
- if (highestDefense != null)
- {
- // Highest contract has profitable defense
- return highestDefense.Validate();
- }
- int denominationIndex = Array.IndexOf(BCalcWrapper.DENOMINATIONS, highest.Denomination);
- int declarerIndex = Array.IndexOf(BCalcWrapper.PLAYERS, highest.Declarer);
- List<int> playerIndexes = new List<int>();
- playerIndexes.Add(declarerIndex);
- playerIndexes.Add((declarerIndex + 2) & 3);
- bool vulnerable = this.determineVulnerability(vulnerability, highest.Declarer);
- int scoreSquared = highest.Score * highest.Score;
- List<ParContract> possibleOptimums = new List<ParContract>();
- for (int i = 0; i < 5; i++)
- {
- foreach (int player in playerIndexes)
- {
- int level = highest.Level;
- if (i > denominationIndex)
- {
- level--;
- }
- while (level > 0)
- {
- ParContract contract = new ParContract(level, BCalcWrapper.DENOMINATIONS[i], BCalcWrapper.PLAYERS[player], false, 0);
- contract.Score = contract.CalculateScore(ddTable[player, i], vulnerable);
- if (otherSideHighest.Higher(contract))
- {
- // Contract is lower than other side's contract
- break;
- }
- if (highest.Score * contract.Score > 0)
- {
- // Contract makes
- if (Math.Abs(contract.Score) >= Math.Abs(highest.Score))
- {
- // Contract is profitable
- ParContract defense = contract.GetDefense(ddTable, defenseVulnerability);
- if (defense != null && (contract.Score * contract.Score > contract.Score * defense.Score))
- {
- // Contract has defense
- possibleOptimums.Add(defense);
- // So lower contracts will too.
- break;
- }
- else
- {
- // Contract does not have defense
- possibleOptimums.Add(contract);
- }
- }
- else
- {
- // Contract is not profitable
- break;
- }
- }
- level--;
- }
- }
- }
- foreach (ParContract contract in possibleOptimums)
- {
- if ((Math.Abs(contract.Score) > Math.Abs(highest.Score)))
- {
- // Contract is more profitable
- highest = contract;
- }
- else
- {
- if (contract.Score == highest.Score)
- {
- if (highest.Higher(contract))
- {
- // Equally profitable, but lower
- highest = contract;
- }
- }
- }
- }
- return highest.Validate();
- }
-
- public ParContract GetParContract(int[,] ddTable)
- {
- try
- {
- return this.GetJFRParContract();
- }
- catch (FieldNotFoundException)
- {
- try
- {
- return this.GetPBNParContract();
- }
- catch (FieldNotFoundException)
- {
- return this.GetDDTableParContract(ddTable);
- }
- }
- }
-
- }
-}