cardspy 0.0.1__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.
- cardspy-0.0.1/PKG-INFO +15 -0
- cardspy-0.0.1/README.md +1 -0
- cardspy-0.0.1/cardspy/__init__.py +0 -0
- cardspy-0.0.1/cardspy/card.py +177 -0
- cardspy-0.0.1/cardspy/deck.py +118 -0
- cardspy-0.0.1/cardspy/rank.py +33 -0
- cardspy-0.0.1/cardspy/suit.py +23 -0
- cardspy-0.0.1/pyproject.toml +21 -0
cardspy-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: cardspy
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Functionalities for playing cards
|
|
5
|
+
Author: bd.noads@gmail.com
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Requires-Dist: mypy (>=1.15.0,<2.0.0)
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# cardspy
|
cardspy-0.0.1/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# cardspy
|
|
File without changes
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Card"""
|
|
2
|
+
from typing import List
|
|
3
|
+
from functools import reduce
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from .rank import (
|
|
6
|
+
Rank,
|
|
7
|
+
R2,
|
|
8
|
+
R3,
|
|
9
|
+
R4,
|
|
10
|
+
R5,
|
|
11
|
+
R6,
|
|
12
|
+
R7,
|
|
13
|
+
R8,
|
|
14
|
+
R9,
|
|
15
|
+
RT,
|
|
16
|
+
RJ,
|
|
17
|
+
RQ,
|
|
18
|
+
RK,
|
|
19
|
+
RA,
|
|
20
|
+
)
|
|
21
|
+
from .suit import Suit, CLUB, DIAMOND, HEART, SPADE
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class Card:
|
|
26
|
+
"""Card"""
|
|
27
|
+
key: int
|
|
28
|
+
rank: Rank
|
|
29
|
+
suit: Suit
|
|
30
|
+
code: str
|
|
31
|
+
name: str
|
|
32
|
+
symbol: str
|
|
33
|
+
|
|
34
|
+
def __str__(self) -> str:
|
|
35
|
+
return self.symbol
|
|
36
|
+
|
|
37
|
+
def __repr__(self) -> str:
|
|
38
|
+
return self.__str__()
|
|
39
|
+
|
|
40
|
+
def __lt__(self, other: "Card") -> bool:
|
|
41
|
+
return self.key < other.key
|
|
42
|
+
|
|
43
|
+
def __le__(self, other: "Card") -> bool:
|
|
44
|
+
return self.key <= other.key
|
|
45
|
+
|
|
46
|
+
def __gt__(self, other: "Card") -> bool:
|
|
47
|
+
return self.key > other.key
|
|
48
|
+
|
|
49
|
+
def __ge__(self, other: "Card") -> bool:
|
|
50
|
+
return self.key >= other.key
|
|
51
|
+
|
|
52
|
+
def __eq__(self, other: object) -> bool:
|
|
53
|
+
if not isinstance(other, Card):
|
|
54
|
+
return NotImplemented
|
|
55
|
+
return self.key == other.key
|
|
56
|
+
|
|
57
|
+
def __ne__(self, other: object) -> bool:
|
|
58
|
+
if not isinstance(other, Card):
|
|
59
|
+
return NotImplemented
|
|
60
|
+
return self.key != other.key
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
C2C = Card(0x1, R2, CLUB, "2C", "Two of Clubs", "2♣")
|
|
64
|
+
C2D = Card(0x2, R2, DIAMOND, "2D", "Two of Diamonds", "2♦")
|
|
65
|
+
C2H = Card(0x4, R2, HEART, "2H", "Two of Hearts", "2♥")
|
|
66
|
+
C2S = Card(0x8, R2, SPADE, "2S", "Two of Spades", "2♠")
|
|
67
|
+
|
|
68
|
+
C3C = Card(0x10, R3, CLUB, "3C", "Three of Clubs", "3♣")
|
|
69
|
+
C3D = Card(0x20, R3, DIAMOND, "3D", "Three of Diamonds", "3♦")
|
|
70
|
+
C3H = Card(0x40, R3, HEART, "3H", "Three of Hearts", "3♥")
|
|
71
|
+
C3S = Card(0x80, R3, SPADE, "3S", "Three of Spades", "3♠")
|
|
72
|
+
|
|
73
|
+
C4C = Card(0x100, R4, CLUB, "4C", "Four of Clubs", "4♣")
|
|
74
|
+
C4D = Card(0x200, R4, DIAMOND, "4D", "Four of Diamonds", "4♦")
|
|
75
|
+
C4H = Card(0x400, R4, HEART, "4H", "Four of Hearts", "4♥")
|
|
76
|
+
C4S = Card(0x800, R4, SPADE, "4S", "Four of Spades", "4♠")
|
|
77
|
+
|
|
78
|
+
C5C = Card(0x1000, R5, CLUB, "5C", "Five of Clubs", "5♣")
|
|
79
|
+
C5D = Card(0x2000, R5, DIAMOND, "5D", "Five of Diamonds", "5♦")
|
|
80
|
+
C5H = Card(0x4000, R5, HEART, "5H", "Five of Hearts", "5♥")
|
|
81
|
+
C5S = Card(0x8000, R5, SPADE, "5S", "Five of Spades", "5♠")
|
|
82
|
+
|
|
83
|
+
C6C = Card(0x10000, R6, CLUB, "6C", "Six of Clubs", "6♣")
|
|
84
|
+
C6D = Card(0x20000, R6, DIAMOND, "6D", "Six of Diamonds", "6♦")
|
|
85
|
+
C6H = Card(0x40000, R6, HEART, "6H", "Six of Hearts", "6♥")
|
|
86
|
+
C6S = Card(0x80000, R6, SPADE, "6S", "Six of Spades", "6♠")
|
|
87
|
+
|
|
88
|
+
C7C = Card(0x100000, R7, CLUB, "7C", "Seven of Clubs", "7♣")
|
|
89
|
+
C7D = Card(0x200000, R7, DIAMOND, "7D", "Seven of Diamonds", "7♦")
|
|
90
|
+
C7H = Card(0x400000, R7, HEART, "7H", "Seven of Hearts", "7♥")
|
|
91
|
+
C7S = Card(0x800000, R7, SPADE, "7S", "Seven of Spades", "7♠")
|
|
92
|
+
|
|
93
|
+
C8C = Card(0x1000000, R8, CLUB, "8C", "Eight of Clubs", "8♣")
|
|
94
|
+
C8D = Card(0x2000000, R8, DIAMOND, "8D", "Eight of Diamonds", "8♦")
|
|
95
|
+
C8H = Card(0x4000000, R8, HEART, "8H", "Eight of Hearts", "8♥")
|
|
96
|
+
C8S = Card(0x8000000, R8, SPADE, "8S", "Eight of Spades", "8♠")
|
|
97
|
+
|
|
98
|
+
C9C = Card(0x10000000, R9, CLUB, "9C", "Nine of Clubs", "9♣")
|
|
99
|
+
C9D = Card(0x20000000, R9, DIAMOND, "9D", "Nine of Diamonds", "9♦")
|
|
100
|
+
C9H = Card(0x40000000, R9, HEART, "9H", "Nine of Hearts", "9♥")
|
|
101
|
+
C9S = Card(0x80000000, R9, SPADE, "9S", "Nine of Spades", "9♠")
|
|
102
|
+
|
|
103
|
+
CTC = Card(0x100000000, RT, CLUB, "TC", "Ten of Clubs", "10♣")
|
|
104
|
+
CTD = Card(0x200000000, RT, DIAMOND, "TD", "Ten of Diamonds", "10♦")
|
|
105
|
+
CTH = Card(0x400000000, RT, HEART, "TH", "Ten of Hearts", "10♥")
|
|
106
|
+
CTS = Card(0x800000000, RT, SPADE, "TS", "Ten of Spades", "10♠")
|
|
107
|
+
|
|
108
|
+
CJC = Card(0x1000000000, RJ, CLUB, "JC", "Jack of Clubs", "J♣")
|
|
109
|
+
CJD = Card(0x2000000000, RJ, DIAMOND, "JD", "Jack of Diamonds", "J♦")
|
|
110
|
+
CJH = Card(0x4000000000, RJ, HEART, "JH", "Jack of Hearts", "J♥")
|
|
111
|
+
CJS = Card(0x8000000000, RJ, SPADE, "JS", "Jack of Spades", "J♠")
|
|
112
|
+
|
|
113
|
+
CQC = Card(0x10000000000, RQ, CLUB, "QC", "Queen of Clubs", "Q♣")
|
|
114
|
+
CQD = Card(0x20000000000, RQ, DIAMOND, "QD", "Queen of Diamonds", "Q♦")
|
|
115
|
+
CQH = Card(0x40000000000, RQ, HEART, "QH", "Queen of Hearts", "Q♥")
|
|
116
|
+
CQS = Card(0x80000000000, RQ, SPADE, "QS", "Queen of Spades", "Q♠")
|
|
117
|
+
|
|
118
|
+
CKC = Card(0x100000000000, RK, CLUB, "KC", "King of Clubs", "K♣")
|
|
119
|
+
CKD = Card(0x200000000000, RK, DIAMOND, "KD", "King of Diamonds", "K♦")
|
|
120
|
+
CKH = Card(0x400000000000, RK, HEART, "KH", "King of Hearts", "K♥")
|
|
121
|
+
CKS = Card(0x800000000000, RK, SPADE, "KS", "King of Spades", "K♠")
|
|
122
|
+
|
|
123
|
+
CAC = Card(0x1000000000000, RA, CLUB, "AC", "Ace of Clubs", "A♣")
|
|
124
|
+
CAD = Card(0x2000000000000, RA, DIAMOND, "AD", "Ace of Diamonds", "A♦")
|
|
125
|
+
CAH = Card(0x4000000000000, RA, HEART, "AH", "Ace of Hearts", "A♥")
|
|
126
|
+
CAS = Card(0x8000000000000, RA, SPADE, "AS", "Ace of Spades", "A♠")
|
|
127
|
+
|
|
128
|
+
ALL_CARDS = [
|
|
129
|
+
C2C, C2D, C2H, C2S,
|
|
130
|
+
C3C, C3D, C3H, C3S,
|
|
131
|
+
C4C, C4D, C4H, C4S,
|
|
132
|
+
C5C, C5D, C5H, C5S,
|
|
133
|
+
C6C, C6D, C6H, C6S,
|
|
134
|
+
C7C, C7D, C7H, C7S,
|
|
135
|
+
C8C, C8D, C8H, C8S,
|
|
136
|
+
C9C, C9D, C9H, C9S,
|
|
137
|
+
CTC, CTD, CTH, CTS,
|
|
138
|
+
CJC, CJD, CJH, CJS,
|
|
139
|
+
CQC, CQD, CQH, CQS,
|
|
140
|
+
CKC, CKD, CKH, CKS,
|
|
141
|
+
CAC, CAD, CAH, CAS,
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def sort_cards(cards: List[Card]) -> List[Card]:
|
|
146
|
+
"""Sort cards by rank"""
|
|
147
|
+
return sorted(cards, key=lambda card: card.rank.key)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def cards_mask(cards: List[Card]) -> int:
|
|
151
|
+
"""
|
|
152
|
+
Given an iterable of Card objects with a .id attribute (an int bit‐mask),
|
|
153
|
+
return the combined mask of all those cards.
|
|
154
|
+
"""
|
|
155
|
+
return reduce(lambda mask, card: mask | card.key, cards, 0)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# def rank_mask_from_cards(cards: int) -> int:
|
|
159
|
+
# """
|
|
160
|
+
# Given an int bit-mask of cards, return the combined mask of
|
|
161
|
+
# all those cards.
|
|
162
|
+
# """
|
|
163
|
+
# rmask = 0
|
|
164
|
+
# for card in ALL_CARDS:
|
|
165
|
+
# if cards & card.key != 0:
|
|
166
|
+
# rmask |= card.rank.key
|
|
167
|
+
# return rmask
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def extract_cards(cards: int) -> List[Card]:
|
|
171
|
+
"""
|
|
172
|
+
Given an int bit-mask of cards, return the list of Card objects
|
|
173
|
+
that make up that mask.
|
|
174
|
+
"""
|
|
175
|
+
return [
|
|
176
|
+
card for card in ALL_CARDS if cards & card.key != 0
|
|
177
|
+
]
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Deck"""
|
|
2
|
+
from random import shuffle
|
|
3
|
+
from typing import List
|
|
4
|
+
from functools import reduce
|
|
5
|
+
from .rank import (
|
|
6
|
+
RA,
|
|
7
|
+
RK,
|
|
8
|
+
RQ,
|
|
9
|
+
RJ,
|
|
10
|
+
RT,
|
|
11
|
+
R9,
|
|
12
|
+
R8,
|
|
13
|
+
R7,
|
|
14
|
+
R6,
|
|
15
|
+
R5,
|
|
16
|
+
R4,
|
|
17
|
+
R3,
|
|
18
|
+
R2,
|
|
19
|
+
)
|
|
20
|
+
from .card import ALL_CARDS, Card, cards_mask
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
CLUBS = 0x1111111111111
|
|
24
|
+
DIAMONDS = 0x2222222222222
|
|
25
|
+
HEARTS = 0x4444444444444
|
|
26
|
+
SPADES = 0x8888888888888
|
|
27
|
+
|
|
28
|
+
SAME_RANKS = [
|
|
29
|
+
0xF, 0xF0, 0xF00, 0xF000, 0xF0000, 0xF00000,
|
|
30
|
+
0xF000000, 0xF0000000, 0xF00000000, 0xF000000000,
|
|
31
|
+
0xF0000000000, 0xF00000000000, 0xF000000000000
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
RANKS_DESC = [RA, RK, RQ, RJ, RT, R9, R8, R7, R6, R5, R4, R3, R2]
|
|
35
|
+
|
|
36
|
+
FULL_DECK_MASK = reduce(lambda acc, card: acc | card.key, ALL_CARDS, 0)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Deck:
|
|
40
|
+
"""Deck"""
|
|
41
|
+
def __init__(self) -> None:
|
|
42
|
+
self.bitmask = 0
|
|
43
|
+
for card in ALL_CARDS:
|
|
44
|
+
self.bitmask |= card.key
|
|
45
|
+
|
|
46
|
+
def clear(self) -> None:
|
|
47
|
+
"""Clear the deck"""
|
|
48
|
+
self.bitmask = 0
|
|
49
|
+
|
|
50
|
+
def reset(self) -> None:
|
|
51
|
+
"""Reset the deck"""
|
|
52
|
+
self.bitmask = FULL_DECK_MASK
|
|
53
|
+
|
|
54
|
+
def contains(self, cards: int) -> bool:
|
|
55
|
+
"""Check if the deck contains a list of cards
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
cards (int): List of cards to check
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
bool: True if the deck contains all the cards, False otherwise
|
|
62
|
+
"""
|
|
63
|
+
return cards & self.bitmask == cards
|
|
64
|
+
|
|
65
|
+
def get_cards(self) -> List[Card]:
|
|
66
|
+
"""Get all cards in the deck
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
List[Card]: List of cards in the deck
|
|
70
|
+
"""
|
|
71
|
+
cards: List[Card] = []
|
|
72
|
+
for card in ALL_CARDS:
|
|
73
|
+
if self.contains(card.key):
|
|
74
|
+
cards.append(card)
|
|
75
|
+
return cards
|
|
76
|
+
|
|
77
|
+
def shuffle(self) -> List[Card]:
|
|
78
|
+
"""Shuffle the deck"""
|
|
79
|
+
cards = self.get_cards()
|
|
80
|
+
shuffle(cards)
|
|
81
|
+
return cards
|
|
82
|
+
|
|
83
|
+
def add_card(self, card: Card) -> None:
|
|
84
|
+
"""Add a card to the deck"""
|
|
85
|
+
self.bitmask |= card.key
|
|
86
|
+
|
|
87
|
+
def remove_card(self, card: Card) -> None:
|
|
88
|
+
"""Remove a card from the deck"""
|
|
89
|
+
self.bitmask &= ~card.key
|
|
90
|
+
|
|
91
|
+
def deal_cards(self, count: int = 1) -> int:
|
|
92
|
+
"""Deal cards from the deck
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
count (int, optional): Number of cards to deal. Defaults to 1.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
List[Card]: List of cards dealt
|
|
99
|
+
"""
|
|
100
|
+
cards: List[Card] = []
|
|
101
|
+
for _ in range(count):
|
|
102
|
+
card = self.shuffle().pop()
|
|
103
|
+
self.remove_card(card)
|
|
104
|
+
cards.append(card)
|
|
105
|
+
return cards_mask(cards)
|
|
106
|
+
|
|
107
|
+
def deal_specific_card(self, card: Card) -> int:
|
|
108
|
+
"""Deal a specific card from the deck"""
|
|
109
|
+
if not self.contains(card.key):
|
|
110
|
+
raise ValueError("Card not in deck")
|
|
111
|
+
self.remove_card(card)
|
|
112
|
+
return cards_mask([card])
|
|
113
|
+
|
|
114
|
+
def deal_specific_cards(self, cards: List[Card]) -> int:
|
|
115
|
+
"""Deal specific cards from the deck"""
|
|
116
|
+
for card in cards:
|
|
117
|
+
self.deal_specific_card(card)
|
|
118
|
+
return cards_mask(cards)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Rank"""
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class Rank:
|
|
7
|
+
"""Rank"""
|
|
8
|
+
key: int
|
|
9
|
+
code: str
|
|
10
|
+
name: str
|
|
11
|
+
|
|
12
|
+
def __str__(self) -> str:
|
|
13
|
+
return self.code
|
|
14
|
+
|
|
15
|
+
def __repr__(self) -> str:
|
|
16
|
+
return self.code
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
R2 = Rank(0x1, "2", "Two")
|
|
20
|
+
R3 = Rank(0x2, "3", "Three")
|
|
21
|
+
R4 = Rank(0x4, "4", "Four")
|
|
22
|
+
R5 = Rank(0x8, "5", "Five")
|
|
23
|
+
R6 = Rank(0x10, "6", "Six")
|
|
24
|
+
R7 = Rank(0x20, "7", "Seven")
|
|
25
|
+
R8 = Rank(0x40, "8", "Eight")
|
|
26
|
+
R9 = Rank(0x80, "9", "Nine")
|
|
27
|
+
RT = Rank(0x100, "10", "Ten")
|
|
28
|
+
RJ = Rank(0x200, "J", "Jack")
|
|
29
|
+
RQ = Rank(0x400, "Q", "Queen")
|
|
30
|
+
RK = Rank(0x800, "K", "King")
|
|
31
|
+
RA = Rank(0x1000, "A", "Ace")
|
|
32
|
+
|
|
33
|
+
NUM_RANKS = 13
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Suit"""
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class Suit:
|
|
7
|
+
"""Suit"""
|
|
8
|
+
key: int
|
|
9
|
+
code: str
|
|
10
|
+
name: str
|
|
11
|
+
symbol: str
|
|
12
|
+
|
|
13
|
+
def __str__(self) -> str:
|
|
14
|
+
return self.symbol
|
|
15
|
+
|
|
16
|
+
def __repr__(self) -> str:
|
|
17
|
+
return self.symbol
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
CLUB = Suit(0x1, "C", "Club", "♣")
|
|
21
|
+
DIAMOND = Suit(0x2, "D", "Diamond", "♦")
|
|
22
|
+
HEART = Suit(0x4, "H", "Heart", "♥")
|
|
23
|
+
SPADE = Suit(0x8, "S", "Spade", "♠")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cardspy"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "Functionalities for playing cards"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "bd.noads@gmail.com"}
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"mypy (>=1.15.0,<2.0.0)"
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[tool.poetry]
|
|
15
|
+
|
|
16
|
+
[tool.poetry.group.dev.dependencies]
|
|
17
|
+
pytest = "^8.3.5"
|
|
18
|
+
|
|
19
|
+
[build-system]
|
|
20
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
21
|
+
build-backend = "poetry.core.masonry.api"
|