maverick 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- maverick-0.1.0/PKG-INFO +93 -0
- maverick-0.1.0/README.md +80 -0
- maverick-0.1.0/pyproject.toml +52 -0
- maverick-0.1.0/src/maverick/__init__.py +53 -0
- maverick-0.1.0/src/maverick/_registered_players.py +7 -0
- maverick-0.1.0/src/maverick/card.py +108 -0
- maverick-0.1.0/src/maverick/deck.py +144 -0
- maverick-0.1.0/src/maverick/enums.py +342 -0
- maverick-0.1.0/src/maverick/eventbus.py +117 -0
- maverick-0.1.0/src/maverick/events.py +58 -0
- maverick-0.1.0/src/maverick/game.py +1100 -0
- maverick-0.1.0/src/maverick/hand.py +72 -0
- maverick-0.1.0/src/maverick/holding.py +87 -0
- maverick-0.1.0/src/maverick/player.py +119 -0
- maverick-0.1.0/src/maverick/playeraction.py +39 -0
- maverick-0.1.0/src/maverick/players/__init__.py +41 -0
- maverick-0.1.0/src/maverick/players/agressivebot.py +53 -0
- maverick-0.1.0/src/maverick/players/archetypes/__init__.py +33 -0
- maverick-0.1.0/src/maverick/players/archetypes/abc.py +95 -0
- maverick-0.1.0/src/maverick/players/archetypes/bully.py +94 -0
- maverick-0.1.0/src/maverick/players/archetypes/fish.py +91 -0
- maverick-0.1.0/src/maverick/players/archetypes/grinder.py +88 -0
- maverick-0.1.0/src/maverick/players/archetypes/gto.py +99 -0
- maverick-0.1.0/src/maverick/players/archetypes/hero_caller.py +88 -0
- maverick-0.1.0/src/maverick/players/archetypes/loose_aggressive.py +87 -0
- maverick-0.1.0/src/maverick/players/archetypes/loose_passive.py +45 -0
- maverick-0.1.0/src/maverick/players/archetypes/maniac.py +75 -0
- maverick-0.1.0/src/maverick/players/archetypes/scared_money.py +84 -0
- maverick-0.1.0/src/maverick/players/archetypes/shark.py +101 -0
- maverick-0.1.0/src/maverick/players/archetypes/tight_agressive.py +95 -0
- maverick-0.1.0/src/maverick/players/archetypes/tight_passive.py +72 -0
- maverick-0.1.0/src/maverick/players/archetypes/tilted.py +95 -0
- maverick-0.1.0/src/maverick/players/archetypes/whale.py +77 -0
- maverick-0.1.0/src/maverick/players/callbot.py +17 -0
- maverick-0.1.0/src/maverick/players/foldbot.py +17 -0
- maverick-0.1.0/src/maverick/playerstate.py +45 -0
- maverick-0.1.0/src/maverick/protocol.py +93 -0
- maverick-0.1.0/src/maverick/rules.py +142 -0
- maverick-0.1.0/src/maverick/state.py +164 -0
- maverick-0.1.0/src/maverick/utils/__init__.py +8 -0
- maverick-0.1.0/src/maverick/utils/holding_strength.py +93 -0
- maverick-0.1.0/src/maverick/utils/scoring.py +262 -0
maverick-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: maverick
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Utilities for a poker-playing assistant
|
|
5
|
+
Author: Bence Balogh
|
|
6
|
+
Author-email: Bence Balogh <benceeok@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Requires-Dist: pydantic>=2.12.5
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Project-URL: Homepage, https://github.com/BALOGHBence/maverick
|
|
11
|
+
Project-URL: Documentation, https://pymaverick.readthedocs.io/en/latest/index.html
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
<div align="center">
|
|
15
|
+
<p>
|
|
16
|
+
<a href="https://readthedocs.org/projects/pymaverick/badge/?version=latest)](https://pymaverick.readthedocs.io/en/latest/?badge=latest" target="_blank">
|
|
17
|
+
<img width="100%" src="https://github.com/BALOGHBence/maverick/blob/main/cover_image.png?raw=true" alt="Maverick banner"></a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<div>
|
|
21
|
+
<a href="https://pymaverick.readthedocs.io/en/latest/?badge=latest"><img src="https://readthedocs.org/projects/pymaverick/badge/?version=latest" alt="Documentation Status"></a>
|
|
22
|
+
<a href="https://codecov.io/gh/BALOGHBence/maverick"><img src="https://codecov.io/gh/BALOGHBence/maverick/graph/badge.svg?token=VDRFOUJYUG" alt="Code Coverage"></a>
|
|
23
|
+
<a href="https://app.codacy.com/gh/BALOGHBence/maverick/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade"><img src="https://app.codacy.com/project/badge/Grade/c960167518b646eea31cf1ff02a13823" alt="Code Quality"></a>
|
|
24
|
+
<a href="https://github.com/psf/black"><img src="https://img.shields.io/badge/code%20style-black-000000.svg" alt="Code Style"></a>
|
|
25
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License"></a>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<br>
|
|
29
|
+
|
|
30
|
+
<div>
|
|
31
|
+
<a href="https://pymaverick.readthedocs.io/en/latest/index.html"><img src="https://img.shields.io/badge/Documentation-blue?style=flat" alt="Documentation"></a>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<br>
|
|
35
|
+
|
|
36
|
+
<div>
|
|
37
|
+
<a href="https://buymeacoffee.com/benceeokf"><img src="https://img.shields.io/badge/Buy%20Me%20a%20Coffee-ffdd00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black" alt="BuyMeACoffee"></a>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<br>
|
|
41
|
+
|
|
42
|
+
<p>
|
|
43
|
+
A Python library for simulating poker games with custom player strategies.
|
|
44
|
+
</p>
|
|
45
|
+
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
> **Note**
|
|
49
|
+
>
|
|
50
|
+
> Maverick is under active development and subject to change.
|
|
51
|
+
> First deployment to PyPI is going to be announced on
|
|
52
|
+
> [my LinkedIn profile](https://www.linkedin.com/in/bence-balogh-082073181/).
|
|
53
|
+
|
|
54
|
+
## Highlights
|
|
55
|
+
|
|
56
|
+
- **Configurable Poker Games**: Full rules and mechanics supporting a variety of flavours
|
|
57
|
+
- **State Machine Architecture**: Clean separation of game states and transitions
|
|
58
|
+
- **Flexible Player System**: Protocol-based player interface for custom implementations
|
|
59
|
+
- **Hand Evaluation**: Built-in poker hand scoring and comparison
|
|
60
|
+
- **Event System**: Track all game events for analysis and replay
|
|
61
|
+
- **Well Documented**: Comprehensive documentation of rules and APIs
|
|
62
|
+
- **Thoroughly Tested**: Features are heavily tested with high code coverage
|
|
63
|
+
|
|
64
|
+
## Documentation
|
|
65
|
+
|
|
66
|
+
The project has extensive [documentation](https://pymaverick.readthedocs.io/en/latest/index.html) hosted on ReadTheDocs. Most library information is documented there, with only the essentials kept here.
|
|
67
|
+
|
|
68
|
+
## Versioning
|
|
69
|
+
|
|
70
|
+
The project adheres to [semantic versioning](https://semver.org/).
|
|
71
|
+
|
|
72
|
+
## Contributing
|
|
73
|
+
|
|
74
|
+
Contributions are currently expected in any the following ways:
|
|
75
|
+
|
|
76
|
+
- **finding bugs**
|
|
77
|
+
If you run into trouble when using the library and you think it is a bug, feel free to raise an issue.
|
|
78
|
+
- **feedback**
|
|
79
|
+
All kinds of ideas are welcome. For instance if you feel like something is still shady (after reading the user guide), we want to know. Be gentle though, the development of the library is financially not supported yet.
|
|
80
|
+
- **feature requests**
|
|
81
|
+
Tell us what you think is missing (with realistic expectations).
|
|
82
|
+
- **examples**
|
|
83
|
+
If you've done something with the library and you think that it would make for a good example, get in touch with the developers and we will happily inlude it in the documention.
|
|
84
|
+
- **funding**
|
|
85
|
+
Use one of the supported funding channels. Any amount you can afford is appreciated.
|
|
86
|
+
- **sharing is caring**
|
|
87
|
+
If you like the library, share it with your friends or colleagues so they can like it too.
|
|
88
|
+
|
|
89
|
+
In all cases, read the [contributing guidelines](CONTRIBUTING.md) before you do anything.
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
This package is licensed under the [MIT license](LICENSE.txt).
|
maverick-0.1.0/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<p>
|
|
3
|
+
<a href="https://readthedocs.org/projects/pymaverick/badge/?version=latest)](https://pymaverick.readthedocs.io/en/latest/?badge=latest" target="_blank">
|
|
4
|
+
<img width="100%" src="https://github.com/BALOGHBence/maverick/blob/main/cover_image.png?raw=true" alt="Maverick banner"></a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<div>
|
|
8
|
+
<a href="https://pymaverick.readthedocs.io/en/latest/?badge=latest"><img src="https://readthedocs.org/projects/pymaverick/badge/?version=latest" alt="Documentation Status"></a>
|
|
9
|
+
<a href="https://codecov.io/gh/BALOGHBence/maverick"><img src="https://codecov.io/gh/BALOGHBence/maverick/graph/badge.svg?token=VDRFOUJYUG" alt="Code Coverage"></a>
|
|
10
|
+
<a href="https://app.codacy.com/gh/BALOGHBence/maverick/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade"><img src="https://app.codacy.com/project/badge/Grade/c960167518b646eea31cf1ff02a13823" alt="Code Quality"></a>
|
|
11
|
+
<a href="https://github.com/psf/black"><img src="https://img.shields.io/badge/code%20style-black-000000.svg" alt="Code Style"></a>
|
|
12
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License"></a>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<br>
|
|
16
|
+
|
|
17
|
+
<div>
|
|
18
|
+
<a href="https://pymaverick.readthedocs.io/en/latest/index.html"><img src="https://img.shields.io/badge/Documentation-blue?style=flat" alt="Documentation"></a>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<br>
|
|
22
|
+
|
|
23
|
+
<div>
|
|
24
|
+
<a href="https://buymeacoffee.com/benceeokf"><img src="https://img.shields.io/badge/Buy%20Me%20a%20Coffee-ffdd00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black" alt="BuyMeACoffee"></a>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<br>
|
|
28
|
+
|
|
29
|
+
<p>
|
|
30
|
+
A Python library for simulating poker games with custom player strategies.
|
|
31
|
+
</p>
|
|
32
|
+
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
> **Note**
|
|
36
|
+
>
|
|
37
|
+
> Maverick is under active development and subject to change.
|
|
38
|
+
> First deployment to PyPI is going to be announced on
|
|
39
|
+
> [my LinkedIn profile](https://www.linkedin.com/in/bence-balogh-082073181/).
|
|
40
|
+
|
|
41
|
+
## Highlights
|
|
42
|
+
|
|
43
|
+
- **Configurable Poker Games**: Full rules and mechanics supporting a variety of flavours
|
|
44
|
+
- **State Machine Architecture**: Clean separation of game states and transitions
|
|
45
|
+
- **Flexible Player System**: Protocol-based player interface for custom implementations
|
|
46
|
+
- **Hand Evaluation**: Built-in poker hand scoring and comparison
|
|
47
|
+
- **Event System**: Track all game events for analysis and replay
|
|
48
|
+
- **Well Documented**: Comprehensive documentation of rules and APIs
|
|
49
|
+
- **Thoroughly Tested**: Features are heavily tested with high code coverage
|
|
50
|
+
|
|
51
|
+
## Documentation
|
|
52
|
+
|
|
53
|
+
The project has extensive [documentation](https://pymaverick.readthedocs.io/en/latest/index.html) hosted on ReadTheDocs. Most library information is documented there, with only the essentials kept here.
|
|
54
|
+
|
|
55
|
+
## Versioning
|
|
56
|
+
|
|
57
|
+
The project adheres to [semantic versioning](https://semver.org/).
|
|
58
|
+
|
|
59
|
+
## Contributing
|
|
60
|
+
|
|
61
|
+
Contributions are currently expected in any the following ways:
|
|
62
|
+
|
|
63
|
+
- **finding bugs**
|
|
64
|
+
If you run into trouble when using the library and you think it is a bug, feel free to raise an issue.
|
|
65
|
+
- **feedback**
|
|
66
|
+
All kinds of ideas are welcome. For instance if you feel like something is still shady (after reading the user guide), we want to know. Be gentle though, the development of the library is financially not supported yet.
|
|
67
|
+
- **feature requests**
|
|
68
|
+
Tell us what you think is missing (with realistic expectations).
|
|
69
|
+
- **examples**
|
|
70
|
+
If you've done something with the library and you think that it would make for a good example, get in touch with the developers and we will happily inlude it in the documention.
|
|
71
|
+
- **funding**
|
|
72
|
+
Use one of the supported funding channels. Any amount you can afford is appreciated.
|
|
73
|
+
- **sharing is caring**
|
|
74
|
+
If you like the library, share it with your friends or colleagues so they can like it too.
|
|
75
|
+
|
|
76
|
+
In all cases, read the [contributing guidelines](CONTRIBUTING.md) before you do anything.
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
This package is licensed under the [MIT license](LICENSE.txt).
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "maverick"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Utilities for a poker-playing assistant"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Bence Balogh", email = "benceeok@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
license = { text = "MIT" }
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"pydantic>=2.12.5",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[dependency-groups]
|
|
16
|
+
docs = [
|
|
17
|
+
"linkify-it-py>=2.0.3",
|
|
18
|
+
"myst-nb>=1.3.0",
|
|
19
|
+
"myst-parser>=5.0.0",
|
|
20
|
+
"sphinx>=9.1.0",
|
|
21
|
+
"sphinx-book-theme>=1.1.3",
|
|
22
|
+
"sphinx-copybutton>=0.5.2",
|
|
23
|
+
]
|
|
24
|
+
test = [
|
|
25
|
+
"pandas>=2.3.3",
|
|
26
|
+
"pytest>=9.0.2",
|
|
27
|
+
"pytest-cov>=7.0.0",
|
|
28
|
+
]
|
|
29
|
+
dev = [
|
|
30
|
+
{include-group = "test"},
|
|
31
|
+
{include-group = "docs"},
|
|
32
|
+
"black>=25.12.0",
|
|
33
|
+
"ipykernel>=7.1.0",
|
|
34
|
+
"matplotlib>=3.10.8",
|
|
35
|
+
"scipy>=1.16.3",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://github.com/BALOGHBence/maverick"
|
|
40
|
+
Documentation = "https://pymaverick.readthedocs.io/en/latest/index.html"
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
testpaths = ["tests"]
|
|
44
|
+
addopts = "-q"
|
|
45
|
+
|
|
46
|
+
[build-system]
|
|
47
|
+
requires = ["uv_build>=0.9.22,<0.10.0"]
|
|
48
|
+
build-backend = "uv_build"
|
|
49
|
+
|
|
50
|
+
[tool.uv.build-backend]
|
|
51
|
+
module-name = "maverick"
|
|
52
|
+
module-root = "src"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from importlib.metadata import metadata
|
|
2
|
+
|
|
3
|
+
from .card import Card
|
|
4
|
+
from .deck import Deck
|
|
5
|
+
from .enums import (
|
|
6
|
+
Suit,
|
|
7
|
+
Rank,
|
|
8
|
+
HandType,
|
|
9
|
+
Street,
|
|
10
|
+
PlayerStateType,
|
|
11
|
+
GameStateType,
|
|
12
|
+
ActionType,
|
|
13
|
+
GameEventType,
|
|
14
|
+
)
|
|
15
|
+
from .player import Player
|
|
16
|
+
from .hand import Hand
|
|
17
|
+
from .holding import Holding
|
|
18
|
+
from .utils.scoring import score_hand
|
|
19
|
+
from .game import Game
|
|
20
|
+
from .state import GameState
|
|
21
|
+
from .protocol import PlayerLike
|
|
22
|
+
from .playeraction import PlayerAction
|
|
23
|
+
from .playerstate import PlayerState
|
|
24
|
+
from .events import GameEvent
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"Card",
|
|
28
|
+
"Deck",
|
|
29
|
+
"Suit",
|
|
30
|
+
"Rank",
|
|
31
|
+
"HandType",
|
|
32
|
+
"Street",
|
|
33
|
+
"PlayerStateType",
|
|
34
|
+
"Player",
|
|
35
|
+
"Hand",
|
|
36
|
+
"Holding",
|
|
37
|
+
"score_hand",
|
|
38
|
+
"Game",
|
|
39
|
+
"GameState",
|
|
40
|
+
"GameEventType",
|
|
41
|
+
"GameStateType",
|
|
42
|
+
"ActionType",
|
|
43
|
+
"PlayerLike",
|
|
44
|
+
"PlayerAction",
|
|
45
|
+
"PlayerState",
|
|
46
|
+
"GameEvent",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
__pkg_name__ = "maverick"
|
|
50
|
+
__pkg_metadata__ = metadata(__pkg_name__)
|
|
51
|
+
__version__ = __pkg_metadata__["version"]
|
|
52
|
+
__description__ = __pkg_metadata__["summary"]
|
|
53
|
+
del __pkg_metadata__
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from typing import Tuple
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
from .enums import Suit, Rank, HandType
|
|
7
|
+
from .utils.scoring import score_hand
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
__all__ = ["Card"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Card(BaseModel):
|
|
14
|
+
"""A playing card with a suit and rank.
|
|
15
|
+
|
|
16
|
+
Fields
|
|
17
|
+
------
|
|
18
|
+
suit : Suit
|
|
19
|
+
The suit of the card (Hearts, Diamonds, Clubs, Spades).
|
|
20
|
+
rank : Rank
|
|
21
|
+
The rank of the card (Two through Ace).
|
|
22
|
+
|
|
23
|
+
Examples
|
|
24
|
+
--------
|
|
25
|
+
>>> from maverick import Card, Suit, Rank
|
|
26
|
+
>>> card = Card(suit=Suit.HEARTS, rank=Rank.ACE)
|
|
27
|
+
>>> card.utf8()
|
|
28
|
+
'A♥'
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
suit: Suit
|
|
32
|
+
rank: Rank
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def random(cls, n: int = 1) -> list["Card"]:
|
|
36
|
+
"""Generate n random cards without repetition."""
|
|
37
|
+
suits = list(Suit)
|
|
38
|
+
ranks = list(Rank)
|
|
39
|
+
all_cards = [cls(suit=s, rank=r) for s in suits for r in ranks]
|
|
40
|
+
selected = random.sample(all_cards, n)
|
|
41
|
+
return selected if n > 1 else selected[0]
|
|
42
|
+
|
|
43
|
+
def score(self) -> Tuple[HandType, float]:
|
|
44
|
+
"""Classifies and scores the card.
|
|
45
|
+
|
|
46
|
+
Returns (HandType, float_score) where higher scores = stronger hands.
|
|
47
|
+
"""
|
|
48
|
+
return score_hand([self])
|
|
49
|
+
|
|
50
|
+
def utf8(self) -> str:
|
|
51
|
+
"""Return the UTF-8 representation of the card."""
|
|
52
|
+
suit_symbols = {
|
|
53
|
+
Suit.HEARTS: "♥",
|
|
54
|
+
Suit.SPADES: "♠",
|
|
55
|
+
Suit.CLUBS: "♣",
|
|
56
|
+
Suit.DIAMONDS: "♦",
|
|
57
|
+
}
|
|
58
|
+
rank_symbols = {
|
|
59
|
+
Rank.TWO: "2",
|
|
60
|
+
Rank.THREE: "3",
|
|
61
|
+
Rank.FOUR: "4",
|
|
62
|
+
Rank.FIVE: "5",
|
|
63
|
+
Rank.SIX: "6",
|
|
64
|
+
Rank.SEVEN: "7",
|
|
65
|
+
Rank.EIGHT: "8",
|
|
66
|
+
Rank.NINE: "9",
|
|
67
|
+
Rank.TEN: "10",
|
|
68
|
+
Rank.JACK: "J",
|
|
69
|
+
Rank.QUEEN: "Q",
|
|
70
|
+
Rank.KING: "K",
|
|
71
|
+
Rank.ACE: "A",
|
|
72
|
+
}
|
|
73
|
+
return f"{rank_symbols[self.rank]}{suit_symbols[self.suit]}"
|
|
74
|
+
|
|
75
|
+
def code(self) -> str:
|
|
76
|
+
"""Return short canonical card code (e.g. Ah, Td, Ks)."""
|
|
77
|
+
rank_codes = {
|
|
78
|
+
Rank.TWO: "2",
|
|
79
|
+
Rank.THREE: "3",
|
|
80
|
+
Rank.FOUR: "4",
|
|
81
|
+
Rank.FIVE: "5",
|
|
82
|
+
Rank.SIX: "6",
|
|
83
|
+
Rank.SEVEN: "7",
|
|
84
|
+
Rank.EIGHT: "8",
|
|
85
|
+
Rank.NINE: "9",
|
|
86
|
+
Rank.TEN: "T",
|
|
87
|
+
Rank.JACK: "J",
|
|
88
|
+
Rank.QUEEN: "Q",
|
|
89
|
+
Rank.KING: "K",
|
|
90
|
+
Rank.ACE: "A",
|
|
91
|
+
}
|
|
92
|
+
suit_codes = {
|
|
93
|
+
Suit.HEARTS: "h",
|
|
94
|
+
Suit.DIAMONDS: "d",
|
|
95
|
+
Suit.CLUBS: "c",
|
|
96
|
+
Suit.SPADES: "s",
|
|
97
|
+
}
|
|
98
|
+
return f"{rank_codes[self.rank]}{suit_codes[self.suit]}"
|
|
99
|
+
|
|
100
|
+
def text(self) -> str:
|
|
101
|
+
"""Return human-readable text representation."""
|
|
102
|
+
return f"{self.rank.name.title()} of {self.suit.name.title()}"
|
|
103
|
+
|
|
104
|
+
def __repr__(self) -> str:
|
|
105
|
+
return f"Card({self.code()})"
|
|
106
|
+
|
|
107
|
+
def __str__(self) -> str:
|
|
108
|
+
return self.utf8()
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from warnings import warn
|
|
2
|
+
import random
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
from .enums import Suit, Rank
|
|
7
|
+
from .card import Card
|
|
8
|
+
|
|
9
|
+
__all__ = ["Deck"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Deck(BaseModel):
|
|
13
|
+
"""A standard deck of 52 playing cards.
|
|
14
|
+
|
|
15
|
+
Fields
|
|
16
|
+
------
|
|
17
|
+
cards : list[Card]
|
|
18
|
+
The list of cards in the deck.
|
|
19
|
+
|
|
20
|
+
Examples
|
|
21
|
+
--------
|
|
22
|
+
>>> from maverick import Deck
|
|
23
|
+
>>> deck = Deck.standard_deck(shuffle=True)
|
|
24
|
+
>>> dealt_cards = deck.deal(5)
|
|
25
|
+
>>> len(dealt_cards)
|
|
26
|
+
5
|
|
27
|
+
>>> len(deck.cards)
|
|
28
|
+
47
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
cards: list[Card]
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def build(cls, shuffle: bool = False) -> "Deck":
|
|
35
|
+
"""Build a standard deck of 52 cards.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
shuffle : bool, optional
|
|
40
|
+
Whether to shuffle the deck after building (default is False).
|
|
41
|
+
"""
|
|
42
|
+
ranks = list(Rank)
|
|
43
|
+
suits = list(Suit)
|
|
44
|
+
cards = []
|
|
45
|
+
|
|
46
|
+
for rank in ranks:
|
|
47
|
+
for suit in suits:
|
|
48
|
+
card = Card(suit=suit, rank=rank)
|
|
49
|
+
cards.append(card)
|
|
50
|
+
|
|
51
|
+
deck = cls(cards=cards)
|
|
52
|
+
|
|
53
|
+
if shuffle:
|
|
54
|
+
deck.shuffle()
|
|
55
|
+
|
|
56
|
+
return deck
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def standard_deck(cls, shuffle: bool = False) -> "Deck":
|
|
60
|
+
"""Create and optionally shuffle a standard deck of 52 cards.
|
|
61
|
+
|
|
62
|
+
This is an alias for Deck.build() for clarity.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
shuffle : bool, optional
|
|
67
|
+
Whether to shuffle the deck after building (default is False).
|
|
68
|
+
"""
|
|
69
|
+
return cls.build(shuffle=shuffle)
|
|
70
|
+
|
|
71
|
+
def deal(self, n: int) -> list[Card]:
|
|
72
|
+
"""
|
|
73
|
+
Deal n random cards from the deck.
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
n : int
|
|
78
|
+
The number of cards to deal.
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
list[Card]
|
|
83
|
+
The list of dealt cards.
|
|
84
|
+
|
|
85
|
+
Raises
|
|
86
|
+
------
|
|
87
|
+
ValueError
|
|
88
|
+
If there are not enough cards in the deck to deal.
|
|
89
|
+
|
|
90
|
+
Notes
|
|
91
|
+
-----
|
|
92
|
+
1) Dealt cards are removed from the deck.
|
|
93
|
+
2) If n <= 0, an empty list is returned.
|
|
94
|
+
"""
|
|
95
|
+
if n > len(self.cards):
|
|
96
|
+
raise ValueError("Not enough cards in the deck to deal.")
|
|
97
|
+
|
|
98
|
+
if n <= 0:
|
|
99
|
+
warn(
|
|
100
|
+
"Requested to deal non-positive number of cards; returning empty list."
|
|
101
|
+
)
|
|
102
|
+
return []
|
|
103
|
+
|
|
104
|
+
dealt_cards = random.sample(self.cards, n)
|
|
105
|
+
|
|
106
|
+
for card in dealt_cards:
|
|
107
|
+
self.cards.remove(card)
|
|
108
|
+
|
|
109
|
+
return dealt_cards
|
|
110
|
+
|
|
111
|
+
def shuffle(self, n: int = 1) -> "Deck":
|
|
112
|
+
"""Shuffle the deck of cards n times.
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
n : int, optional
|
|
117
|
+
The number of times to shuffle the deck (default is 1).
|
|
118
|
+
"""
|
|
119
|
+
if n <= 0:
|
|
120
|
+
warn("Number of shuffles must be positive; returning unshuffled deck.")
|
|
121
|
+
return self
|
|
122
|
+
|
|
123
|
+
for _ in range(n):
|
|
124
|
+
random.shuffle(self.cards)
|
|
125
|
+
|
|
126
|
+
return self
|
|
127
|
+
|
|
128
|
+
def missing_cards(self) -> list[Card]:
|
|
129
|
+
"""Return the list of cards missing from the deck."""
|
|
130
|
+
full_deck = Deck.build()
|
|
131
|
+
missing = [card for card in full_deck.cards if card not in self.cards]
|
|
132
|
+
return missing
|
|
133
|
+
|
|
134
|
+
def remove_cards(self, cards_to_remove: list[Card]) -> None:
|
|
135
|
+
"""Remove specified cards from the deck.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
cards_to_remove : list[Card]
|
|
140
|
+
The list of cards to remove from the deck.
|
|
141
|
+
"""
|
|
142
|
+
for card in cards_to_remove:
|
|
143
|
+
if card in self.cards:
|
|
144
|
+
self.cards.remove(card)
|