From 8d5b2ac40b6d7f4cbe06450983fbecead4a8a662 Mon Sep 17 00:00:00 2001 From: thorvald Date: Sun, 21 Apr 2024 00:46:51 +0200 Subject: [PATCH] API-updates and generate PBN from game.py --- .gitignore | 2 ++ src/bots.py | 14 +++++----- src/dools.py | 24 ----------------- src/frontend/api.html | 9 +++++++ src/game.py | 60 ++++++++++++++++++++++++++++++++++++++++++- src/gameapi.py | 27 +++++++++++++++---- 6 files changed, 98 insertions(+), 38 deletions(-) delete mode 100644 src/dools.py diff --git a/.gitignore b/.gitignore index b3333c44..30ef37eb 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,5 @@ scripts/training/data/WBC/play.txt scripts/match/bidding/default UCBC 2024/MvsM/models/1 Mill SAYC.ben scripts/training/bidding/1 Mill SAYC.ben +scripts/training/data/GIB-Thorvald.ben +scripts/training/data/BEN-UCBC.ben diff --git a/src/bots.py b/src/bots.py index 2158a8f7..aca10be6 100644 --- a/src/bots.py +++ b/src/bots.py @@ -1199,8 +1199,6 @@ def next_card_softmax(self, trick_i): def pick_card_after_pimc_eval(self, trick_i, leader_i, current_trick, players_states, card_dd, bidding_scores, quality, samples, play_status): t_start = time.time() card_softmax = self.next_card_softmax(trick_i) - #if trick_i == 4: - # xx if self.verbose: print(f'Next card response time: {time.time() - t_start:0.4f}') @@ -1231,9 +1229,9 @@ def pick_card_after_pimc_eval(self, trick_i, leader_i, current_trick, players_st )) if self.models.matchpoint: - candidate_cards = sorted(enumerate(candidate_cards), key=lambda x: (round(x[1].expected_tricks_dd, 1), round(x[1].insta_score, 2), -x[0]), reverse=True) + candidate_cards = sorted(enumerate(candidate_cards), key=lambda x: (int(x[1].expected_tricks_dd * 10) / 10, round(x[1].insta_score, 2), -x[0]), reverse=True) else: - candidate_cards = sorted(enumerate(candidate_cards), key=lambda x: (round(5*x[1].p_make_contract, 1), round(x[1].expected_tricks_dd, 1), round(x[1].insta_score, 2), -x[0]), reverse=True) + candidate_cards = sorted(enumerate(candidate_cards), key=lambda x: (round(5*x[1].p_make_contract, 1), int(x[1].expected_tricks_dd * 10), round(x[1].insta_score, 2), -x[0]), reverse=True) candidate_cards = [card for _, card in candidate_cards] @@ -1300,20 +1298,20 @@ def pick_card_after_dd_eval(self, trick_i, leader_i, current_trick, players_stat # We should probably also consider bidding_scores in this # If we have bad quality of samples we should probably just use the neural network if valid_bidding_samples >= 0: - candidate_cards = sorted(enumerate(candidate_cards), key=lambda x: (round(5*x[1].p_make_contract, 1), round(x[1].expected_tricks_dd, 1), round(x[1].insta_score, 2), -x[0]), reverse=True) + candidate_cards = sorted(enumerate(candidate_cards), key=lambda x: (round(5*x[1].p_make_contract, 1), int(x[1].expected_tricks_dd * 10) / 10, round(x[1].expected_score_dd, 1), round(x[1].insta_score, 2), -x[0]), reverse=True) candidate_cards = [card for _, card in candidate_cards] who = "NN" else: if self.models.use_biddingquality_in_eval: - candidate_cards = sorted(enumerate(candidate_cards), key=lambda x: ( round(x[1].insta_score, 2), round(5*x[1].p_make_contract, 1), round(x[1].expected_tricks_dd, 1), -x[0]), reverse=True) + candidate_cards = sorted(enumerate(candidate_cards), key=lambda x: ( round(x[1].insta_score, 2), round(5*x[1].p_make_contract, 1), int(x[1].expected_tricks_dd * 10) / 10, -x[0]), reverse=True) candidate_cards = [card for _, card in candidate_cards] - candidate_cards2 = sorted(enumerate(candidate_cards), key=lambda x: (round(x[1].expected_score_dd, 1), round(x[1].insta_score, 2), round(x[1].expected_tricks_dd, 1), -x[0]), reverse=True) + candidate_cards2 = sorted(enumerate(candidate_cards), key=lambda x: (round(x[1].expected_score_dd, 1), round(x[1].insta_score, 2), int(x[1].expected_tricks_dd * 10) / 10, -x[0]), reverse=True) candidate_cards2 = [card for _, card in candidate_cards] if candidate_cards[0].expected_score_dd < 0 and candidate_cards2[0].expected_score_dd: candidate_cards = candidate_cards2 who = "DD" else: - candidate_cards = sorted(enumerate(candidate_cards), key=lambda x: (round(5*x[1].p_make_contract, 1), round(x[1].insta_score, 2), round(x[1].expected_tricks_dd, 1), -x[0]), reverse=True) + candidate_cards = sorted(enumerate(candidate_cards), key=lambda x: (round(5*x[1].p_make_contract, 1), round(x[1].insta_score, 2), int(x[1].expected_tricks_dd * 10) / 10, -x[0]), reverse=True) candidate_cards = [card for _, card in candidate_cards] who = "Make" diff --git a/src/dools.py b/src/dools.py deleted file mode 100644 index 46164a89..00000000 --- a/src/dools.py +++ /dev/null @@ -1,24 +0,0 @@ -import numpy as np - -n_samples = 100 -PBN = "" -decl_tricks_softmax = np.zeros((n_samples, 14), dtype=np.int32) - -# Shuffle to two unknown hands -hands_pbn = [] -from ddsolver import ddsolver -dd = ddsolver.DDSolver() -# 4 suits + NT -for i in range (5): - # Lead from 2 sides - for j in range(2): - dd_solved = dd.solve(i, j, [], hands_pbn, 1) - # Only use 1st element from the result - first_key = next(iter(dd_solved)) - first_item = dd_solved[first_key] - decl_tricks_softmax[i,13 - first_item[0]] = 1 - - -# Find the suit, where we take the most tricks -# Find the bid with the best score -# scores_by_trick[i] = scoring.contract_scores_by_trick(contract, tuple(self.vuln)) \ No newline at end of file diff --git a/src/frontend/api.html b/src/frontend/api.html index 3355b119..d8a6d88d 100644 --- a/src/frontend/api.html +++ b/src/frontend/api.html @@ -152,6 +152,15 @@

Enter Board information

processLinCommand(command, param); startIndex = closePipeIndex + 1; } + // Replace "D" with "X" and "RD" with "XX" + bidSequence = bidSequence.map(item => { + if (item === "D") { + return "X"; + } else if (item === "RD") { + return "XX"; + } + return item; // Keep other items unchanged + }); document.getElementById('biddingInput').value = bidSequence.join('-').toUpperCase() const seat = document.getElementById('seatInput').value; const dealer = "NESW".indexOf(document.getElementById('dealerInput').value); diff --git a/src/game.py b/src/game.py index c84df40b..03706739 100644 --- a/src/game.py +++ b/src/game.py @@ -119,6 +119,7 @@ def set_deal(self, board_number, deal_str, auction_str, play_only = None, biddin self.vuln_ns = self.deal_data.vuln_ns self.vuln_ew = self.deal_data.vuln_ew self.trick_winners = [] + self.tricks_taken = 0 # Now you can use hash_integer as a seed hash_integer = calculate_seed(deal_str) @@ -232,7 +233,57 @@ async def run(self): 'pbn': self.deal_str, 'dict': self.to_dict() })) - + + def asPBN(self): + dealer = "NESW"[self.dealer_i] + pbn_str = "" + pbn_str += '% PBN 2.1' + pbn_str += '% EXPORT' + pbn_str += '[Event ""]\n' + pbn_str += '[Site ""]\n' + pbn_str += f'[Date "{datetime.datetime.now().date().isoformat()}"]' + pbn_str += f'[Board "{self.board_number}"]\n' + pbn_str += '[West "BEN"]\n' + pbn_str += '[North "BEN"]\n' + pbn_str += '[East "BEN"]\n' + pbn_str += '[South "BEN"]\n' + pbn_str += f'[Dealer "{dealer}"]\n' + if self.vuln_ns and self.vuln_ew: + pbn_str += '[Vulnerable "All"]\n' + if self.vuln_ns and not self.vuln_ew: + pbn_str += '[Vulnerable "NS"]\n' + if not self.vuln_ns and self.vuln_ew: + pbn_str += '[Vulnerable "EW"]\n' + if not self.vuln_ns and not self.vuln_ew: + pbn_str += '[Vulnerable "None"]\n' + pbn_str += f'[Deal "N:{self.deal_str}"]\n' + pbn_str += '[Scoring "IMP"]\n' + pbn_str += f'[Declarer "{self.contract[-1]}"]\n' + pbn_str += f'[Contract "{self.contract[:-1]}"]\n' + pbn_str += f'[Result "{self.tricks_taken}"]\n' + pbn_str += f'[Auction "{dealer}"]\n' + for i, b in enumerate(self.bid_responses, start=1): + pbn_str += (b.bid) + if i % 4 == 0: + pbn_str += "\n" + else: + pbn_str += " " + # Add an additional line break if the total number of bids is not divisible by 4 + if i % 4 != 0: + pbn_str += "\n" + pbn_str += '[Play ""]\n' + for i, c in enumerate(self.card_responses, start=1): + pbn_str += c.card.symbol() + if i % 4 == 0: + pbn_str += "\n" + else: + pbn_str += " " + pbn_str += '[HomeTeam ""]\n' + pbn_str += '[VisitTeam ""]\n' + pbn_str += '[ScoreIMP ""]\n' + pbn_str += '\n' + return pbn_str + def to_dict(self): result = { 'timestamp': time.time(), @@ -630,6 +681,7 @@ async def play(self, contract, strain_i, decl_i, auction, opening_lead52): pprint.pprint(list(zip(decoded_tricks52, trick_won_by))) self.trick_winners = trick_won_by + self.tricks_taken = card_players[3].n_tricks_taken # Print contract and result print("Contract: ",self.contract, card_players[3].n_tricks_taken, "tricks") @@ -734,6 +786,7 @@ async def main(): parser.add_argument("--config", default=f"{base_path}/config/default.conf", help="Filename for configuration") parser.add_argument("--playonly", type=bool, default=False, help="Just play, no bidding") parser.add_argument("--biddingonly", type=bool, default=False, help="Just bidding, no play") + parser.add_argument("--outputpbn", default="", help="Save each board to this PBN file") parser.add_argument("--verbose", type=bool, default=False, help="Output samples and other information during play") parser.add_argument("--seed", type=int, help="Seed for random") @@ -745,6 +798,7 @@ async def main(): playonly = args.playonly biddingonly = args.biddingonly seed = args.seed + outputpbn = args.outputpbn boards = [] if args.boards: @@ -829,6 +883,10 @@ async def main(): print('{1} Board played in {0:0.1f} seconds.'.format(time.time() - t_start, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) db[uuid.uuid4().hex] = deal + if outputpbn != "": + with open(outputpbn, "a") as file: + file.write(driver.asPBN()) + if not auto: user_input = input("\n Q to quit or any other key for next deal ") if user_input.lower() == "q": diff --git a/src/gameapi.py b/src/gameapi.py index c7ce99e0..ccfe69be 100644 --- a/src/gameapi.py +++ b/src/gameapi.py @@ -13,6 +13,8 @@ import json import os import logging +import uuid +import shelve # Set logging level to suppress warnings logging.getLogger().setLevel(logging.ERROR) @@ -307,6 +309,9 @@ def home(): @app.route('/bid') def bid(): try: + if request.args.get("tournament"): + matchpoint = request.args.get("tournament").lower() == "mp" + models.matchpoint = matchpoint # First we extract our hand hand = request.args.get("hand").replace('_','.') seat = request.args.get("seat") @@ -334,6 +339,8 @@ def bid(): hint_bot = BotBid(vuln, hand, models, sampler, position, dealer_i, verbose) bid = hint_bot.bid(auction) print("Bidding: ",bid.bid) + with shelve.open(f"{base_path}/gameapibiddb") as db: + db[uuid.uuid4().hex] = bid.to_dict() return json.dumps(bid.to_dict()) except Exception as e: print(e) @@ -343,6 +350,9 @@ def bid(): @app.route('/lead') def lead(): try: + if request.args.get("tournament"): + matchpoint = request.args.get("tournament").lower() == "mp" + models.matchpoint = matchpoint # First we extract our hand and seat hand = request.args.get("hand").replace('_','.') seat = request.args.get("seat") @@ -365,6 +375,8 @@ def lead(): card_resp.who = user print("Leading:", card_resp.card.symbol()) result = card_resp.to_dict() + with shelve.open(f"{base_path}/gameapiplaydb") as db: + db[uuid.uuid4().hex] = result return json.dumps(result) except Exception as e: print(e) @@ -374,7 +386,10 @@ def lead(): @app.route('/play') async def frontend(): - #try: + try: + if request.args.get("tournament"): + matchpoint = request.args.get("tournament").lower() == "mp" + models.matchpoint = matchpoint # First we extract the hands and seat hand_str = request.args.get("hand").replace('_','.') dummy_str = request.args.get("dummy").replace('_','.') @@ -432,12 +447,14 @@ async def frontend(): card_resp = await play_api(dealer_i, vuln[0], vuln[1], hands, models, sampler, contract, strain_i, decl_i, auction, cards, cardplayer, verbose) print("Playing:", card_resp.card.symbol()) result = card_resp.to_dict() + with shelve.open(f"{base_path}/gameapiplaydb") as db: + db[uuid.uuid4().hex] = result #print(json.dumps(result)) return json.dumps(result) - # except Exception as e: - # print(e) - # error_message = "An error occurred: {}".format(str(e)) - # return jsonify({"error": error_message}), 400 # HTTP status code 500 for internal server error + except Exception as e: + print(e) + error_message = "An error occurred: {}".format(str(e)) + return jsonify({"error": error_message}), 400 # HTTP status code 500 for internal server error def get_binary_contract(position, vuln, hand_str, dummy_str): X = np.zeros(2 + 2 * 32, dtype=np.float16)