diff --git a/README.md b/README.md index e4fc34a..296a4de 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,7 @@ found [on my site](http://martinfitzpatrick.name/tag/pyqt). 1. [Translator](translate/) - "Translataarrr" (QtDesigner) 1. [Weather](weather/) - "Raindar" (QtDesigner) 1. [Currency converter](currency/) - "Doughnut" (PyQtGraph) - -## In progress - -1. Solitaire - "Ronery" +1. [Solitaire](solitaire/) - "Ronery" (QGraphicsScene) # License diff --git a/solitaire/cards/10C.png b/solitaire/cards/10C.png new file mode 100644 index 0000000..ef6d1c2 Binary files /dev/null and b/solitaire/cards/10C.png differ diff --git a/solitaire/cards/10D.png b/solitaire/cards/10D.png new file mode 100644 index 0000000..632c70e Binary files /dev/null and b/solitaire/cards/10D.png differ diff --git a/solitaire/cards/10H.png b/solitaire/cards/10H.png new file mode 100644 index 0000000..b9245ca Binary files /dev/null and b/solitaire/cards/10H.png differ diff --git a/solitaire/cards/10S.png b/solitaire/cards/10S.png new file mode 100644 index 0000000..7f24eca Binary files /dev/null and b/solitaire/cards/10S.png differ diff --git a/solitaire/cards/11C.png b/solitaire/cards/11C.png new file mode 100644 index 0000000..24e66d2 Binary files /dev/null and b/solitaire/cards/11C.png differ diff --git a/solitaire/cards/11D.png b/solitaire/cards/11D.png new file mode 100644 index 0000000..6b58db8 Binary files /dev/null and b/solitaire/cards/11D.png differ diff --git a/solitaire/cards/11H.png b/solitaire/cards/11H.png new file mode 100644 index 0000000..976c6e8 Binary files /dev/null and b/solitaire/cards/11H.png differ diff --git a/solitaire/cards/11S.png b/solitaire/cards/11S.png new file mode 100644 index 0000000..c8decd8 Binary files /dev/null and b/solitaire/cards/11S.png differ diff --git a/solitaire/cards/12C.png b/solitaire/cards/12C.png new file mode 100644 index 0000000..bfa977c Binary files /dev/null and b/solitaire/cards/12C.png differ diff --git a/solitaire/cards/12D.png b/solitaire/cards/12D.png new file mode 100644 index 0000000..1469f77 Binary files /dev/null and b/solitaire/cards/12D.png differ diff --git a/solitaire/cards/12H.png b/solitaire/cards/12H.png new file mode 100644 index 0000000..97f3900 Binary files /dev/null and b/solitaire/cards/12H.png differ diff --git a/solitaire/cards/12S.png b/solitaire/cards/12S.png new file mode 100644 index 0000000..64b7f6c Binary files /dev/null and b/solitaire/cards/12S.png differ diff --git a/solitaire/cards/13C.png b/solitaire/cards/13C.png new file mode 100644 index 0000000..60c1f9d Binary files /dev/null and b/solitaire/cards/13C.png differ diff --git a/solitaire/cards/13D.png b/solitaire/cards/13D.png new file mode 100644 index 0000000..bf36093 Binary files /dev/null and b/solitaire/cards/13D.png differ diff --git a/solitaire/cards/13H.png b/solitaire/cards/13H.png new file mode 100644 index 0000000..2195756 Binary files /dev/null and b/solitaire/cards/13H.png differ diff --git a/solitaire/cards/13S.png b/solitaire/cards/13S.png new file mode 100644 index 0000000..8d2be0c Binary files /dev/null and b/solitaire/cards/13S.png differ diff --git a/solitaire/cards/1C.png b/solitaire/cards/1C.png new file mode 100644 index 0000000..1d0f031 Binary files /dev/null and b/solitaire/cards/1C.png differ diff --git a/solitaire/cards/1D.png b/solitaire/cards/1D.png new file mode 100644 index 0000000..229b3c6 Binary files /dev/null and b/solitaire/cards/1D.png differ diff --git a/solitaire/cards/1H.png b/solitaire/cards/1H.png new file mode 100644 index 0000000..5c0035e Binary files /dev/null and b/solitaire/cards/1H.png differ diff --git a/solitaire/cards/1S.png b/solitaire/cards/1S.png new file mode 100644 index 0000000..4ab8511 Binary files /dev/null and b/solitaire/cards/1S.png differ diff --git a/solitaire/cards/2C.png b/solitaire/cards/2C.png new file mode 100644 index 0000000..208f2a5 Binary files /dev/null and b/solitaire/cards/2C.png differ diff --git a/solitaire/cards/2D.png b/solitaire/cards/2D.png new file mode 100644 index 0000000..b6ec279 Binary files /dev/null and b/solitaire/cards/2D.png differ diff --git a/solitaire/cards/2H.png b/solitaire/cards/2H.png new file mode 100644 index 0000000..96a766a Binary files /dev/null and b/solitaire/cards/2H.png differ diff --git a/solitaire/cards/2S.png b/solitaire/cards/2S.png new file mode 100644 index 0000000..a8a724f Binary files /dev/null and b/solitaire/cards/2S.png differ diff --git a/solitaire/cards/3C.png b/solitaire/cards/3C.png new file mode 100644 index 0000000..5d3ccf5 Binary files /dev/null and b/solitaire/cards/3C.png differ diff --git a/solitaire/cards/3D.png b/solitaire/cards/3D.png new file mode 100644 index 0000000..eaa57dd Binary files /dev/null and b/solitaire/cards/3D.png differ diff --git a/solitaire/cards/3H.png b/solitaire/cards/3H.png new file mode 100644 index 0000000..ac9ebc8 Binary files /dev/null and b/solitaire/cards/3H.png differ diff --git a/solitaire/cards/3S.png b/solitaire/cards/3S.png new file mode 100644 index 0000000..d382080 Binary files /dev/null and b/solitaire/cards/3S.png differ diff --git a/solitaire/cards/4C.png b/solitaire/cards/4C.png new file mode 100644 index 0000000..c353568 Binary files /dev/null and b/solitaire/cards/4C.png differ diff --git a/solitaire/cards/4D.png b/solitaire/cards/4D.png new file mode 100644 index 0000000..fd25637 Binary files /dev/null and b/solitaire/cards/4D.png differ diff --git a/solitaire/cards/4H.png b/solitaire/cards/4H.png new file mode 100644 index 0000000..b57dacd Binary files /dev/null and b/solitaire/cards/4H.png differ diff --git a/solitaire/cards/4S.png b/solitaire/cards/4S.png new file mode 100644 index 0000000..9781091 Binary files /dev/null and b/solitaire/cards/4S.png differ diff --git a/solitaire/cards/5C.png b/solitaire/cards/5C.png new file mode 100644 index 0000000..e54943f Binary files /dev/null and b/solitaire/cards/5C.png differ diff --git a/solitaire/cards/5D.png b/solitaire/cards/5D.png new file mode 100644 index 0000000..d44adab Binary files /dev/null and b/solitaire/cards/5D.png differ diff --git a/solitaire/cards/5H.png b/solitaire/cards/5H.png new file mode 100644 index 0000000..84fcc68 Binary files /dev/null and b/solitaire/cards/5H.png differ diff --git a/solitaire/cards/5S.png b/solitaire/cards/5S.png new file mode 100644 index 0000000..0001e27 Binary files /dev/null and b/solitaire/cards/5S.png differ diff --git a/solitaire/cards/6C.png b/solitaire/cards/6C.png new file mode 100644 index 0000000..8e329cd Binary files /dev/null and b/solitaire/cards/6C.png differ diff --git a/solitaire/cards/6D.png b/solitaire/cards/6D.png new file mode 100644 index 0000000..d5570c2 Binary files /dev/null and b/solitaire/cards/6D.png differ diff --git a/solitaire/cards/6H.png b/solitaire/cards/6H.png new file mode 100644 index 0000000..3cf5c35 Binary files /dev/null and b/solitaire/cards/6H.png differ diff --git a/solitaire/cards/6S.png b/solitaire/cards/6S.png new file mode 100644 index 0000000..b76a0dc Binary files /dev/null and b/solitaire/cards/6S.png differ diff --git a/solitaire/cards/7C.png b/solitaire/cards/7C.png new file mode 100644 index 0000000..6fa06e9 Binary files /dev/null and b/solitaire/cards/7C.png differ diff --git a/solitaire/cards/7D.png b/solitaire/cards/7D.png new file mode 100644 index 0000000..7604252 Binary files /dev/null and b/solitaire/cards/7D.png differ diff --git a/solitaire/cards/7H.png b/solitaire/cards/7H.png new file mode 100644 index 0000000..2a0eb95 Binary files /dev/null and b/solitaire/cards/7H.png differ diff --git a/solitaire/cards/7S.png b/solitaire/cards/7S.png new file mode 100644 index 0000000..2aa82c8 Binary files /dev/null and b/solitaire/cards/7S.png differ diff --git a/solitaire/cards/8C.png b/solitaire/cards/8C.png new file mode 100644 index 0000000..ad40960 Binary files /dev/null and b/solitaire/cards/8C.png differ diff --git a/solitaire/cards/8D.png b/solitaire/cards/8D.png new file mode 100644 index 0000000..46e6d56 Binary files /dev/null and b/solitaire/cards/8D.png differ diff --git a/solitaire/cards/8H.png b/solitaire/cards/8H.png new file mode 100644 index 0000000..d15c399 Binary files /dev/null and b/solitaire/cards/8H.png differ diff --git a/solitaire/cards/8S.png b/solitaire/cards/8S.png new file mode 100644 index 0000000..8e628ab Binary files /dev/null and b/solitaire/cards/8S.png differ diff --git a/solitaire/cards/9C.png b/solitaire/cards/9C.png new file mode 100644 index 0000000..63f0f4c Binary files /dev/null and b/solitaire/cards/9C.png differ diff --git a/solitaire/cards/9D.png b/solitaire/cards/9D.png new file mode 100644 index 0000000..08b1113 Binary files /dev/null and b/solitaire/cards/9D.png differ diff --git a/solitaire/cards/9H.png b/solitaire/cards/9H.png new file mode 100644 index 0000000..6d5dbad Binary files /dev/null and b/solitaire/cards/9H.png differ diff --git a/solitaire/cards/9S.png b/solitaire/cards/9S.png new file mode 100644 index 0000000..9eb3e50 Binary files /dev/null and b/solitaire/cards/9S.png differ diff --git a/solitaire/images/back.png b/solitaire/images/back.png new file mode 100644 index 0000000..287ae49 Binary files /dev/null and b/solitaire/images/back.png differ diff --git a/solitaire/images/felt.png b/solitaire/images/felt.png new file mode 100644 index 0000000..cf5aaaa Binary files /dev/null and b/solitaire/images/felt.png differ diff --git a/solitaire/images/playing-card.png b/solitaire/images/playing-card.png new file mode 100755 index 0000000..77c45b0 Binary files /dev/null and b/solitaire/images/playing-card.png differ diff --git a/solitaire/images/ronery.png b/solitaire/images/ronery.png new file mode 100644 index 0000000..b9a8094 Binary files /dev/null and b/solitaire/images/ronery.png differ diff --git a/solitaire/solitaire.py b/solitaire/solitaire.py new file mode 100644 index 0000000..4f625cf --- /dev/null +++ b/solitaire/solitaire.py @@ -0,0 +1,665 @@ +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5.QtCore import * + +import os +import random + +WINDOW_SIZE = 840, 600 + +CARD_DIMENSIONS = QSize(80, 116) +CARD_RECT = QRect(0, 0, 80, 116) +CARD_SPACING_X = 110 +CARD_BACK = QImage(os.path.join('images', 'back.png')) + +DEAL_RECT = QRect(30, 30, 110, 140) + +OFFSET_X = 50 +OFFSET_Y = 50 +WORK_STACK_Y = 200 + +SIDE_FACE = 0 +SIDE_BACK = 1 + +SLIPPING_OFFSET = 20 +DEAL_N = 3 + +# We store cards as numbers 1-13, since we only need +# to know their order for solitaire. +SUITS = ["C", "S", "H", "D"] + + +class Signals(QObject): + complete = pyqtSignal() + clicked = pyqtSignal() + doubleclicked = pyqtSignal() + + +class Card(QGraphicsPixmapItem): + + def __init__(self, value, suit, *args, **kwargs): + super(Card, self).__init__(*args, **kwargs) + + self.signals = Signals() + + self.stack = None # Stack this card currently is in. + self.child = None # Card stacked on this one (for work deck). + + # Store the value & suit of the cards internal to it. + self.value = value + self.suit = suit + self.side = None + + # For end animation only. + self.vector = None + + # Cards have no internal transparent areas, so we can use this faster method. + self.setShapeMode(QGraphicsPixmapItem.BoundingRectShape) + self.setFlag(QGraphicsItem.ItemIsMovable) + self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) + + self.load_images() + + def load_images(self): + self.face = QPixmap( + os.path.join('cards', '%s%s.png' % (self.value, self.suit)) + ) + + self.back = QPixmap( + os.path.join('images', 'back.png') + ) + + def turn_face_up(self): + self.side = SIDE_FACE + self.setPixmap(self.face) + + def turn_back_up(self): + self.side = SIDE_BACK + self.setPixmap(self.back) + + @property + def is_face_up(self): + return self.side == SIDE_FACE + + @property + def color(self): + return 'r' if self.suit in ('H', 'D') else 'b' + + def mousePressEvent(self, e): + if not self.is_face_up and self.stack.cards[-1] == self: + self.turn_face_up() # We can do this without checking. + e.accept() + return + + if self.stack and not self.stack.is_free_card(self): + e.ignore() + return + + self.stack.activate() + + e.accept() + + super(Card, self).mouseReleaseEvent(e) + + def mouseReleaseEvent(self, e): + self.stack.deactivate() + + items = self.collidingItems() + if items: + # Find the topmost item from a different stack: + for item in items: + if ((isinstance(item, Card) and item.stack != self.stack) or + (isinstance(item, StackBase) and item != self.stack)): + + if item.stack.is_valid_drop(self): + # Remove card + all children from previous stack, add to the new. + # Note: the only place there will be children is on a workstack. + cards = self.stack.remove_card(self) + item.stack.add_cards(cards) + break + + # Refresh this card's stack, pulling it back if it was dropped. + self.stack.update() + + super(Card, self).mouseReleaseEvent(e) + + def mouseDoubleClickEvent(self, e): + if self.stack.is_free_card(self): + self.signals.doubleclicked.emit() + e.accept() + + super(Card, self).mouseDoubleClickEvent(e) + + +class StackBase(QGraphicsRectItem): + + def __init__(self, *args, **kwargs): + super(StackBase, self).__init__(*args, **kwargs) + + self.setRect(QRectF(CARD_RECT)) + self.setZValue(-1) + + # Cards on this deck, in order. + self.cards = [] + + # Store a self ref, so the collision logic can handle cards and + # stacks with the same approach. + self.stack = self + self.setup() + self.reset() + + def setup(self): + pass + + def reset(self): + self.remove_all_cards() + + def update(self): + for n, card in enumerate(self.cards): + card.setPos( self.pos() + QPointF(n * self.offset_x, n * self.offset_y)) + card.setZValue(n) + + def activate(self): + pass + + def deactivate(self): + pass + + def add_card(self, card, update=True): + card.stack = self + self.cards.append(card) + if update: + self.update() + + def add_cards(self, cards): + for card in cards: + self.add_card(card, update=False) + self.update() + + def remove_card(self, card): + card.stack = None + self.cards.remove(card) + self.update() + return [card] # Returns a list, as WorkStack must return children + + def remove_all_cards(self): + for card in self.cards[:]: + card.stack = None + self.cards = [] + + def take_top_card(self): + try: + card = self.cards[-1] + self.remove_card(card) + return card + except IndexError: + pass + + def is_valid_drop(self, card): + return True + + def is_free_card(self, card): + return False + + +class DeckStack(StackBase): + + offset_x = -0.2 + offset_y = -0.3 + + restack_counter = 0 + + def reset(self): + super(DeckStack, self).reset() + self.restack_counter = 0 + self.set_color(Qt.green) + + def stack_cards(self, cards): + for card in cards: + self.add_card(card) + card.turn_back_up() + + def can_restack(self, n_rounds=3): + return n_rounds is None or self.restack_counter < n_rounds-1 + + def update_stack_status(self, n_rounds): + if not self.can_restack(n_rounds): + self.set_color(Qt.red) + else: + # We only need this if players change the round number during a game. + self.set_color(Qt.green) + + def restack(self, fromstack): + self.restack_counter += 1 + + # We need to slice as we're adding to the list, reverse to stack back + # in the original order. + for card in fromstack.cards[::-1]: + fromstack.remove_card(card) + self.add_card(card) + card.turn_back_up() + + def set_color(self, color): + color = QColor(color) + color.setAlpha(50) + brush = QBrush(color) + self.setBrush(brush) + self.setPen(QPen(Qt.NoPen)) + + def is_valid_drop(self, card): + return False + + +class DealStack(StackBase): + + offset_x = 20 + offset_y = 0 + + spread_from = 0 + + def setup(self): + self.setPen(QPen(Qt.NoPen)) + color = QColor(Qt.black) + color.setAlpha(50) + brush = QBrush(color) + self.setBrush(brush) + + def reset(self): + super(DealStack, self).reset() + self.spread_from = 0 # Card index to start spreading cards out. + + def is_valid_drop(self, card): + return False + + def is_free_card(self, card): + return card == self.cards[-1] + + def update(self): + # Only spread the top 3 cards + offset_x = 0 + for n, card in enumerate(self.cards): + card.setPos(self.pos() + QPointF(offset_x, 0)) + card.setZValue(n) + + if n >= self.spread_from: + offset_x = offset_x + self.offset_x + + +class WorkStack(StackBase): + + offset_x = 0 + offset_y = 15 + offset_y_back = 5 + + def setup(self): + self.setPen(QPen(Qt.NoPen)) + color = QColor(Qt.black) + color.setAlpha(50) + brush = QBrush(color) + self.setBrush(brush) + + def activate(self): + # Raise z-value of this stack so children float above all other cards. + self.setZValue(1000) + + def deactivate(self): + self.setZValue(-1) + + def is_valid_drop(self, card): + if not self.cards: + return True + + if (card.color != self.cards[-1].color and + card.value == self.cards[-1].value -1): + return True + + return False + + def is_free_card(self, card): + return card.is_face_up #self.cards and card == self.cards[-1] + + def add_card(self, card, update=True): + if self.cards: + card.setParentItem(self.cards[-1]) + else: + card.setParentItem(self) + + super(WorkStack, self).add_card(card, update=update) + + def remove_card(self, card): + index = self.cards.index(card) + self.cards, cards = self.cards[:index], self.cards[index:] + + for card in cards: + # Remove card and all children, returning a list of cards removed in order. + card.setParentItem(None) + card.stack = None + + self.update() + return cards + + def remove_all_cards(self): + for card in self.cards[:]: + card.setParentItem(None) + card.stack = None + self.cards = [] + + def update(self): + self.stack.setZValue(-1) # Reset this stack the the background. + # Only spread the top 3 cards + offset_y = 0 + for n, card in enumerate(self.cards): + card.setPos(QPointF(0, offset_y)) + + if card.is_face_up: + offset_y = self.offset_y + else: + offset_y = self.offset_y_back + + +class DropStack(StackBase): + + offset_x = -0.2 + offset_y = -0.3 + + suit = None + value = 0 + + def setup(self): + self.signals = Signals() + color = QColor(Qt.blue) + color.setAlpha(50) + pen = QPen(color) + pen.setWidth(5) + self.setPen(pen) + + def reset(self): + super(DropStack, self).reset() + self.suit = None + self.value = 0 + + def is_valid_drop(self, card): + if ((self.suit is None or card.suit == self.suit) and + (card.value == self.value + 1)): + return True + + return False + + def add_card(self, card, update=True): + super(DropStack, self).add_card(card, update=update) + self.suit = card.suit + self.value = self.cards[-1].value + + if self.is_complete: + self.signals.complete.emit() + + def remove_card(self, card): + super(DropStack, self).remove_card(card) + self.value = self.cards[-1].value if self.cards else 0 + + @property + def is_complete(self): + return self.value == 13 + + +class DealTrigger(QGraphicsRectItem): + + def __init__(self, *args, **kwargs): + super(DealTrigger, self).__init__(*args, **kwargs) + self.setRect(QRectF(DEAL_RECT)) + self.setZValue(1000) + + pen = QPen(Qt.NoPen) + self.setPen(pen) + + self.signals = Signals() + + def mousePressEvent(self, e): + self.signals.clicked.emit() + + +class AnimationCover(QGraphicsRectItem): + def __init__(self, *args, **kwargs): + super(AnimationCover, self).__init__(*args, **kwargs) + self.setRect(QRectF(0, 0, *WINDOW_SIZE)) + self.setZValue(5000) + pen = QPen(Qt.NoPen) + self.setPen(pen) + + def mousePressEvent(self, e): + e.accept() + + +class MainWindow(QMainWindow): + + def __init__(self, *args, **kwargs): + super(MainWindow, self).__init__(*args, **kwargs) + + view = QGraphicsView() + self.scene = QGraphicsScene() + self.scene.setSceneRect(QRectF(0, 0, *WINDOW_SIZE)) + + felt = QBrush(QPixmap(os.path.join('images','felt.png'))) + self.scene.setBackgroundBrush(felt) + + name = QGraphicsPixmapItem() + name.setPixmap(QPixmap(os.path.join('images','ronery.png'))) + name.setPos(QPointF(170, 375)) + self.scene.addItem(name) + + view.setScene(self.scene) + + # Timer for the win animation only. + self.timer = QTimer() + self.timer.setInterval(5) + self.timer.timeout.connect(self.win_animation) + + self.animation_event_cover = AnimationCover() + self.scene.addItem(self.animation_event_cover) + + menu = self.menuBar().addMenu("&Game") + + deal_action = QAction(QIcon(os.path.join('images', 'playing-card.png')), "Deal...", self) + deal_action.triggered.connect(self.restart_game) + menu.addAction(deal_action) + + menu.addSeparator() + + deal1_action = QAction("1 card", self) + deal1_action.setCheckable(True) + deal1_action.triggered.connect(lambda: self.set_deal_n(1)) + menu.addAction(deal1_action) + + deal3_action = QAction("3 card", self) + deal3_action.setCheckable(True) + deal3_action.setChecked(True) + deal3_action.triggered.connect(lambda: self.set_deal_n(3)) + + menu.addAction(deal3_action) + + dealgroup = QActionGroup(self) + dealgroup.addAction(deal1_action) + dealgroup.addAction(deal3_action) + dealgroup.setExclusive(True) + + menu.addSeparator() + + rounds3_action = QAction("3 rounds", self) + rounds3_action.setCheckable(True) + rounds3_action.setChecked(True) + rounds3_action.triggered.connect(lambda: self.set_rounds_n(3)) + menu.addAction(rounds3_action) + + rounds5_action = QAction("5 rounds", self) + rounds5_action.setCheckable(True) + rounds5_action.triggered.connect(lambda: self.set_rounds_n(5)) + menu.addAction(rounds5_action) + + roundsu_action = QAction("Unlimited rounds", self) + roundsu_action.setCheckable(True) + roundsu_action.triggered.connect(lambda: self.set_rounds_n(None)) + menu.addAction(roundsu_action) + + roundgroup = QActionGroup(self) + roundgroup.addAction(rounds3_action) + roundgroup.addAction(rounds5_action) + roundgroup.addAction(roundsu_action) + roundgroup.setExclusive(True) + + menu.addSeparator() + + quit_action = QAction("Quit", self) + quit_action.triggered.connect(self.quit) + menu.addAction(quit_action) + + self.deck = [] + self.deal_n = 3 # Number of cards to deal each time + self.rounds_n = 3 # Number of rounds (restacks) before end. + + for suit in SUITS: + for value in range(1, 14): + card = Card(value, suit) + self.deck.append(card) + self.scene.addItem(card) + card.signals.doubleclicked.connect(lambda card=card: self.auto_drop_card(card)) + + self.setCentralWidget(view) + self.setFixedSize(*WINDOW_SIZE) + + self.deckstack = DeckStack() + self.deckstack.setPos(OFFSET_X, OFFSET_Y) + self.scene.addItem(self.deckstack) + + # Set up the working locations. + self.works = [] + for n in range(7): + stack = WorkStack() + stack.setPos(OFFSET_X + CARD_SPACING_X*n, WORK_STACK_Y) + self.scene.addItem(stack) + self.works.append(stack) + + self.drops = [] + # Set up the drop locations. + for n in range(4): + stack = DropStack() + stack.setPos(OFFSET_X + CARD_SPACING_X * (3+n), OFFSET_Y) + stack.signals.complete.connect(self.check_win_condition) + + self.scene.addItem(stack) + self.drops.append(stack) + + # Add the deal location. + self.dealstack = DealStack() + self.dealstack.setPos(OFFSET_X + CARD_SPACING_X, OFFSET_Y) + self.scene.addItem(self.dealstack) + + # Add the deal click-trigger. + dealtrigger = DealTrigger() + dealtrigger.signals.clicked.connect(self.deal) + self.scene.addItem(dealtrigger) + + self.shuffle_and_stack() + + self.setWindowTitle("Ronery") + self.show() + + def restart_game(self): + reply = QMessageBox.question(self, "Deal again", "Are you sure you want to start a new game?", + QMessageBox.Yes | QMessageBox.No) + + if reply == QMessageBox.Yes: + self.shuffle_and_stack() + + def quit(self): + self.close() + + def set_deal_n(self, n): + self.deal_n = n + + def set_rounds_n(self, n): + self.rounds_n = n + self.deckstack.update_stack_status(self.rounds_n) + + def shuffle_and_stack(self): + # Stop any ongoing animation. + self.timer.stop() + self.animation_event_cover.hide() + + # Remove cards from all stacks. + for stack in [self.deckstack, self.dealstack] + self.drops + self.works: + stack.reset() + + random.shuffle(self.deck) + + # Deal out from the top of the deck, turning over the + # final card on each line. + cards = self.deck[:] + for n, workstack in enumerate(self.works, 1): + for a in range(n): + card = cards.pop() + workstack.add_card(card) + card.turn_back_up() + if a == n-1: + card.turn_face_up() + + # Ensure removed from all other stacks here. + self.deckstack.stack_cards(cards) + + def deal(self): + if self.deckstack.cards: + self.dealstack.spread_from = len(self.dealstack.cards) + for n in range(self.deal_n): + card = self.deckstack.take_top_card() + if card: + self.dealstack.add_card(card) + card.turn_face_up() + + elif self.deckstack.can_restack(self.rounds_n): + self.deckstack.restack(self.dealstack) + self.deckstack.update_stack_status(self.rounds_n) + + def auto_drop_card(self, card): + for stack in self.drops: + if stack.is_valid_drop(card): + card.stack.remove_card(card) + stack.add_card(card) + break + + def check_win_condition(self): + complete = all(s.is_complete for s in self.drops) + if complete: + # Add click-proof cover to play area. + self.animation_event_cover.show() + # Get the stacks of cards from the drop,stacks. + self.timer.start() + + def win_animation(self): + # Start off a new card + for drop in self.drops: + card = drop.take_top_card() + if card and card.vector is None: + card.vector = QPoint(-random.randint(1, 10), 0) + break + + for card in self.deck: + if card.vector is not None: + card.setPos(card.pos() + card.vector) + card.vector += QPoint(0, 1) # Gravity + + if card.pos().y() > WINDOW_SIZE[1] - CARD_DIMENSIONS.height(): + card.vector = QPoint(card.vector.x(), -card.vector.y() ) + + if card.pos().x() < - CARD_DIMENSIONS.width(): + card.vector = None + drop = random.choice(self.drops) + drop.add_card(card) + + + + + +if __name__ == '__main__': + + app = QApplication([]) + window = MainWindow() + app.exec_() \ No newline at end of file diff --git a/unzip/unzip.py b/unzip/unzip.py index 1d2bc2e..e4255bf 100644 --- a/unzip/unzip.py +++ b/unzip/unzip.py @@ -26,6 +26,10 @@ QLabel { } """ +EXCLUDE_PATHS = [ + '__MACOSX/', +] + class WorkerSignals(QObject): ''' Defines the signals available from a running worker thread. @@ -53,8 +57,10 @@ class UnzipWorker(QRunnable): total_n = len(items) for n, item in enumerate(items): - self.zipfile.extract(item) - self.signals.progress.emit(n/total_n) + if not any(item.filename.startswith(p) for p in EXCLUDE_PATHS): + self.zipfile.extract(item) + + self.signals.progress.emit(n / total_n) except Exception as e: exctype, value = sys.exc_info()[:2]