Exploding Kittens

Here’s a bit more advanced game using pyCardDeck. This code itself is not the full game, but should showcase how the library is meant to be used. If you find anything in here impractical or not clean, easy and nice, please file an issue!

import pyCardDeck
from pyCardDeck.cards import BaseCard
from random import randrange


class Player:

    def __init__(self):
        self.hand = []

    def turn(self):
        pass

    def skip(self):
        pass

    def take_turn_twice(self):
        self.turn()
        self.turn()

    def nope_prompt(self) -> bool:
        for card in self.hand:
            if card.name == "Nope":
                if input("Do you want to use your Nope card?").lower().startswith("y"):
                    return True
                else:
                    return False
        return False

    def insert_explode(self) -> int:
        position = int(input("At which position from top do you want to insert Exploding Kitten back into the deck?"))
        return position


class KittenCard(BaseCard):

    def __init__(self, name: str, targetable: bool = False, selfcast: bool = False):
        super().__init__(name)
        self.selfcast = selfcast
        self.targetable = targetable

    def effect(self, player: Player, target: Player):
        pass


class ExplodeCard(KittenCard):

    def __init__(self, name: str = "Exploding Kitten"):
        super().__init__(name)


class DefuseCard(KittenCard):

    def __init__(self, deck: pyCardDeck.deck, name: str = "Defuse"):
        super().__init__(name, selfcast=True)
        self.deck = deck

    def effect(self, player: Player, target: Player):
        position = player.insert_explode()
        self.deck.add_single(ExplodeCard(), position=position)


class TacocatCard(KittenCard):

    def __init__(self, name: str = "Tacocat"):
        super().__init__(name)


class OverweightCard(KittenCard):

    def __init__(self, name: str = "Overweight Bikini Cat"):
        super().__init__(name)


class ShuffleCard(KittenCard):

    def __init__(self, deck: pyCardDeck.Deck, name: str = "Shuffle"):
        super().__init__(name)
        self.deck = deck

    def effect(self, player: Player, target: Player):
        self.deck.shuffle()


class AttackCard(KittenCard):

    def __init__(self, name: str = "Attack"):
        super().__init__(name, selfcast=True, targetable=True)

    def effect(self, player: Player, target: Player):
        player.skip()
        target.take_turn_twice()


class SeeTheFuture(KittenCard):

    def __init__(self, deck: pyCardDeck.Deck, name: str = "See The Future"):
        super().__init__(name)
        self.deck = deck

    def effect(self, player: Player, target: Player):
        self.deck.show_top(3)


class NopeCard(KittenCard):

    def __init__(self, name: str = "Nope"):
        super().__init__(name)


class SkipCard(KittenCard):

    def __init__(self, name: str = "Skip"):
        super().__init__(name, selfcast=True)

    def effect(self, player: Player, target: Player):
        player.skip()


class FavorCard(KittenCard):

    def __init__(self, name: str = "Favor"):
        super().__init__(name, targetable=True, selfcast=True)

    def effect(self, player: Player, target: Player):
        random_target_card = target.hand.pop(randrange(target.hand))
        player.hand.append(random_target_card)


class Game:

    def __init__(self, players: list):
        self.deck = pyCardDeck.Deck()
        self.players = players
        self.prepare_cards()
        self.deal_to_players()
        self.add_defuses()
        self.add_explodes()
        while len(self.players) > 1:
            self.play()

    def play(self):
        pass

    def turn(self):
        pass

    def prepare_cards(self):
        print("Preparing deck from which to deal to players")
        self.deck.add_many(construct_deck(self))

    def deal_to_players(self):
        print("Dealing cards to players")
        for _ in range(4):
            for player in self.players:
                player.hand.append(self.deck.draw())

    def ask_for_nope(self):
        noped = False
        for player in self.players:
            noped = player.nope_prompt()
        return noped

    def add_explodes(self):
        print("Adding explodes to the deck")
        self.deck.add_many([ExplodeCard() for _ in range(len(self.players) - 1)])

    def add_defuses(self):
        print("Adding defuses to the deck")
        self.deck.add_many([DefuseCard(self.deck) for _ in range(6 - len(self.players))])

    def play_card(self, card: KittenCard, player: Player = None, target: Player = None):
        if card.selfcast and player is None:
            raise Exception("You must pass a player who owns the card!")
        elif card.targetable and target is None:
            raise Exception("You must pass a target!")
        elif not self.ask_for_nope():
            card.effect(player, target)
        else:
            print("Card was noped :(")



def construct_deck(game: Game):
    card_list = [
        TacocatCard(),
        TacocatCard(),
        TacocatCard(),
        TacocatCard(),
        OverweightCard(),
        OverweightCard(),
        OverweightCard(),
        OverweightCard(),
        ShuffleCard(game.deck),
        ShuffleCard(game.deck),
        ShuffleCard(game.deck),
        ShuffleCard(game.deck),
        AttackCard(),
        AttackCard(),
        AttackCard(),
        AttackCard(),
        SeeTheFuture(game.deck),
        SeeTheFuture(game.deck),
        SeeTheFuture(game.deck),
        SeeTheFuture(game.deck),
        SeeTheFuture(game.deck),
        NopeCard(),
        NopeCard(),
        NopeCard(),
        NopeCard(),
        NopeCard(),
        SkipCard(),
        SkipCard(),
        SkipCard(),
        SkipCard(),
        FavorCard(),
        FavorCard(),
        FavorCard(),
        FavorCard(),
    ]
    return card_list