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

Improve Python Code Formatting and Quality #94

Merged
merged 4 commits into from
May 2, 2024
Merged
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
14 changes: 8 additions & 6 deletions python/benchmark.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
import time
from itertools import combinations

from phevaluator import _evaluate_cards, _evaluate_omaha_cards, sample_cards
from phevaluator import _evaluate_cards
from phevaluator import _evaluate_omaha_cards
from phevaluator import sample_cards


def evaluate_all_five_card_hands():
def evaluate_all_five_card_hands() -> None:
for cards in combinations(range(52), 5):
_evaluate_cards(*cards)


def evaluate_all_six_card_hands():
def evaluate_all_six_card_hands() -> None:
for cards in combinations(range(52), 6):
_evaluate_cards(*cards)


def evaluate_all_seven_card_hands():
def evaluate_all_seven_card_hands() -> None:
for cards in combinations(range(52), 7):
_evaluate_cards(*cards)


def evaluate_random_omaha_card_hands():
def evaluate_random_omaha_card_hands() -> None:
total = 100_000
for _ in range(total):
cards = sample_cards(9)
_evaluate_omaha_cards(cards[:5], cards[5:])


def benchmark():
def benchmark() -> None:
print("--------------------------------------------------------------------")
print("Benchmark Time")
t = time.process_time()
Expand Down
9 changes: 5 additions & 4 deletions python/examples.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from phevaluator import evaluate_cards, evaluate_omaha_cards
from phevaluator import evaluate_cards
from phevaluator import evaluate_omaha_cards


def example1():
def example1() -> None:
print("Example 1: A Texas Holdem example")

a = 7 * 4 + 0 # 9c
Expand All @@ -26,7 +27,7 @@ def example1():
print("Player 2 has a stronger hand")


def example2():
def example2() -> None:
print("Example 2: Another Texas Holdem example")

rank1 = evaluate_cards("9c", "4c", "4s", "9d", "4h", "Qc", "6c") # expected 292
Expand All @@ -37,7 +38,7 @@ def example2():
print("Player 2 has a stronger hand")


def example3():
def example3() -> None:
print("Example 3: An Omaha poker example")
# fmt: off
rank1 = evaluate_omaha_cards(
Expand Down
13 changes: 7 additions & 6 deletions python/phevaluator/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"""Package for evaluating a poker hand."""
from . import hash as hash_ # FIXME: `hash` collides to built-in function

from typing import Any

# import mapping to objects in other modules
all_by_module = {
"phevaluator": ["card", "evaluator_omaha", "evaluator", "hash", "tables", "utils"],
"phevaluator.card": ["Card"],
"phevaluator.evaluator": ["_evaluate_cards", "evaluate_cards"],
"phevaluator.evaluator_omaha": ["_evaluate_omaha_cards", "evaluate_omaha_cards"],
"phevaluator.utils": ["sample_cards"]
"phevaluator.utils": ["sample_cards"],
}

# Based on werkzeug library
Expand All @@ -21,10 +21,11 @@


# Based on https://peps.python.org/pep-0562/ and werkzeug library
def __getattr__(name):
"""lazy submodule imports"""
def __getattr__(name: str) -> Any: # noqa: ANN401
"""Lazy submodule imports."""
if name in object_origins:
module = __import__(object_origins[name], None, None, [name])
return getattr(module, name)
else:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

msg = f"module {__name__!r} has no attribute {name!r}"
raise AttributeError(msg)
61 changes: 34 additions & 27 deletions python/phevaluator/card.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"""Module for card."""

from __future__ import annotations

from typing import Any, Union

CARD_DESCRIPTION_LENGTH = 2

# fmt: off
rank_map = {
Expand All @@ -10,7 +12,7 @@
}
suit_map = {
"C": 0, "D": 1, "H": 2, "S": 3,
"c": 0, "d": 1, "h": 2, "s": 3
"c": 0, "d": 1, "h": 2, "s": 3,
}
# fmt: on

Expand Down Expand Up @@ -63,33 +65,33 @@ class Card:
The string parameter of the constructor should be exactly 2 characters.

>>> Card("9h") # OK
>>> Card("9h ") # ERROR
>>> Card("9h ") # ERROR

TypeError: Construction with unsupported type
The parameter of the constructor should be one of the following types: [int,
str, Card].
>>> Card(0) # OK. The 0 stands 2 of Clubs
>>> Card("2c") # OK
>>> Card("2C") # OK. Capital letter is also accepted.
>>> Card(Card(0)) # OK
>>> Card(0.0) # ERROR. float is not allowed
>>> Card(0) # OK. The 0 stands 2 of Clubs
>>> Card("2c") # OK
>>> Card("2C") # OK. Capital letter is also accepted.
>>> Card(Card(0)) # OK
>>> Card(0.0) # ERROR. float is not allowed

TypeError: Setting attribute
>>> c = Card("2c")
>>> c.__id = 1 # ERROR
>>> c._Card__id = 1 # ERROR
>>> c.__id = 1 # ERROR
>>> c._Card__id = 1 # ERROR

TypeError: Deliting attribute
>>> c = Card("2c")
>>> del c.__id # ERROR
>>> del c._Card__id # ERROR
>>> del c.__id # ERROR
>>> del c._Card__id # ERROR

"""

__slots__ = ["__id"]
__id: int

def __init__(self, other: Union[int, str, Card]):
def __init__(self, other: int | str | Card) -> None:
"""Construct card object.

If the passed argument is integer, it's set to `self.__id`.
Expand Down Expand Up @@ -128,7 +130,7 @@ def id_(self) -> int:
return self.__id

@staticmethod
def to_id(other: Union[int, str, Card]) -> int:
def to_id(other: int | str | Card) -> int:
"""Return the Card ID integer as API.

If the passed argument is integer, it's returned with doing nothing.
Expand All @@ -149,17 +151,20 @@ def to_id(other: Union[int, str, Card]) -> int:
"""
if isinstance(other, int):
return other
elif isinstance(other, str):
if len(other) != 2:
raise ValueError(f"The length of value must be 2. passed: {other}")
if isinstance(other, str):
if len(other) != CARD_DESCRIPTION_LENGTH:
msg = (
f"The length of value must be {CARD_DESCRIPTION_LENGTH}. "
f"passed: {other}"
)
raise ValueError(msg)
rank, suit, *_ = tuple(other)
return rank_map[rank] * 4 + suit_map[suit]
elif isinstance(other, Card):
if isinstance(other, Card):
return other.id_

raise TypeError(
f"Type of parameter must be int, str or Card. passed: {type(other)}"
)
msg = f"Type of parameter must be int, str or Card. passed: {type(other)}"
raise TypeError(msg)

def describe_rank(self) -> str:
"""Calculate card rank.
Expand Down Expand Up @@ -212,7 +217,7 @@ def describe_card(self) -> str:
"""
return self.describe_rank() + self.describe_suit()

def __eq__(self, other: Any) -> bool:
def __eq__(self, other: object) -> bool:
"""Return equality. This is special method.

Args:
Expand Down Expand Up @@ -262,10 +267,12 @@ def __hash__(self) -> int:
"""int: Special method for `hash(self)`."""
return hash(self.id_)

def __setattr__(self, name: str, value: Any) -> None:
"""Set an attribute. This causes TypeError since assignment to attribute is prevented."""
raise TypeError("Card object does not support assignment to attribute")
def __setattr__(self, name: str, value: object) -> None:
"""Set an attribute. This causes TypeError since assignment is prevented."""
msg = "Card object does not support assignment to attribute"
raise TypeError(msg)

def __delattr__(self, name: str) -> None:
"""Delete an attribute. This causes TypeError since deletion of attribute is prevented."""
raise TypeError("Card object does not support deletion of attribute")
"""Delete an attribute. This causes TypeError since deletion is prevented."""
msg = "Card object does not support deletion of attribute"
raise TypeError(msg)
28 changes: 14 additions & 14 deletions python/phevaluator/evaluator.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
"""Module evaluating cards."""
from typing import Union

from __future__ import annotations

from .card import Card
from .hash import hash_quinary
from .tables import (
BINARIES_BY_ID,
FLUSH,
NO_FLUSH_5,
NO_FLUSH_6,
NO_FLUSH_7,
SUITBIT_BY_ID,
SUITS,
)
from .tables import BINARIES_BY_ID
from .tables import FLUSH
from .tables import NO_FLUSH_5
from .tables import NO_FLUSH_6
from .tables import NO_FLUSH_7
from .tables import SUITBIT_BY_ID
from .tables import SUITS

MIN_CARDS = 5
MAX_CARDS = 7

NO_FLUSHES = {5: NO_FLUSH_5, 6: NO_FLUSH_6, 7: NO_FLUSH_7}


def evaluate_cards(*cards: Union[int, str, Card]) -> int:
def evaluate_cards(*cards: int | str | Card) -> int:
"""Evaluate cards for the best five cards.

This function selects the best combination of the five cards from given cards and
return its rank.
The number of cards must be between 5 and 7.

Args:
cards(Union[int, str, Card]): List of cards
cards(int | str | Card): List of cards

Raises:
ValueError: Unsupported size of the cards
Expand All @@ -39,17 +38,18 @@ def evaluate_cards(*cards: Union[int, str, Card]) -> int:
>>> rank1 = evaluate_cards("Ac", "Ad", "Ah", "As", "Kc")
>>> rank2 = evaluate_cards("Ac", "Ad", "Ah", "As", "Kd")
>>> rank3 = evaluate_cards("Ac", "Ad", "Ah", "As", "Kc", "Qh")
>>> rank1 == rank2 == rank3 # Those three are evaluated by `A A A A K`
>>> rank1 == rank2 == rank3 # Those three are evaluated by `A A A A K`
True
"""
int_cards = list(map(Card.to_id, cards))
hand_size = len(cards)

if not (MIN_CARDS <= hand_size <= MAX_CARDS) or (hand_size not in NO_FLUSHES):
raise ValueError(
msg = (
f"The number of cards must be between {MIN_CARDS} and {MAX_CARDS}."
f"passed size: {hand_size}"
)
raise ValueError(msg)

return _evaluate_cards(*int_cards)

Expand Down
Loading
Loading