Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unbal5 vs 4333 #48

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ setup.cfg
*.orig
.coverage
.gdb_history

.idea/sonarlint/
10 changes: 10 additions & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[Master]
# Add redeal module to path to avoid "can't import" issues.
init-hook='import sys; sys.path.append("/media/data/mycroft/PycharmProjects/redeal")'

[BASIC]
# Good variable names which should always be accepted, separated by a comma.
# "do" is the name of a function in redeal and can't be changed.
# "nt" is Notrump, immediately recognizable by any user of redeal (bridge player)
good-names=do,
nt
97 changes: 97 additions & 0 deletions mfscripts/ehaa_2_length.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""EHAA length of 2 bids checker."""

from collections import Counter
import pprint

from redeal import Evaluator, hcp, Shape, Simulation, Deal, Hand
from redeal.global_defs import Suit


DEBUG = {
"hand": False,
"hcp": True,
"is_clubs": False,
"suits": False,
}

IS_CLUBS = True
RANGE_MIN = 6
RANGE_MAX = 12

two_bid_shape = Shape.from_cond(
lambda s, h, d, c: s >= 5 or h >= 5 or d >= 5 or (c >= 5 and IS_CLUBS)
)
full_suit_eval = Evaluator(*range(14, 1, -1))


def ehaa_nt(hand: Hand) -> bool:
"""True if this is an EHAA 1NT opener"""
is_nt_shape = (
Shape("(4333)") + Shape("(4432)") + Shape("(33)(25)") + Shape("(32)(35)")
)
return is_nt_shape(hand) and hcp(hand) in (10, 11, 12)


class EHAA2length(Simulation):
"""Generate and collect stats for EHAA 2 bids."""

def __init__(self):
super().__init__()
self.bid_suit = []
self.max_length = Counter()
self.points = Counter()

def accept(self, deal: Deal) -> bool:
"""True if it's a 2 bid."""
south = deal.south
return (
south.shape in two_bid_shape
and hcp(south) in range(RANGE_MIN, RANGE_MAX + 1)
and not ehaa_nt(south)
)

def do(self, deal: Deal) -> None:
"""Get stats."""
south = deal.south
if DEBUG["hand"]:
print(south)

if DEBUG["is_clubs"] and len(south.clubs) >= 5:
other_suit = (
len(south.diamonds) >= len(south.clubs)
or len(south.hearts) >= len(south.clubs)
or len(south.spades) >= len(south.clubs)
)
print(f"{south} {other_suit}")

max_length = max(south.shape)
self.max_length[max_length] += 1
self.points[south.hcp] += 1
for suit in Suit:
if len(south[suit]) == max_length:
self.bid_suit.append(south[suit])
break # only store 1 longest suit (highest by fiat) per hand

def final(self, n_tries: int) -> None:
"""Print stats."""
print("Lengths:")
pprint.pprint(dict(self.max_length), width=1)

if DEBUG["hcp"]:
print("\nHCP:")
pprint.pprint(dict(self.points), width=1)

# sort on secondary key first (strength of suit), then primary (length)
sorted_bid_suit = sorted(self.bid_suit, key=full_suit_eval, reverse=True)
sorted_bid_suit = sorted(sorted_bid_suit, key=len, reverse=True)
if DEBUG["suits"]:
pprint.pprint(
[(str(x), len(x), full_suit_eval(x)) for x in sorted_bid_suit],
width=100,
compact=True,
)

print(f"\nMedian hand: {sorted_bid_suit[len(self.bid_suit) // 2]}")


simulation = EHAA2length()
40 changes: 40 additions & 0 deletions mfscripts/ks_payoff.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Does it hurt KS to pass borderline openers? from BBO"""

from collections import Counter

from redeal import hcp, Hand, Deal


predeal = {"S": "8 A94 AT843 K942", "N": "A93 5 K752 AT853"}
tricks_ns = Counter()
tricks_ew = Counter()
open_hand = Counter()


def preempt(hand: Hand) -> bool:
"""True if hand is a major preempt (coarse: 6-10 and 6+card major)"""
return max(len(hand.spades), len(hand.hearts)) >= 6 and 6 <= hcp(hand) <= 10


def major_opener(hand: Hand) -> bool:
"""True if hand is an 1M (or 2C) opener. Again, coarse."""
return max(len(hand.spades), len(hand.hearts)) >= 5 and hcp(hand) >= 11


def do(deal: Deal) -> None:
"""Count stats: tricks in contract, will west open/preempt?"""
tricks_ns[max(deal.dd_tricks("5CN"), deal.dd_tricks("5DS"))] += 1
tricks_ew[max(deal.dd_tricks("4SE"), deal.dd_tricks("4HW"))] += 1
open_hand["pre"] += preempt(deal.west)
open_hand["1M"] += major_opener(deal.west) or (
major_opener(deal.east) and not preempt(deal.west)
)


def final(n_tries: int) -> None:
"""Display stats."""
print(f"N-S in minor: {sorted(tricks_ns.items())}")
print(f"E-W in major: {sorted(tricks_ew.items())}")
print(f"West preempt in major: {open_hand['pre']}")
print(f"E-W open 1 Major: {open_hand['1M']}")
print(f"hands dealt: {n_tries}")
51 changes: 51 additions & 0 deletions mfscripts/normal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""No strange hands for lesson final tourney."""
from pathlib import Path

from redeal import Deal, hcp

dealvul = (
("N", "None"),
("E", "NS"),
("S", "EW"),
("W", "All"),
("N", "NS"),
("E", "EW"),
("S", "All"),
("W", "None"),
("N", "EW"),
("E", "All"),
("S", "None"),
("W", "NS"),
("N", "All"),
("E", "None"),
("S", "NS"),
("W", "EW"),
)


def accept(deal: Deal) -> bool:
"""Boring hands. No 30+ in one partnership, no 7+ card suits."""

if hcp(deal.north) + hcp(deal.south) not in range(10, 30, 1):
return False

max_length = 0
for hand in deal:
hand_length = max(hand.shape)
max_length = max(max_length, hand_length)
if max_length > 6:
return False

return True


Deal.set_str_style("pbn")
dealer = Deal.prepare()

with Path(Path.cwd() / "normal.pbn").open("w", newline="\r\n", encoding="utf-8") as f:
for i in range(18):
dv = i % 16
print(f'\n[Board "{i + 1}"]', file=f)
print(f'[Dealer "{dealvul[dv][0]}"]', file=f)
print(f'[Vulnerable "{dealvul[dv][1]}"]', file=f)
print(dealer(accept), file=f)
111 changes: 111 additions & 0 deletions mfscripts/ntvs5332.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"""Should 5M332s transfer over 1NT?"""

from collections import defaultdict

from redeal import hcp, Shape, Simulation, SmartStack

# play with these values if desired
NT_MIN = 15
NT_MAX = 17
RESP_MIN = 7
RESP_MAX = 8
INCLUDE_5M332 = True
INCLUDE_6m322 = False # pylint: disable=invalid-name
PRINT_EACH_HAND = False
# You shouldn't have to change anything after this.

# Give N a strong NT opener
nt_shape = Shape("(4333)") + Shape("(4432)")
if INCLUDE_5M332:
nt_shape += Shape("(5332)")
else:
nt_shape += Shape("33(52)") + Shape("(32)(53)")
if INCLUDE_6m322:
nt_shape += Shape("(32)(62)") + Shape("22(63)")
predeal = {"N": SmartStack(nt_shape, hcp, range(NT_MIN, NT_MAX + 1))}


class MySim(Simulation):
"""Should max pass 5M332s transfer or try to take the same tricks in 1NT?

Question on the BB Forums about "do you transfer or pass with 5332 at
matchpoints?" The only thing not reaching consensus was "if you're 22-23,
so you're a max for not-an-invite, is it better to pass and try to take
the same number of tricks in NT as in the suit?"

This simulation attempts to answer this question.

"""

def __init__(self):
self.stats = {
"accepted": {
"count": 0,
"display": "{item} hands processed of {n_tries} attempted:\n",
},
"nt_best": {"count": 0, "display": "NT scores better on {item} deals"},
"nt_not_worse": {
"count": 0,
"display": "NT scores equal or better on {item} deals",
},
"nt_minus_1": {"count": 0, "display": "NT one trick less on {item} deals"},
"nt_down": {"count": 0, "display": "1NT goes down on {item} deals"},
"suit_down": {"count": 0, "display": "2M goes down on {item} deals"},
"fit": {"count": 0, "display": "8-card M fit on {item} deals"},
}
self.points = defaultdict(int)

def accept(self, deal):
"""Accept the deal if South is 5M332 and within requested range."""

return deal.south.shape in Shape("(53)(32)") + Shape(
"(52)33"
) and deal.south.hcp in range(RESP_MIN, RESP_MAX + 1)

def do(self, deal):
"""Process the accepted deal. Increment the relevant counters."""

def _increment_if(stat, test):
# relies on int(bool) being 1 or 0.
self.stats[stat]["count"] += test

# increment total points counter
points = deal.north.hcp + deal.south.hcp
self.points[points] += 1

# get tricks in NT and the major. Transfers, so NTer always plays.
nt = deal.dd_tricks("1NN")
if len(deal.south.spades) == 5:
s_contract = "2SN"
fit = len(deal.south.spades) + len(deal.north.spades) >= 8
else:
s_contract = "2HN"
fit = len(deal.south.hearts) + len(deal.north.hearts) >= 8
suit = deal.dd_tricks(s_contract)

# increment relevant counters for table.
_increment_if("accepted", True)
_increment_if("nt_best", deal.dd_score("1NN") > deal.dd_score(s_contract))
_increment_if("nt_not_worse", deal.dd_score("1NN") >= deal.dd_score(s_contract))
_increment_if("nt_minus_1", suit - nt == 1)
_increment_if("nt_down", deal.dd_score("1NN") < 0)
_increment_if("suit_down", deal.dd_score(s_contract) < 0)
_increment_if("fit", fit)

if PRINT_EACH_HAND:
print(f"NT: {nt}, suit: {suit}, HCP: {points}{' FIT' if fit else ''}")

def final(self, n_tries):
"""After all processing, print out the results"""

print(
f"{NT_MIN}-{NT_MAX} opposite {RESP_MIN}-{RESP_MAX},"
f"{' 5M332' if INCLUDE_5M332 else ''}",
f"{' 6m322' if INCLUDE_6m322 else ''}",
)
for stat in self.stats.values():
print(stat["display"].format(item=stat["count"], n_tries=n_tries))
print(f"frequencies of HCP totals: {sorted(self.points.items())}")


simulation = MySim()
19 changes: 19 additions & 0 deletions mfscripts/six_d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Given a pair of sample hands, does it make 6D or 3NT?"""

from redeal import Deal


predeal = {"W": "4 AJT864 A53 QJ9", "E": "AQ762 7 KQJ94 A4"}
TABLE = {"6d": 0, "3nt": 0}


def do(deal: Deal) -> None:
"""Count if slam or 3NT makes."""
TABLE["6d"] += deal.dd_score("6DE") > 0
TABLE["3nt"] += deal.dd_score("3NE") > 0


def final(n_tries: int) -> None:
"""Print results."""
print(TABLE)
print(f"Tries: {n_tries}")
42 changes: 42 additions & 0 deletions mfscripts/slam_on_31.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Given control-rich 14 count, does slam make after 1C-1D; 2NT?"""

from collections import Counter

from redeal import Hand, Deal, semibalanced


predeal = {"S": "KJ8 K5 A9753 K73"}
table = Counter()


def one_c_two_nt(hand: Hand) -> bool:
"""Will open 1C, will rebid 2NT over 1D response"""
if (
semibalanced(hand)
and len(hand.diamonds) <= 4
and len(hand.clubs) >= len(hand.diamonds)
and len(hand.clubs) >= 3
and not (len(hand.hearts) == 5 or len(hand.spades) == 5)
):
return hand.hcp in [18, 19]
return False


def accept(deal: Deal) -> bool:
"""Accept 1C-2NT hands."""
return one_c_two_nt(deal.north)


def do(deal: Deal) -> None:
"""Determine if slam makes in diamonds or NT."""
table["D"] += deal.dd_tricks("6DS") >= 12
table["NT"] += deal.dd_tricks("6NN") >= 12
table["count"] += 1


def final(n_hands: int) -> None:
"""After hand generation, print stats."""
print(
f"hands dealt: {n_hands}, matches: {table['count']}, "
f"6D makes on: {table['D']}, 6NT on {table['NT']}"
)
Loading
Loading