Skip to content

Commit

Permalink
Loading DLL's as singletons
Browse files Browse the repository at this point in the history
Clean up in logging with --verbose
Added config file
  • Loading branch information
ThorvaldAagaard committed Dec 11, 2024
1 parent 971677c commit cf6f426
Show file tree
Hide file tree
Showing 16 changed files with 669 additions and 199 deletions.
4 changes: 3 additions & 1 deletion install/assemble.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ robocopy dist\BENGUI "BENAll" /E
robocopy dist\BEN "BENAll" /E
robocopy dist\table_manager_client "BENAll" /E
robocopy ..\src\nn "BENAll\nn" *tf2.py*
robocopy ..\bin "BENAll\bin" /E
robocopy ..\bin "BENAll\bin" /E
copy ..\src\ben.ico "BENAll"
copy ..\src\logo.png "BENAll"
45 changes: 31 additions & 14 deletions src/bba/BBA.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import sys
import os
import util

from threading import Lock
# Get the directory of the current script
script_dir = os.path.dirname(os.path.abspath(__file__))
# Calculate the parent directory
Expand Down Expand Up @@ -34,6 +34,34 @@

class BBABotBid:

_dll_loaded = None # Class-level attribute to store the DLL singleton
_lock = Lock() # Lock to ensure thread-safe initialization

@classmethod
def get_dll(cls, verbose = False):
"""Access the loaded DLL classes."""
if cls._dll_loaded is None:
with cls._lock: # Ensure only one thread can enter this block at a time
if cls._dll_loaded is None: # Double-checked locking
try:
util.load_dotnet_framework_assembly(EPBot_PATH, verbose)
from EPBot86 import EPBot
# Load the .NET assembly and import the types and classes from the assembly
if verbose:
print(f"EPBot Version (DLL): {EPBot().version()}")
cls._dll_loaded = {
"EPBot": EPBot,
}
except Exception as ex:
# Provide a message to the user if the assembly is not found
print(f"{Fore.RED}Error: Unable to load EPBot86.dll. Make sure the DLL is in the ./bin directory")
print("Make sure the dll is not blocked by OS (Select properties and click unblock)")
print(f"Make sure the dll is not writeprotected{Fore.RESET}")
print('Error:', ex)
sys.exit(1)
return cls._dll_loaded


# Define constants for system types and conventions
C_NS = 0
C_WE = 1
Expand All @@ -45,21 +73,10 @@ class BBABotBid:
def __init__(self, ns_system, ew_system, position, hand, vuln, dealer, scoring_matchpoint, verbose):


dll = BBABotBid.get_dll(verbose) # Retrieve the loaded DLL classes through the singleton
EPBot = dll["EPBot"]
self.verbose = verbose
# Load the .NET assembly
try:
util.load_dotnet_framework_assembly(EPBot_PATH, verbose)
from EPBot86 import EPBot
# Load the .NET assembly and import the types and classes from the assembly
if self.verbose:
print(f"EPBot Version (DLL): {EPBot().version()}")
except Exception as ex:
# Provide a message to the user if the assembly is not found
print(f"{Fore.RED}Error: Unable to load EPBot86.dll. Make sure the DLL is in the ./bin directory")
print("Make sure the dll is not blocked by OS (Select properties and click unblock)")
print(f"Make sure the dll is not writeprotected{Fore.RESET}")
print('Error:', ex)
sys.exit(1)
if ew_system == None and ns_system == None:
return
if ew_system == '-1' or ns_system == '-1':
Expand Down
42 changes: 22 additions & 20 deletions src/bots.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,8 @@ def get_binary_contract(self, position, vuln, hand_str, dummy_str, n_cards=32):
def evaluate_rescue_bid(self, auction, passout, samples, candidate_bid, quality, my_bid_no ):
# check configuration
if self.verbose:
print("Checking if we should evaluate rescue bid", self.models.check_final_contract, len(samples))
print("Auction",auction, passout)
print("Candidate", candidate_bid, quality)
print("Checking if we should evaluate rescue bid", self.models.check_final_contract, "Samples:",len(samples))
print("Auction",auction, "Passout?" ,passout,"Candidate bid", candidate_bid.bid, "Sample quality: ",quality)
if not self.models.check_final_contract:
return False

Expand Down Expand Up @@ -138,7 +137,7 @@ def evaluate_rescue_bid(self, auction, passout, samples, candidate_bid, quality,
return True

if candidate_bid.expected_score is None:
# We will prepare data for calculating recue bid
# We will prepare data for calculating rescue bid
return True

# We only evaluate if the score is below a certain value, so if simulation give a score above this we do not try to rescue
Expand Down Expand Up @@ -231,7 +230,7 @@ def bid(self, auction):
hands_np_as_pbn = self.translate_hands(hands_np, self.hand_str, sample_count)
for candidate in candidates:
if self.verbose:
print(f" {candidate.bid.ljust(4)} {candidate.insta_score:.3f} Samples: {len(hands_np)}")
print(f"Bid: {candidate.bid.ljust(4)} {candidate.insta_score:.3f}")
auctions_np = self.bidding_rollout(auction, candidate.bid, hands_np)

t_start = time.time()
Expand Down Expand Up @@ -269,9 +268,9 @@ def bid(self, auction):

# Format the average tricks string
average_tricks_str = ", ".join(map(str, average_tricks))
samples[idx] += f" \n {auc} ({average_tricks_str}) "
samples[idx] += f" | {auc} ({average_tricks_str}) "
else:
samples[idx] += f" \n {auc}"
samples[idx] += f" | {auc}"

if self.verbose:
print("tricks", np.mean(decoded_tricks))
Expand Down Expand Up @@ -499,7 +498,7 @@ def bid(self, auction):
result = {}
for i in range(len(contracts[0])):
# We should make calculations on this, so 4H, %h or even 6H is added, if tricks are fin
if contracts[0][i] > 0.3:
if contracts[0][i] > 0.2:
y = np.zeros(5)
suit = bidding.ID2BID[i][1]
strain_i = 'NSHDC'.index(suit)
Expand Down Expand Up @@ -532,15 +531,19 @@ def bid(self, auction):
contract = bidding.ID2BID[contract_id]


if self.verbose:
print(result)

if score < self.models.min_bidding_trust_for_sample_when_rescue:
if self.verbose:
print(self.hand_str, [sample[(self.seat + 2) % 4]])
print(f"Skipping sample below level{self.models.min_bidding_trust_for_sample_when_rescue} {contract}-{tricks} score {score:.3f}")
#if self.verbose:
# print(self.hand_str, [sample[(self.seat + 2) % 4]])
# if score == 0:
# print(f"No obvious rescue contract")
# else:
# print(f"Skipping sample below level: {self.models.min_bidding_trust_for_sample_when_rescue} {contract} {tricks} score {score:.3f}")
continue

if self.verbose:
print(result)

while not bidding.can_bid(contract, auction) and contract_id < 35:
contract_id += 5
contract = bidding.ID2BID[contract_id]
Expand Down Expand Up @@ -587,8 +590,8 @@ def bid(self, auction):
# If we go down we assume we are doubled
doubled = tricks < level + 6
score = scoring.score(contract + ("X" if doubled else ""), self.vuln, tricks)
if self.verbose:
print(result, score)
#if self.verbose:
# print(result, score)
if contract not in alternatives:
alternatives[contract] = []
alternatives[contract].append({"score": score, "tricks": tricks})
Expand Down Expand Up @@ -1283,7 +1286,7 @@ def find_opening_lead(self, auction):
opening_lead = candidate_cards[0].card.code()

if self.verbose:
print("Samples quality:", quality)
print(f"Samples quality: {quality:.3f}")
for card in candidate_cards:
print(card)
if opening_lead % 8 == 7:
Expand Down Expand Up @@ -1427,7 +1430,7 @@ def double_dummy_estimates(self, lead_card_indexes, contract, accepted_samples):
if (k != 3):
hand_str += '.'
if self.verbose:
print("Opening lead being examined: ", Card.from_code(opening_lead52), n_accepted)
print("Opening lead being examined: ", Card.from_code(opening_lead52), n_accepted, end="")
t_start = time.time()
hands_pbn = []
for i in range(n_accepted):
Expand Down Expand Up @@ -1702,7 +1705,7 @@ def play_card(self, trick_i, leader_i, current_trick52, tricks52, players_states
pimc_resp_cards = self.pimc.nextplay(self.player_i, shown_out_suits, self.missing_cards)
if self.verbose:
print("PIMCDef result:")
print("\n".join(f"{Card.from_code(k)}:\n{v}" for k, v in pimc_resp_cards.items()))
print("\n".join(f"{Card.from_code(k)}: {v}" for k, v in pimc_resp_cards.items()))

assert pimc_resp_cards is not None, "PIMCDef result is None"
if self.models.pimc_ben_dd_defending:
Expand Down Expand Up @@ -1938,7 +1941,6 @@ def pick_card_after_pimc_eval(self, trick_i, leader_i, current_trick, tricks52,
msg=msg + (f"|trump adjust={trump_adjust}" if trump_adjust != 0 and (card32 // 8) + 1 == self.strain_i else "")
))


if self.models.use_real_imp_or_mp:
if self.models.matchpoint:
candidate_cards = sorted(enumerate(candidate_cards), key=lambda x: (x[1].expected_score_mp, x[1].expected_tricks_dd, x[1].insta_score, -x[0]), reverse=True)
Expand All @@ -1954,7 +1956,7 @@ def pick_card_after_pimc_eval(self, trick_i, leader_i, current_trick, tricks52,

if self.verbose:
for i in range(len(candidate_cards)):
print(candidate_cards[i].card, f"{candidate_cards[i].insta_score}:.3f", candidate_cards[i].expected_tricks_dd, round(5*candidate_cards[i].p_make_contract, 1), candidate_cards[i].expected_score_dd, int(candidate_cards[i].expected_tricks_dd * 10) / 10)
print(candidate_cards[i].card, f"{candidate_cards[i].insta_score:.3f}", candidate_cards[i].expected_tricks_dd, round(5 * candidate_cards[i].p_make_contract, 1), int(candidate_cards[i].expected_tricks_dd * 10) / 10)

if self.models.matchpoint:
if self.models.pimc_ben_dd_declaring or self.models.pimc_ben_dd_defending:
Expand Down
3 changes: 2 additions & 1 deletion src/carding.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ def select_right_card_for_play(candidate_cards, rng, contract, models, hand_str,
#print("Tricks", play['Plays'][0]['Tricks'], " Max: ",max(len(suits_north),len(suits_south)))
if play['Plays'][0]['Tricks'] == max(len(suits_north),len(suits_south)):
if play['Plays'][0]['Percentage'] == 100:
print(f"SuitC dropped as we can take all tricks {current_count} {original_count} ")
if verbose:
print(f"SuitC dropped as we can take all tricks {current_count} {original_count} ")
return candidate_cards[0].card, who
# We can have more than one play for MAX
# So currently we are then selecting lowest card. Should that be different
Expand Down
14 changes: 7 additions & 7 deletions src/config/default.conf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[models]
name = GIB as on BBO
name = BEN Sayc
# Model version 1 drops state for bidding, and introduce different system for NS and EW
# Model version 2 includes 4 bids when making the lookup
model_version = 3
Expand All @@ -19,8 +19,8 @@ use_bba = False
# Instead of the neural network, check BBA if it is RKC and get the correct bid from BBA
use_bba_to_count_aces = True
# Use 2/1 in BBA
bba_ns = BBA/CC/GIB-BBO.bbsa
bba_ew = BBA/CC/GIB-BBO.bbsa
bba_ns = BBA/CC/BEN-SAYC.bbsa
bba_ew = BBA/CC/BEN-SAYC.bbsa
# Playing matchpoint? Otherwise it is teams
matchpoint = False
# Do not print warnings about bad models, training etc in the output
Expand All @@ -31,10 +31,10 @@ contract = models/TF2models/Contract_2024-12-09-E50.keras
trick = models/TF2models/Tricks_2024-12-09-E50.keras

[bidding]
bidder = models/TF2models/GIB-BBO-8712_2024-12-01-E44.keras
bidder = models/TF2models/BEN-Sayc-8712_2024-11-29-E49.keras
# only used for sampling
opponent = models/TF2models/GIB-BBO-8712_2024-12-01-E44.keras
info = models/TF2models/GIB-BBOInfo-8712_2024-11-30-E50.keras
opponent = models/TF2models/BEN-Sayc-8712_2024-11-29-E49.keras
info = models/TF2models/BEN-SaycInfo-8712_2024-11-29-E71.keras
# If there are multiple bids over this threshold make a simulation for the bids, using an array we can lower the trust the more we bid
search_threshold = [0.10, 0.07, 0.06, 0.05, 0.04, 0.03, 0.03]
# If there is bid above this threshold, make that bid ignoring other bids
Expand Down Expand Up @@ -189,7 +189,7 @@ pimc_constraints_each_trick = True
# Stop evaluation after finding the number of playouts unless pimc_wait expires before
pimc_max_playouts = 200
# Max number of threads PIMC is allowed to use
pimc_max_threads = 6
pimc_max_threads = 12
# If singleton, just play it without evaluating it
autoplaysingleton = True
# PIMC trust NN. We can filter away play, that is not suggested by the neural network
Expand Down
Loading

0 comments on commit cf6f426

Please sign in to comment.