Compare commits
5 Commits
master
...
fad15e84c5
| Author | SHA1 | Date | |
|---|---|---|---|
| fad15e84c5 | |||
| 4f9f790086 | |||
| 2c421727e5 | |||
| c1120bcd93 | |||
| 2e676e4708 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -636,7 +636,8 @@ DerivedData/
|
|||||||
.LSOverride
|
.LSOverride
|
||||||
|
|
||||||
# Icon must end with two \r
|
# Icon must end with two \r
|
||||||
Icon
|
Icon
|
||||||
|
|
||||||
|
|
||||||
# Thumbnails
|
# Thumbnails
|
||||||
._*
|
._*
|
||||||
@@ -683,3 +684,4 @@ $RECYCLE.BIN/
|
|||||||
# Windows shortcuts
|
# Windows shortcuts
|
||||||
*.lnk
|
*.lnk
|
||||||
|
|
||||||
|
.vscode/settings.json
|
||||||
|
|||||||
0
BearOnline/__init__.py
Normal file
0
BearOnline/__init__.py
Normal file
2
BearOnline/cards/__init__.py
Normal file
2
BearOnline/cards/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from ._card import Card
|
||||||
|
from ._playingcard import PlayingCard
|
||||||
25
BearOnline/cards/_card.py
Normal file
25
BearOnline/cards/_card.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import abc
|
||||||
|
from abc import ABC
|
||||||
|
from functools import total_ordering
|
||||||
|
|
||||||
|
@total_ordering
|
||||||
|
class Card(ABC):
|
||||||
|
def __init__(self, rank, suit):
|
||||||
|
self._rank = rank
|
||||||
|
self._suit = suit
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def __lt__(self, other):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def __eq__(self, other):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rank(self):
|
||||||
|
return self._rank
|
||||||
|
|
||||||
|
@property
|
||||||
|
def suit(self):
|
||||||
|
return self._suit
|
||||||
65
BearOnline/cards/_playingcard.py
Normal file
65
BearOnline/cards/_playingcard.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
from . import Card
|
||||||
|
|
||||||
|
|
||||||
|
class PlayingCard(Card):
|
||||||
|
|
||||||
|
# internal class attributes for a playing card including valid suits and ranks
|
||||||
|
suits = {"SPADES": "BLACK", "CLUBS": "BLACK",
|
||||||
|
"HEARTS": "RED", "DIAMONDS": "RED"}
|
||||||
|
|
||||||
|
ranks = {rank[0].upper() if not rank.isnumeric() else rank: rank.upper() for rank in ["ace"]+[str(i)
|
||||||
|
for i in range(2, 11)] + ["jack", "queen", "king"]}
|
||||||
|
|
||||||
|
# assign an absolute numeric value to each rank for easy compairison. Ace is 0 to make it high/low
|
||||||
|
_values = {rank: value for value, rank in enumerate(ranks.keys())}
|
||||||
|
|
||||||
|
def __init__(self, rank, suit):
|
||||||
|
# do some validation of rank and suit
|
||||||
|
if not isinstance(rank, str):
|
||||||
|
try:
|
||||||
|
if str(rank).isnumeric():
|
||||||
|
rank = str(rank)
|
||||||
|
else:
|
||||||
|
raise ValueError
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(f"Rank should be a string")
|
||||||
|
|
||||||
|
if not isinstance(suit, str):
|
||||||
|
raise TypeError("Suit should be a string.")
|
||||||
|
|
||||||
|
if not suit.lower().strip() in self.suits.keys():
|
||||||
|
raise ValueError(
|
||||||
|
f"'{suit.capitalize()}' is not a valid suit in a standard playing card deck")
|
||||||
|
|
||||||
|
if not rank.upper() in chain(self.ranks.keys(), self.ranks.values()):
|
||||||
|
raise IndexError(
|
||||||
|
f"'{rank.capitalize()}' is out of the range of valid playing card ranks")
|
||||||
|
|
||||||
|
# initialize the class and store its color
|
||||||
|
super().__init__(rank.upper(), suit.upper())
|
||||||
|
self._color = self.suits[suit]
|
||||||
|
|
||||||
|
# define __lt__ and __eq__ (< and =) as required by abstract Card class to take advantage of total ordering of Card
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, PlayingCard) or issubclass(other, PlayingCard):
|
||||||
|
raise NotImplementedError(f"Comparison of {type(self).__name__} to {type(other).__name__} is not supported")
|
||||||
|
return self._values[self.rank] == self._values[other.rank]
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if not isinstance(other, PlayingCard) or issubclass(other, PlayingCard):
|
||||||
|
raise NotImplementedError(f"Comparison of {type(self).__name__} to {type(other).__name__} is not supported")
|
||||||
|
# since Ace is 0, it will always pass one of these two conditions so it is always less than
|
||||||
|
return (self._values[self.rank] < self._values[other.rank]) or (-1 * self._values[self.rank] > -1 * self._values[other.rank])
|
||||||
|
|
||||||
|
# the total ordering of Card won't get __gt__ (>) right because it's not equivalent to not __lt__ because of Ace, so explicitly define it
|
||||||
|
def __gt__(self, other):
|
||||||
|
if not isinstance(other, PlayingCard) or issubclass(other, PlayingCard):
|
||||||
|
raise NotImplementedError(f"Comparison of {type(self).__name__} to {type(other).__name__} is not supported")
|
||||||
|
# since Ace is 0, it will always pass one of these two conditions so it is always greater than
|
||||||
|
return (self._values[self.rank] > self._values[other.rank]) or (-1 * self._values[self.rank] < -1 * self._values[other.rank])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def color(self):
|
||||||
|
return self._color
|
||||||
5
BearOnline/cards/collections/__init__.py
Normal file
5
BearOnline/cards/collections/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from ._cardcollection import CardCollection
|
||||||
|
from ._deck import Deck
|
||||||
|
from ._playingcarddeck import PlayingCardDeck
|
||||||
|
from ._nullcollection import NullDeck
|
||||||
|
from ._hand import Hand
|
||||||
10
BearOnline/cards/collections/_cardcollection.py
Normal file
10
BearOnline/cards/collections/_cardcollection.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from collections import deque
|
||||||
|
|
||||||
|
from .. import Card
|
||||||
|
|
||||||
|
class CardCollection(deque):
|
||||||
|
def __init__(self, iterable, maxlen=None):
|
||||||
|
if not len(iterable) == 0 or all([isinstance(Card, item) for item in iterable]):
|
||||||
|
raise TypeError("CardCollections must only contain Cards")
|
||||||
|
else:
|
||||||
|
super().__init__(iterable, maxlen)
|
||||||
34
BearOnline/cards/collections/_deck.py
Normal file
34
BearOnline/cards/collections/_deck.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from collections.abc import Iterable
|
||||||
|
from itertools import cycle
|
||||||
|
from more_itertools import random_permutation
|
||||||
|
|
||||||
|
from ._cardcollection import CardCollection
|
||||||
|
|
||||||
|
class Deck(CardCollection):
|
||||||
|
def draw(self):
|
||||||
|
return self.pop()
|
||||||
|
|
||||||
|
def shuffle(self):
|
||||||
|
shuffled = list(random_permutation(self))
|
||||||
|
self.clear()
|
||||||
|
self.extend(shuffled)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def deal(self, card_collections, cards_per_rotation=1, max_rotations=None):
|
||||||
|
if not issubclass(card_collections, Iterable) or not all([issubclass(CardCollection, collection) for collection in card_collections]):
|
||||||
|
raise TypeError("You can only deal cards to an iterable containing CardCollections")
|
||||||
|
|
||||||
|
if max_rotations is None:
|
||||||
|
rotation = cycle(card_collections)
|
||||||
|
elif isinstance(max_rotations, int):
|
||||||
|
rotation = card_collections * max_rotations
|
||||||
|
else:
|
||||||
|
raise TypeError("Max rotations should be a positive integer")
|
||||||
|
|
||||||
|
for card_collection in rotation:
|
||||||
|
try:
|
||||||
|
for _ in range(cards_per_rotation):
|
||||||
|
card_collection.append(self.draw())
|
||||||
|
except IndexError:
|
||||||
|
break
|
||||||
|
|
||||||
6
BearOnline/cards/collections/_hand.py
Normal file
6
BearOnline/cards/collections/_hand.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from ._cardcollection import CardCollection
|
||||||
|
|
||||||
|
class Hand(CardCollection):
|
||||||
|
def __init__(self, max_handsize=None):
|
||||||
|
super().__init__([], max_handsize)
|
||||||
|
|
||||||
26
BearOnline/cards/collections/_nullcollection.py
Normal file
26
BearOnline/cards/collections/_nullcollection.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from ._deck import Deck
|
||||||
|
|
||||||
|
|
||||||
|
class NullDeck(Deck):
|
||||||
|
def __init__(self):
|
||||||
|
"""Initializes an empty deck that can never contain cards. Useful as a placeholder to prevent cards from being played to a space.
|
||||||
|
"""
|
||||||
|
super().__init__([], 0)
|
||||||
|
|
||||||
|
def __add__(self, *args, **kwargs):
|
||||||
|
raise NotImplementedError("Cards can not be added to a NullDeck")
|
||||||
|
|
||||||
|
def __iadd__(self, *args, **kwargs):
|
||||||
|
raise NotImplementedError("Cards can not be added to a NullDeck")
|
||||||
|
|
||||||
|
def append(self, *args, **kwargs):
|
||||||
|
raise NotImplementedError("Cards can not be added to a NullDeck")
|
||||||
|
|
||||||
|
def appendleft(self, *args, **kwargs):
|
||||||
|
raise NotImplementedError("Cards can not be added to a NullDeck")
|
||||||
|
|
||||||
|
def extend(self, *args, **kwargs):
|
||||||
|
raise NotImplementedError("Cards can not be added to a NullDeck")
|
||||||
|
|
||||||
|
def extendleft(self, *args, **kwargs):
|
||||||
|
raise NotImplementedError("Cards can not be added to a NullDeck")
|
||||||
9
BearOnline/cards/collections/_playingcarddeck.py
Normal file
9
BearOnline/cards/collections/_playingcarddeck.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from itertools import product, starmap
|
||||||
|
from ._deck import Deck
|
||||||
|
from .. import PlayingCard
|
||||||
|
|
||||||
|
class PlayingCardDeck(Deck):
|
||||||
|
def __init__(self):
|
||||||
|
#initialize the deck with the product of the ranks and suits
|
||||||
|
super().__init__(list(starmap(PlayingCard, product(PlayingCard.ranks.keys(), PlayingCard.suits.keys()))))
|
||||||
|
|
||||||
85
BearOnline/game/_player.py
Normal file
85
BearOnline/game/_player.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
from collections.abc import Iterable
|
||||||
|
from functools import singledispatch
|
||||||
|
|
||||||
|
from ..cards.collections import Hand
|
||||||
|
from ..cards import Card
|
||||||
|
|
||||||
|
class Player(object):
|
||||||
|
|
||||||
|
valid_roles = {"dealer","host","guest"}
|
||||||
|
|
||||||
|
def __init__(self, name, roles={}):
|
||||||
|
self._name = name
|
||||||
|
self._hand = Hand()
|
||||||
|
self._score = 0
|
||||||
|
if not roles:
|
||||||
|
roles = {}
|
||||||
|
elif isinstance(roles, str):
|
||||||
|
if roles in self.valid_roles:
|
||||||
|
roles = {roles}
|
||||||
|
else:
|
||||||
|
raise ValueError(f"{roles} is not a valid role.")
|
||||||
|
elif isinstance(roles, Iterable):
|
||||||
|
if all([role in self.valid_roles for role in roles]):
|
||||||
|
self._roles = set(roles)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"{', '.join(set(roles) - self.valid_roles)} are not valid roles")
|
||||||
|
else:
|
||||||
|
raise TypeError("Role should be a string or iterable")
|
||||||
|
|
||||||
|
@singledispatch
|
||||||
|
def __add__(self, other):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@__add__.register
|
||||||
|
def _(self, other: int):
|
||||||
|
self.score += other
|
||||||
|
|
||||||
|
@__add__.register
|
||||||
|
def _(self, other: Card):
|
||||||
|
self._hand.append(other)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hand(self):
|
||||||
|
return self._hand
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@name.setter
|
||||||
|
def name(self, value):
|
||||||
|
self._name = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def score(self):
|
||||||
|
return self._score
|
||||||
|
|
||||||
|
@score.setter
|
||||||
|
def score(self, value):
|
||||||
|
self._score = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def roles(self):
|
||||||
|
return self._roles
|
||||||
|
|
||||||
|
@roles.setter
|
||||||
|
def roles(self, value):
|
||||||
|
if not value in self.valid_roles:
|
||||||
|
raise ValueError(f"Invalid Player role {value}")
|
||||||
|
|
||||||
|
if (value == "host" or "host" in value) and "host" in self._roles:
|
||||||
|
raise RuntimeError("Once a player is the host, they are the host forever")
|
||||||
|
|
||||||
|
if isinstance(value, str):
|
||||||
|
self._roles ^= set((value,))
|
||||||
|
elif isinstance(Iterable, value):
|
||||||
|
self._roles ^= set(value)
|
||||||
|
else:
|
||||||
|
raise TypeError("Invalid role type")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cards_in_hand(self):
|
||||||
|
return len(self._hand)
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user