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