Add some code
This commit is contained in:
parent
e33c23473a
commit
a7a3c5bed2
6
LICENSE
6
LICENSE
@ -208,8 +208,8 @@ If you develop a new program, and you want it to be of the greatest possible use
|
||||
|
||||
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
Cardy, a Python package for building card games
|
||||
Copyright (C) 2021 Ian Fijolek
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
@ -221,7 +221,7 @@ Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
Cardy Copyright (C) 2021 Ian Fijolek
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
|
||||
|
||||
|
15
Makefile
Normal file
15
Makefile
Normal file
@ -0,0 +1,15 @@
|
||||
.PHONY: test clean all
|
||||
VENV := venv
|
||||
PYTHON := python3
|
||||
|
||||
.PHONY: default
|
||||
default: run
|
||||
|
||||
.PHONY: run
|
||||
run: $(VENV)
|
||||
$(VENV)/bin/python -m cardy
|
||||
|
||||
$(VENV):
|
||||
$(PYTHON) -m venv $(VENV)
|
||||
$(VENV)/bin/pip install -r requirements-dev.txt -r requirements.txt
|
||||
|
0
cardy/__init__.py
Normal file
0
cardy/__init__.py
Normal file
12
cardy/__main__.py
Normal file
12
cardy/__main__.py
Normal file
@ -0,0 +1,12 @@
|
||||
from cardy.phase_10 import Game
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
g = Game(["Ian", "Jessica"])
|
||||
print(g)
|
||||
|
||||
print(f"Deal it out {g.dealer.name}")
|
||||
g.deal()
|
||||
print(g)
|
||||
|
||||
print(f"Next player is {g.next_player.name}")
|
178
cardy/deck.py
Normal file
178
cardy/deck.py
Normal file
@ -0,0 +1,178 @@
|
||||
from random import shuffle
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Iterable
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class EmptyPileError(IndexError):
|
||||
pass
|
||||
|
||||
|
||||
class Card(object):
|
||||
_color: Optional[str]
|
||||
_face: Optional[str]
|
||||
_suit: Optional[str]
|
||||
_value: int
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
value: int,
|
||||
suit: Optional[str] = None,
|
||||
color: Optional[str] = None,
|
||||
face: Optional[str] = None,
|
||||
):
|
||||
self._color = color
|
||||
self._face = face
|
||||
self._suit = suit
|
||||
self._value = value
|
||||
|
||||
@property
|
||||
def color(self) -> Optional[str]:
|
||||
return self._color
|
||||
|
||||
@property
|
||||
def face(self) -> Optional[str]:
|
||||
return self._face
|
||||
|
||||
@property
|
||||
def suit(self) -> Optional[str]:
|
||||
return self._suit
|
||||
|
||||
@property
|
||||
def value(self) -> int:
|
||||
return self._value
|
||||
|
||||
def __lt__(self, other) -> bool:
|
||||
return self.value < other.value
|
||||
|
||||
def __le__(self, other) -> bool:
|
||||
return self.value <= other.value
|
||||
|
||||
def __gt__(self, other) -> bool:
|
||||
return self.value > other.value
|
||||
|
||||
def __ge__(self, other) -> bool:
|
||||
return self.value >= other.value
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
return (
|
||||
self.value == other.value and
|
||||
self.face == other.face and
|
||||
self.suit == other.suit and
|
||||
self.color == other.color
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Card value={self.value} suit={self.suit} color={self.color} face={self.face}>"
|
||||
|
||||
def json(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"value": self.value,
|
||||
"suit": self.suit,
|
||||
"color": self.color,
|
||||
"face": self.face,
|
||||
}
|
||||
|
||||
|
||||
class FaceDownCard(Card):
|
||||
def json(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"value": "FACE_DOWN",
|
||||
}
|
||||
|
||||
|
||||
class WildCard(Card):
|
||||
def __init__(self):
|
||||
super().__init__(-1, face="Wild", suit="Wild", color="Wild")
|
||||
|
||||
def __lt__(self, other) -> bool:
|
||||
return True
|
||||
|
||||
def __le__(self, other) -> bool:
|
||||
return True
|
||||
|
||||
def __gt__(self, other) -> bool:
|
||||
return True
|
||||
|
||||
def __ge__(self, other) -> bool:
|
||||
return True
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
class Pile(object):
|
||||
# Pile of cards where 0 is the top and -1 is the bottom
|
||||
_pile: List[Card]
|
||||
|
||||
def __init__(self, cards: Optional[List[Card]] = None):
|
||||
if cards is not None:
|
||||
self._pile = cards
|
||||
else:
|
||||
self._pile = []
|
||||
|
||||
def add_top(self, card: Card):
|
||||
self._pile.insert(0, card)
|
||||
|
||||
def add_bottom(self, card: Card):
|
||||
self._pile.insert(-1, card)
|
||||
|
||||
def peek_top(self) -> Optional[Card]:
|
||||
if self._pile:
|
||||
return self._pile[0]
|
||||
|
||||
return None
|
||||
|
||||
def draw_card(self) -> Card:
|
||||
if not self._pile:
|
||||
raise EmptyPileError("Cannot draw from pile because it's empty")
|
||||
|
||||
return self._pile.pop(0)
|
||||
|
||||
def draw_cards(self, num_cards: int = 1) -> Iterable[Card]:
|
||||
return [self.draw_card() for _ in range(0, num_cards)]
|
||||
|
||||
def shuffle(self):
|
||||
shuffle(self._pile)
|
||||
|
||||
def __getitem__(self, i):
|
||||
return self._cards[i]
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._pile)
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self:
|
||||
return (
|
||||
f"[[{len(self)-1} cards]]"
|
||||
)
|
||||
else:
|
||||
return "[[empty pile]]"
|
||||
|
||||
def json(self) -> List[Dict[str, Any]]:
|
||||
return [c.json() for c in self._pile]
|
||||
|
||||
|
||||
class FaceUpPile(Pile):
|
||||
|
||||
def __str__(self) -> str:
|
||||
top_card = self.peek_top()
|
||||
if top_card:
|
||||
return (
|
||||
f"[[{str(top_card)} +{len(self)-1} cards]]"
|
||||
)
|
||||
else:
|
||||
return "[[empty pile]]"
|
||||
|
||||
|
||||
class OpenCardSet(Pile):
|
||||
|
||||
def __str__(self) -> str:
|
||||
cards = ", ".join((
|
||||
str(card) for card in self._pile
|
||||
))
|
||||
return (
|
||||
f"[{cards}]"
|
||||
)
|
137
cardy/phase_10.py
Normal file
137
cardy/phase_10.py
Normal file
@ -0,0 +1,137 @@
|
||||
from dataclasses import dataclass
|
||||
from itertools import product
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
from cardy.deck import Card
|
||||
from cardy.deck import FaceUpPile
|
||||
from cardy.deck import OpenCardSet
|
||||
from cardy.deck import Pile
|
||||
from cardy.deck import WildCard
|
||||
from cardy.player import Player
|
||||
|
||||
|
||||
class Phase10Card(Card):
|
||||
pass
|
||||
|
||||
|
||||
class Skip(Phase10Card):
|
||||
pass
|
||||
|
||||
|
||||
class Wild(WildCard, Phase10Card):
|
||||
pass
|
||||
|
||||
|
||||
class Deck(Pile):
|
||||
def __init__(self):
|
||||
cards: List[Phase10Card] = []
|
||||
for c in product(range(1, 13), ("red", "blue", "green", "yellow")):
|
||||
cards.append(Phase10Card(c[0], color=c[1]))
|
||||
|
||||
super().__init__(cards)
|
||||
|
||||
|
||||
class PhaseSet(OpenCardSet):
|
||||
pass
|
||||
|
||||
|
||||
class Phase10Player(Player):
|
||||
_table: List[FaceUpPile]
|
||||
|
||||
def __init__(self, name):
|
||||
self._table = []
|
||||
super().__init__(name)
|
||||
|
||||
def take_turn(self) -> Any:
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
table = " no sets played"
|
||||
if self._table:
|
||||
table = "\n".join((" "+str(s) for s in self._table))
|
||||
return "\n".join((
|
||||
f"{self.name}: {len(self._hand)} in hand",
|
||||
"On table:",
|
||||
table,
|
||||
))
|
||||
|
||||
def json(self, is_owner: bool=False) -> Dict[str, Any]:
|
||||
state = {
|
||||
"table": [pile.json() for pile in self._table],
|
||||
}
|
||||
if is_owner:
|
||||
state["hand"] = [card.json() for card in self._hand]
|
||||
if is_owner:
|
||||
state["hand"] = len(self._hand)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Phase10Turn:
|
||||
player: Player
|
||||
draw_card: bool
|
||||
discard_card: Optional[Card]
|
||||
|
||||
play_sets: Optional[List[PhaseSet]]
|
||||
play_on_sets: Optional[Dict[str, List[Card]]]
|
||||
|
||||
|
||||
class Game(object):
|
||||
_dealer_index: int
|
||||
draw_pile: Deck
|
||||
discard_pile: FaceUpPile
|
||||
turn: int
|
||||
game_round: int
|
||||
players: List[Phase10Player]
|
||||
sets: List[OpenCardSet]
|
||||
|
||||
def __init__(self, player_names: List[str]):
|
||||
self._dealer_index = 0
|
||||
self.turn = 1
|
||||
self.game_round = 1
|
||||
self.players = [Phase10Player(name) for name in player_names]
|
||||
self.draw_pile = Deck()
|
||||
self.draw_pile.shuffle()
|
||||
self.discard_pile = FaceUpPile()
|
||||
self.sets = []
|
||||
|
||||
def deal(self):
|
||||
# each player draws 10
|
||||
for _ in range(0, 10):
|
||||
for seat in range(0, len(self.players)):
|
||||
draw_seat = seat + self._dealer_index + 1
|
||||
p = self.players[draw_seat % len(self.players)]
|
||||
p.draw_cards(self.draw_pile)
|
||||
|
||||
def take_turn(self):
|
||||
end_round = self.next_player.take_turn()
|
||||
self.turn += 1
|
||||
if end_round:
|
||||
self._reset_round()
|
||||
|
||||
@property
|
||||
def next_player(self) -> Player:
|
||||
return self.players[self.turn % len(self.players)]
|
||||
|
||||
@property
|
||||
def dealer(self) -> Player:
|
||||
return self.players[self._dealer_index]
|
||||
|
||||
def _reset_round(self):
|
||||
self.draw_pile = Deck()
|
||||
self.draw_pile.shuffle()
|
||||
self.dealer += 1
|
||||
self.deal()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "\n".join((
|
||||
"Game",
|
||||
f"Draw: {str(self.draw_pile)}",
|
||||
f"Discard: {str(self.discard_pile)}",
|
||||
"",
|
||||
"\n\n".join(
|
||||
str(p) for p in self.players
|
||||
),
|
||||
))
|
20
cardy/player.py
Normal file
20
cardy/player.py
Normal file
@ -0,0 +1,20 @@
|
||||
from typing import Any
|
||||
from typing import List
|
||||
|
||||
from cardy.deck import Card
|
||||
from cardy.deck import Pile
|
||||
|
||||
|
||||
class Player(object):
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
self._hand: List[Card] = []
|
||||
|
||||
def draw_cards(self, pile: Pile, num_cards: int = 1):
|
||||
self._hand += pile.draw_cards(num_cards)
|
||||
|
||||
def take_turn(self) -> Any:
|
||||
raise NotImplementedError()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Player name={self.name}>"
|
60
cardy/playing_cards.py
Normal file
60
cardy/playing_cards.py
Normal file
@ -0,0 +1,60 @@
|
||||
from itertools import product
|
||||
from typing import List
|
||||
|
||||
from cardy.deck import Card
|
||||
from cardy.deck import Pile
|
||||
from cardy.deck import WildCard
|
||||
|
||||
|
||||
# SUITES is a collection of tuples with suites and their colors
|
||||
SUITS = (
|
||||
("diamonds", "red"),
|
||||
("hearts", "red"),
|
||||
("clubs", "black"),
|
||||
("spades", "black"),
|
||||
)
|
||||
|
||||
# FACES maps special values to their face names
|
||||
FACES = {
|
||||
11: "Jack",
|
||||
12: "Queen",
|
||||
13: "King",
|
||||
14: "Ace",
|
||||
-1: "Joker",
|
||||
}
|
||||
|
||||
|
||||
class PlayingCard(Card):
|
||||
@property
|
||||
def face(self) -> str:
|
||||
return FACES.get(self.value, str(self.value))
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f"[{self.face.capitalize()} of {self.suit.capitalize()} "
|
||||
f"({self.color.capitalize()})]"
|
||||
)
|
||||
|
||||
|
||||
class Joker(WildCard, PlayingCard):
|
||||
def __str__(self) -> str:
|
||||
return "[Joker]"
|
||||
|
||||
|
||||
class Deck(Pile):
|
||||
def __init__(self, num_jokers: int = 0, aces_high: bool = False):
|
||||
cards: List[PlayingCard] = []
|
||||
card_values = range(2, 15) if aces_high else range(1, 14)
|
||||
for c in product(card_values, SUITS):
|
||||
cards.append(
|
||||
PlayingCard(
|
||||
value=c[0],
|
||||
suit=c[1][0],
|
||||
color=c[1][1],
|
||||
)
|
||||
)
|
||||
|
||||
for _ in range(0, num_jokers):
|
||||
cards.append(Joker())
|
||||
|
||||
super().__init__(cards2)
|
0
requirements-dev.txt
Normal file
0
requirements-dev.txt
Normal file
0
requirements.txt
Normal file
0
requirements.txt
Normal file
Loading…
Reference in New Issue
Block a user