-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcardview-example.py
More file actions
185 lines (149 loc) · 6.98 KB
/
Copy pathcardview-example.py
File metadata and controls
185 lines (149 loc) · 6.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
from PyQt5.QtCore import *
from PyQt5.QtSvg import *
class TableScene(QGraphicsScene):
""" A scene with a table cloth background """
def __init__(self):
super().__init__()
self.tile = QPixmap('cards/table.png')
self.setBackgroundBrush(QBrush(self.tile))
class CardItem(QGraphicsSvgItem):
""" A simple overloaded QGraphicsSvgItem that also stores the card position """
def __init__(self, renderer, position):
super().__init__()
self.setSharedRenderer(renderer)
self.position = position
def read_cards():
"""
Reads all the 52 cards from files.
:return: Dictionary of SVG renderers
"""
all_cards = dict() # Dictionaries let us have convenient mappings between cards and their images
for suit in 'HDSC': # You'll need to map your suits to the filenames here. You are expected to change this!
for value_file, value in zip(['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'], range(2, 15)):
file = value_file + suit
key = (value, suit) # I'm choosing this tuple to be the key for this dictionary
all_cards[key] = QSvgRenderer('cards/' + file + '.svg')
return all_cards
# A trivial card class (you should use the stuff you made in your library instead!
class MySimpleCard:
def __init__(self, value, suit):
self.value = value
self.suit = suit
def get_value(self):
return self.value
# You have made a class similar to this (hopefully):
class Hand:
def __init__(self):
# Lets use some hardcoded values for most of this to start with
self.cards = [MySimpleCard(13, 'H'), MySimpleCard(7, 'D'), MySimpleCard(13, 'S')]
def add_card(self, card):
self.cards.append(card)
import abc
class CardModel(QObject):
""" Base class that described what is expected from the CardView widget """
new_cards = pyqtSignal() #: Signal should be emited when cards change.
@abc.abstractmethod
def __iter__(self):
"""Returns an iterator of card objects"""
@abc.abstractmethod
def flipped(self):
"""Returns true of cards should be drawn face down"""
class HandModel(Hand, CardModel):
def __init__(self):
Hand.__init__(self)
CardModel.__init__(self)
# Additional state needed by the UI
self.flipped_cards = False
def __iter__(self):
return iter(self.cards)
def flip(self):
# Flips over the cards (to hide them)
self.flipped_cards = not self.flipped_cards
self.new_cards.emit() # something changed, better emit the signal!
def flipped(self):
# This model only flips all or no cards, so we don't care about the index.
# Might be different for other games though!
return self.flipped_cards
def add_card(self, card):
super().add_card(card)
self.new_cards.emit() # something changed, better emit the signal!
class CardView(QGraphicsView):
""" A View widget that represents the table area displaying a players cards. """
# We read all the card graphics as static class variables
back_card = QSvgRenderer('cards/Red_Back_2.svg')
all_cards = read_cards()
def __init__(self, card_model: CardModel, card_spacing: int = 250, padding: int = 10):
"""
Initializes the view to display the content of the given model
:param cards_model: A model that represents a set of cards. Needs to support the CardModel interface.
:param card_spacing: Spacing between the visualized cards.
:param padding: Padding of table area around the visualized cards.
"""
self.scene = TableScene()
super().__init__(self.scene)
self.card_spacing = card_spacing
self.padding = padding
self.model = card_model
# Whenever the this window should update, it should call the "__change_cards" method.
# This view can do so by listening to the matching signal:
card_model.new_cards.connect(self.__change_cards)
# Add the cards the first time around to represent the initial state.
self.__change_cards()
def __change_cards(self): # double underscore indicates that this is a private method
# Add the cards from scratch
self.scene.clear()
for i, card in enumerate(self.model):
# The ID of the card in the dictionary of images is a tuple with (value, suit), both integers
graphics_key = (card.get_value(), card.suit)
renderer = self.back_card if self.model.flipped() else self.all_cards[graphics_key]
c = CardItem(renderer, i)
# Shadow effects are cool!
shadow = QGraphicsDropShadowEffect(c)
shadow.setBlurRadius(10.)
shadow.setOffset(5, 5)
shadow.setColor(QColor(0, 0, 0, 180)) # Semi-transparent black!
c.setGraphicsEffect(shadow)
# Place the cards on the default positions
c.setPos(c.position * self.card_spacing, 0)
# We could also do cool things like marking card by making them transparent if we wanted to!
# c.setOpacity(0.5 if self.model.marked(i) else 1.0)
self.scene.addItem(c)
self.update_view()
def update_view(self):
scale = (self.viewport().height()-2*self.padding)/313
self.resetTransform()
self.scale(scale, scale)
# Put the scene bounding box
self.setSceneRect(-self.padding//scale, -self.padding//scale,
self.viewport().width()//scale, self.viewport().height()//scale)
def resizeEvent(self, painter):
# This method is called when the window is resized.
# If the widget is resize, we gotta adjust the card sizes.
# QGraphicsView automatically re-paints everything when we modify the scene.
self.update_view()
super().resizeEvent(painter)
# This is the Controller part of the GUI, handling input events that modify the Model
# def mousePressEvent(self, event):
# # We can check which item, if any, that we clicked on by fetching the scene items (neat!)
# pos = self.mapToScene(event.pos())
# item = self.scene.itemAt(pos, self.transform())
# if item is not None:
# # Report back that the user clicked on the card at given position:
# # The model can choose to do whatever it wants with this information.
# self.model.clicked_position(item.position)
# You can remove these events if you don't need them.
def mouseDoubleClickEvent(self, event):
self.model.flip() # Another possible event. Lets add it to the flip functionality for fun!
qt_app = QApplication.instance()
hand = HandModel()
card_view = CardView(hand, card_spacing=50)
button = QPushButton("Flip!")
button.clicked.connect(hand.flip)
# Creating a small demo window to work with, and put the card_view inside:
box = QVBoxLayout()
box.addWidget(card_view)
box.addWidget(button)
player_view = QGroupBox("Player 1")
player_view.setLayout(box)
player_view.show()
qt_app.exec_()