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.
|
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.>
|
Cardy, a Python package for building card games
|
||||||
Copyright (C) <year> <name of author>
|
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.
|
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:
|
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 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.
|
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