1
2#############################################################################
3##
4## Copyright (C) 2016 The Qt Company Ltd.
5## Contact: http://www.qt.io/licensing/
6##
7## This file is part of the Qt for Python examples of the Qt Toolkit.
8##
9## $QT_BEGIN_LICENSE:BSD$
10## You may use this file under the terms of the BSD license as follows:
11##
12## "Redistribution and use in source and binary forms, with or without
13## modification, are permitted provided that the following conditions are
14## met:
15##   * Redistributions of source code must retain the above copyright
16##     notice, this list of conditions and the following disclaimer.
17##   * Redistributions in binary form must reproduce the above copyright
18##     notice, this list of conditions and the following disclaimer in
19##     the documentation and/or other materials provided with the
20##     distribution.
21##   * Neither the name of The Qt Company Ltd nor the names of its
22##     contributors may be used to endorse or promote products derived
23##     from this software without specific prior written permission.
24##
25##
26## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
37##
38## $QT_END_LICENSE$
39##
40#############################################################################
41
42# PySide2 tutorial 13
43
44
45import sys
46import math
47import random
48from PySide2 import QtCore, QtGui, QtWidgets
49
50
51class LCDRange(QtWidgets.QWidget):
52    valueChanged = QtCore.Signal(int)
53    def __init__(self, text=None, parent=None):
54        if isinstance(text, QtWidgets.QWidget):
55            parent = text
56            text = None
57
58        QtWidgets.QWidget.__init__(self, parent)
59
60        self.init()
61
62        if text:
63            self.setText(text)
64
65    def init(self):
66        lcd = QtWidgets.QLCDNumber(2)
67        self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
68        self.slider.setRange(0, 99)
69        self.slider.setValue(0)
70        self.label = QtWidgets.QLabel()
71        self.label.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignTop)
72        self.label.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
73
74        self.connect(self.slider, QtCore.SIGNAL("valueChanged(int)"),
75                     lcd, QtCore.SLOT("display(int)"))
76        self.connect(self.slider, QtCore.SIGNAL("valueChanged(int)"),
77                     self, QtCore.SIGNAL("valueChanged(int)"))
78
79        layout = QtWidgets.QVBoxLayout()
80        layout.addWidget(lcd)
81        layout.addWidget(self.slider)
82        layout.addWidget(self.label)
83        self.setLayout(layout)
84
85        self.setFocusProxy(self.slider)
86
87    def value(self):
88        return self.slider.value()
89
90    @QtCore.Slot(int)
91    def setValue(self, value):
92        self.slider.setValue(value)
93
94    def text(self):
95        return self.label.text()
96
97    def setRange(self, minValue, maxValue):
98        if minValue < 0 or maxValue > 99 or minValue > maxValue:
99            QtCore.qWarning("LCDRange::setRange(%d, %d)\n"
100                    "\tRange must be 0..99\n"
101                    "\tand minValue must not be greater than maxValue" % (minValue, maxValue))
102            return
103
104        self.slider.setRange(minValue, maxValue)
105
106    def setText(self, text):
107        self.label.setText(text)
108
109
110class CannonField(QtWidgets.QWidget):
111    angleChanged = QtCore.Signal(int)
112    forceChanged = QtCore.Signal(int)
113    hit = QtCore.Signal()
114    missed = QtCore.Signal()
115    canShoot = QtCore.Signal(bool)
116    def __init__(self, parent=None):
117        QtWidgets.QWidget.__init__(self, parent)
118
119        self.currentAngle = 45
120        self.currentForce = 0
121        self.timerCount = 0
122        self.autoShootTimer = QtCore.QTimer(self)
123        self.connect(self.autoShootTimer, QtCore.SIGNAL("timeout()"),
124                     self.moveShot)
125        self.shootAngle = 0
126        self.shootForce = 0
127        self.target = QtCore.QPoint(0, 0)
128        self.gameEnded = False
129        self.setPalette(QtGui.QPalette(QtGui.QColor(250, 250, 200)))
130        self.setAutoFillBackground(True)
131        self.newTarget()
132
133    def angle(self):
134        return self.currentAngle
135
136    @QtCore.Slot(int)
137    def setAngle(self, angle):
138        if angle < 5:
139            angle = 5
140        if angle > 70:
141            angle = 70
142        if self.currentAngle == angle:
143            return
144        self.currentAngle = angle
145        self.update()
146        self.emit(QtCore.SIGNAL("angleChanged(int)"), self.currentAngle)
147
148    def force(self):
149        return self.currentForce
150
151    @QtCore.Slot(int)
152    def setForce(self, force):
153        if force < 0:
154            force = 0
155        if self.currentForce == force:
156            return
157        self.currentForce = force
158        self.emit(QtCore.SIGNAL("forceChanged(int)"), self.currentForce)
159
160    @QtCore.Slot()
161    def shoot(self):
162        if self.isShooting():
163            return
164        self.timerCount = 0
165        self.shootAngle = self.currentAngle
166        self.shootForce = self.currentForce
167        self.autoShootTimer.start(5)
168        self.emit(QtCore.SIGNAL("canShoot(bool)"), False)
169
170    firstTime = True
171
172    def newTarget(self):
173        if CannonField.firstTime:
174            CannonField.firstTime = False
175            midnight = QtCore.QTime(0, 0, 0)
176            random.seed(midnight.secsTo(QtCore.QTime.currentTime()))
177
178        self.target = QtCore.QPoint(200 + random.randint(0, 190 - 1), 10 + random.randint(0, 255 - 1))
179        self.update()
180
181    def setGameOver(self):
182        if self.gameEnded:
183            return
184        if self.isShooting():
185            self.autoShootTimer.stop()
186        self.gameEnded = True
187        self.update()
188
189    def restartGame(self):
190        if self.isShooting():
191            self.autoShootTimer.stop()
192        self.gameEnded = False
193        self.update()
194        self.emit(QtCore.SIGNAL("canShoot(bool)"), True)
195
196    @QtCore.Slot()
197    def moveShot(self):
198        region = QtGui.QRegion(self.shotRect())
199        self.timerCount += 1
200
201        shotR = self.shotRect()
202
203        if shotR.intersects(self.targetRect()):
204            self.autoShootTimer.stop()
205            self.emit(QtCore.SIGNAL("hit()"))
206            self.emit(QtCore.SIGNAL("canShoot(bool)"), True)
207        elif shotR.x() > self.width() or shotR.y() > self.height():
208            self.autoShootTimer.stop()
209            self.emit(QtCore.SIGNAL("missed()"))
210            self.emit(QtCore.SIGNAL("canShoot(bool)"), True)
211        else:
212            region = region.united(QtGui.QRegion(shotR))
213
214        self.update(region)
215
216    def paintEvent(self, event):
217        painter = QtGui.QPainter(self)
218
219        if self.gameEnded:
220            painter.setPen(QtCore.Qt.black)
221            painter.setFont(QtGui.QFont("Courier", 48, QtGui.QFont.Bold))
222            painter.drawText(self.rect(), QtCore.Qt.AlignCenter, "Game Over")
223
224        self.paintCannon(painter)
225        if self.isShooting():
226            self.paintShot(painter)
227        if not self.gameEnded:
228            self.paintTarget(painter)
229
230    def paintShot(self, painter):
231        painter.setPen(QtCore.Qt.NoPen)
232        painter.setBrush(QtCore.Qt.black)
233        painter.drawRect(self.shotRect())
234
235    def paintTarget(self, painter):
236        painter.setPen(QtCore.Qt.black)
237        painter.setBrush(QtCore.Qt.red)
238        painter.drawRect(self.targetRect())
239
240    barrelRect = QtCore.QRect(33, -4, 15, 8)
241
242    def paintCannon(self, painter):
243        painter.setPen(QtCore.Qt.NoPen)
244        painter.setBrush(QtCore.Qt.blue)
245
246        painter.save()
247        painter.translate(0, self.height())
248        painter.drawPie(QtCore.QRect(-35, -35, 70, 70), 0, 90 * 16)
249        painter.rotate(-self.currentAngle)
250        painter.drawRect(CannonField.barrelRect)
251        painter.restore()
252
253    def cannonRect(self):
254        result = QtCore.QRect(0, 0, 50, 50)
255        result.moveBottomLeft(self.rect().bottomLect())
256        return result
257
258    def shotRect(self):
259        gravity = 4.0
260
261        time = self.timerCount / 40.0
262        velocity = self.shootForce
263        radians = self.shootAngle * 3.14159265 / 180
264
265        velx = velocity * math.cos(radians)
266        vely = velocity * math.sin(radians)
267        x0 = (CannonField.barrelRect.right() + 5) * math.cos(radians)
268        y0 = (CannonField.barrelRect.right() + 5) * math.sin(radians)
269        x = x0 + velx * time
270        y = y0 + vely * time - 0.5 * gravity * time * time
271
272        result = QtCore.QRect(0, 0, 6, 6)
273        result.moveCenter(QtCore.QPoint(round(x), self.height() - 1 - round(y)))
274        return result
275
276    def targetRect(self):
277        result = QtCore.QRect(0, 0, 20, 10)
278        result.moveCenter(QtCore.QPoint(self.target.x(), self.height() - 1 - self.target.y()))
279        return result
280
281    def gameOver(self):
282        return self.gameEnded
283
284    def isShooting(self):
285        return self.autoShootTimer.isActive()
286
287
288class GameBoard(QtWidgets.QWidget):
289    def __init__(self, parent=None):
290        QtWidgets.QWidget.__init__(self, parent)
291
292        quit = QtWidgets.QPushButton("&Quit")
293        quit.setFont(QtGui.QFont("Times", 18, QtGui.QFont.Bold))
294
295        self.connect(quit, QtCore.SIGNAL("clicked()"),
296                     qApp, QtCore.SLOT("quit()"))
297
298        angle = LCDRange("ANGLE")
299        angle.setRange(5, 70)
300
301        force = LCDRange("FORCE")
302        force.setRange(10, 50)
303
304        self.cannonField = CannonField()
305
306        self.connect(angle, QtCore.SIGNAL("valueChanged(int)"),
307                     self.cannonField.setAngle)
308        self.connect(self.cannonField, QtCore.SIGNAL("angleChanged(int)"),
309                     angle.setValue)
310
311        self.connect(force, QtCore.SIGNAL("valueChanged(int)"),
312                     self.cannonField.setForce)
313        self.connect(self.cannonField, QtCore.SIGNAL("forceChanged(int)"),
314                     force.setValue)
315
316        self.connect(self.cannonField, QtCore.SIGNAL("hit()"), self.hit)
317        self.connect(self.cannonField, QtCore.SIGNAL("missed()"), self.missed)
318
319        shoot = QtWidgets.QPushButton("&Shoot")
320        shoot.setFont(QtGui.QFont("Times", 18, QtGui.QFont.Bold))
321
322        self.connect(shoot, QtCore.SIGNAL("clicked()"), self.fire)
323        self.connect(self.cannonField, QtCore.SIGNAL("canShoot(bool)"),
324                     shoot, QtCore.SLOT("setEnabled(bool)"))
325
326        restart = QtWidgets.QPushButton("&New Game")
327        restart.setFont(QtGui.QFont("Times", 18, QtGui.QFont.Bold))
328
329        self.connect(restart, QtCore.SIGNAL("clicked()"), self.newGame)
330
331        self.hits = QtWidgets.QLCDNumber(2)
332        self.shotsLeft = QtWidgets.QLCDNumber(2)
333        hitsLabel = QtWidgets.QLabel("HITS")
334        shotsLeftLabel = QtWidgets.QLabel("SHOTS LEFT")
335
336        topLayout = QtWidgets.QHBoxLayout()
337        topLayout.addWidget(shoot)
338        topLayout.addWidget(self.hits)
339        topLayout.addWidget(hitsLabel)
340        topLayout.addWidget(self.shotsLeft)
341        topLayout.addWidget(shotsLeftLabel)
342        topLayout.addStretch(1)
343        topLayout.addWidget(restart)
344
345        leftLayout = QtWidgets.QVBoxLayout()
346        leftLayout.addWidget(angle)
347        leftLayout.addWidget(force)
348
349        gridLayout = QtWidgets.QGridLayout()
350        gridLayout.addWidget(quit, 0, 0)
351        gridLayout.addLayout(topLayout, 0, 1)
352        gridLayout.addLayout(leftLayout, 1, 0)
353        gridLayout.addWidget(self.cannonField, 1, 1, 2, 1)
354        gridLayout.setColumnStretch(1, 10)
355        self.setLayout(gridLayout)
356
357        angle.setValue(60)
358        force.setValue(25)
359        angle.setFocus()
360
361        self.newGame()
362
363    @QtCore.Slot()
364    def fire(self):
365        if self.cannonField.gameOver() or self.cannonField.isShooting():
366            return
367        self.shotsLeft.display(self.shotsLeft.intValue() - 1)
368        self.cannonField.shoot()
369
370    @QtCore.Slot()
371    def hit(self):
372        self.hits.display(self.hits.intValue() + 1)
373        if self.shotsLeft.intValue() == 0:
374            self.cannonField.setGameOver()
375        else:
376            self.cannonField.newTarget()
377
378    @QtCore.Slot()
379    def missed(self):
380        if self.shotsLeft.intValue() == 0:
381            self.cannonField.setGameOver()
382
383    @QtCore.Slot()
384    def newGame(self):
385        self.shotsLeft.display(15)
386        self.hits.display(0)
387        self.cannonField.restartGame()
388        self.cannonField.newTarget()
389
390
391app = QtWidgets.QApplication(sys.argv)
392board = GameBoard()
393board.setGeometry(100, 100, 500, 355)
394board.show()
395sys.exit(app.exec_())
396