LCDRange
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
PyQt4 conversion of Qt Tutorial 13
The differences from 112_battle.pyw are:
- modified layout to allow digit display to take additional space
last modified: 2012-01-21 jeg
ref:
http://doc.trolltech.com/3.3/tutorial1-13.html
'''
from PyQt4.QtGui import (QWidget, QLabel, QLCDNumber, QSlider, QVBoxLayout)
from PyQt4.QtCore import (Qt, pyqtSlot)
class LCDRange(QWidget):
'''
A two digit QLCDNumber and QSlider widget.
'''
def __init__(self, wlabel='', parent=None):
super(LCDRange, self).__init__(parent)
self.label = QLabel(wlabel)
self.label.setAlignment(Qt.AlignCenter)
lcd = QLCDNumber(2, self);
self.slider = QSlider(Qt.Horizontal, self);
self.slider.setRange(0, 99);
self.slider.setValue(0);
self.slider.valueChanged.connect(lcd.display)
layout = QVBoxLayout()
layout.addWidget(lcd, 1) # give additional space to the digit display
layout.addWidget(self.slider)
layout.addWidget(self.label)
self.setLayout(layout)
# set the widget focus to the 'slider' object
self.setFocusProxy(self.slider)
def text(self):
return self.label.text()
def setText(self, txt):
self.label.setText(txt)
def value(self):
return self.slider.value()
@pyqtSlot(int)
def setValue(self, value):
self.slider.setValue(value)
# set the 'slider' range, if min and max values are not
# between 0 and 99, print a warning message and leave
# the slider values as they were
def setRange(self, minVal, maxVal):
if (minVal < 0 or maxVal > 99 or minVal > maxVal) :
qWarning("LCDRange.setRange({0},{1})\n"
"\tRange must be 0..99\n"
"\tand minVal must not be greater than maxVal".format(minVal, maxVal))
return
self.slider.setRange(minVal, maxVal)
def main():
import sys
from PyQt4.QtGui import (QApplication)
app = QApplication(sys.argv) # required
w = LCDRange(wlabel="ANGLE")
w.setGeometry(100, 100, 500, 355)
w.show()
sys.exit(app.exec_()) # start main event loop, exit when app closed
if __name__ == '__main__':
main()
CannonField
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
PyQt4 conversion of Qt Tutorial 13
Now handles 'game over' conditions and displays hits/misses
The differences from 112_battle.pyw are:
- added methods gameOver(), setGameOver(), restartGame()
- modified paintEvent() to handle game over conditions
- modified _moveShot() to handle canShoot()
- added attribute: gameEnded
- added signal: canShoot
NOTES:
=====
last modified: 2012-01-21 jg
ref:
http://doc.trolltech.com/3.3/tutorial1-13.html
'''
from math import (cos, sin)
from random import (seed, randrange)
from PyQt4.QtGui import (QWidget, QColor, QPainter, QSizePolicy,
QRegion, QPixmap, QFont)
from PyQt4.QtCore import (Qt, pyqtSignal, pyqtSlot, QRect, QTimer, QPoint, QTime)
class CannonField(QWidget):
cRect = None # class attribute, only one can exist
bRect = QRect(33, -4, 15, 8) # class attribute, cannon barrel definition
firstTarget = True # first time we're creating a target
def __init__(self, parent=None):
super(QWidget, self).__init__(parent)
self.setObjectName('cannonField')
self.ang = 45
self.f = 0 # force
self.target = QPoint(0, 0)
self.gameEnded = False
# add timer to handle shooting
self.autoShootTimer = QTimer(self)
self.autoShootTimer.timeout.connect(self._moveShot)
# set background colour
pal = self.palette()
pal.setColor(self.backgroundRole(), QColor(250, 250, 200))
self.setPalette(pal)
self.setAutoFillBackground(True)
self.newTarget() # create a target
# custom signals
angleChanged = pyqtSignal(int, name="angleChanged")
forceChanged = pyqtSignal(int, name="forceChanged")
missed = pyqtSignal(name="missed")
hit = pyqtSignal(name="hit")
canShoot = pyqtSignal(bool, name="canShoot")
def newTarget(self):
if CannonField.firstTarget:
# seed random number generator
CannonField.firstTarget = False
midnight = QTime(0, 0, 0)
seed(midnight.secsTo(QTime.currentTime()))
r = QRegion(self._targetRect())
self.target = QPoint(200 + randrange(0, 190),
10 + randrange(0, 255))
self.repaint(r.unite(QRegion(self._targetRect())))
def gameOver(self):
return self.gameEnded
def setGameOver(self):
if self.gameEnded:
return
if self.isShooting():
self.autoShootTimer.stop()
self.gameEnded = True
self.repaint()
def restartGame(self):
if self.isShooting():
self.autoShootTimer.stop()
self.gameEnded = False
self.target = QPoint(0, 0) # force repaint of old target
self.repaint()
self.canShoot.emit(True)
def isShooting(self):
return self.autoShootTimer.isActive()
def shoot(self):
if self.isShooting():
return
self.timerCount = 0
self.shoot_ang = self.ang
self.shoot_f = self.f
self.autoShootTimer.start(1)
self.canShoot.emit(False)
# define the screen area containing the cannon
# using a 'private' method
def _cannonRect(self):
if not CannonField.cRect: # create once
r = QRect(0, 0, 50, 50)
r.moveBottomLeft(self.rect().bottomLeft())
return r
else: # already defined so return existing rectangle
return CannonField.cRect
@pyqtSlot()
def _moveShot(self):
# moves the 'shot' every 50 milliseconds when the
# timer is fired in shoot()
r = self._shotRect()
self.timerCount += 1
shotR = self._shotRect()
if shotR.intersects(self._targetRect()):
self.autoShootTimer.stop()
self.hit.emit()
self.canShoot.emit(True)
elif (shotR.x() > self.width()) or (shotR.y() > self.height()):
self.autoShootTimer.stop()
self.missed.emit()
self.canShoot.emit(True)
else:
r = r.unite(shotR)
self.repaint(r)
def _shotRect(self):
# identifies where the 'shot' is on the screen and returns
# its bounding rectangle
gravity = 4
time = self.timerCount / 4.0
velocity = self.shoot_f
radians = self.shoot_ang * 3.14159265 / 180
velx = velocity * cos(radians)
vely = velocity * sin(radians)
x0 = (CannonField.bRect.right() + 5) * cos(radians)
y0 = (CannonField.bRect.right() + 5) * sin(radians)
x = x0 + velx * time
y = y0 + vely * time - 0.5 * gravity * time * time
r = QRect(0, 0, 6, 6)
r.moveCenter(QPoint(int(x), self.height() - 1 - int(y)))
return r
def _targetRect(self):
r = QRect(0, 0, 20, 10)
r.moveCenter(QPoint(self.target.x(), self.height() - 1 - self.target.y()))
return r
def setAngle(self, degrees):
if degrees < 5: degrees = 5
elif degrees > 70: degrees = 70
elif self.ang == degrees: return
self.ang = degrees
self.repaint(self._cannonRect())
self.angleChanged.emit(self.ang)
# handle the force of the cannon shot
def setForce(self, newton):
if newton < 0: newton = 0
elif self.f == newton: return
self.f = newton
self.forceChanged.emit(self.f)
def paintEvent(self, event):
updateR = event.rect()
p = QPainter(self)
if self.gameEnded:
p.setPen(Qt.black)
p.setFont(QFont("Courier", 48, QFont.Bold))
p.drawText(self.rect(), Qt.AlignCenter, "Game Over")
if updateR.intersects(self._cannonRect()):
self.paintCannon(p)
if self.autoShootTimer.isActive() and updateR.intersects(self._shotRect()):
self.paintShot(p)
if updateR.intersects(self._targetRect()):
self.paintTarget(p)
def paintTarget(self, p):
p.setBrush(Qt.red)
p.setPen(Qt.black)
p.drawRect(self._targetRect())
def paintShot(self, p):
p.setBrush(Qt.black)
p.setPen(Qt.NoPen)
p.drawRect(self._shotRect())
def paintCannon(self, p):
cr = self._cannonRect()
pix = QPixmap(cr.size())
pix.fill(self, cr.topLeft())
tmp = QPainter(pix)
tmp.setBrush(Qt.blue) # brush colour for filling object
tmp.setPen(Qt.NoPen) # no special edges
# set QPainter's origin (0,0) coords to the bottom-left
tmp.translate(0, pix.height() - 1)
# draw a quarter circle in the bottom left corner
tmp.drawPie(QRect(-35, -35, 70, 70), 0, 90 * 16)
# rotate counter-clockwise 'ang' degrees around the origin
# and draw the cannon's barrel
tmp.rotate(-self.ang)
tmp.drawRect(CannonField.bRect)
tmp.end()
# paint the pixmap on the screen
p.drawPixmap(cr.topLeft(), pix)
def sizePolicy(self):
return QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
def main():
import sys
from PyQt4.QtGui import (QApplication)
app = QApplication(sys.argv) # required
w = CannonField()
w.setGeometry(100, 100, 500, 355)
w.show()
w.setGameOver()
sys.exit(app.exec_()) # start main event loop, exit when app closed
if __name__ == '__main__':
main()
MyWidget
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
PyQt4 conversion of Qt Tutorial 13
In this example we start to approach a real playable game with a score.
We give MyWidget a new name (GameBoard) and add some slots.
The differences from 112_battle.pyw are:
- renamed MyWidget() to GameBoard()
- turned 'cannonField' into an attribute
- added methods: fire(), hit(), missed(), newGame()
- added widgets for shots left and hits
BEHAVIOUR:
=========
The cannon can shoot at a target; a new target is automatically created when
one has been hit.
Hits and shots left are displayed and the program keeps track of them.
The game can end, and there's a button to start a new game.
last modified: 2012-01-21 jg
ref:
http://doc.trolltech.com/3.3/tutorial1-13.html
'''
import sys
from PyQt4.QtGui import (QApplication, QWidget, QPushButton, QFont, QLabel, QLCDNumber,
QVBoxLayout, QGridLayout, QHBoxLayout)
from t113_lcdrange import (LCDRange)
from t113_cannon import (CannonField)
class GameBoard(QWidget):
def __init__(self, parent=None, name=''):
super(GameBoard, self).__init__(parent)
if name:
self.setObjectName(name)
quitBtn = QPushButton('&Quit', self)
quitBtn.setFont(QFont("Times", 18, QFont.Bold))
quitBtn.clicked.connect(QApplication.instance().quit)
angle = LCDRange(wlabel="ANGLE")
angle.setRange(5, 70)
# add LCDRange widget to handle force
force = LCDRange(wlabel="FORCE")
force.setRange(10, 50)
self.cannonField = CannonField(self)
angle.slider.valueChanged.connect(self.cannonField.setAngle)
self.cannonField.angleChanged.connect(angle.setValue)
# add event handling for 'force'
force.slider.valueChanged.connect(self.cannonField.setForce)
self.cannonField.forceChanged.connect(force.setValue)
# handle target hits/misses
self.cannonField.hit.connect(self.hit)
self.cannonField.missed.connect(self.missed)
# buttons
shootBtn = QPushButton('&Shoot', self)
shootBtn.setFont(QFont("Times", 18, QFont.Bold))
shootBtn.clicked.connect(self.fire)
self.cannonField.canShoot.connect(self.setEnabled)
restartBtn = QPushButton("&New Game", self)
restartBtn.setFont(QFont("Times", 18, QFont.Bold))
restartBtn.clicked.connect(self.newGame)
self.hits = QLCDNumber(2, self)
self.shotsLeft = QLCDNumber(2, self)
self.hitsL = QLabel("HITS", self)
self.shotsLeftL = QLabel("SHOTS LEFT", self)
# layout widgets
grid = QGridLayout()
grid.addWidget(quitBtn, 0, 0)
grid.addWidget(self.cannonField, 1, 1)
grid.setColumnStretch(1, 10)
# add the angle and force widgets
leftBox = QVBoxLayout()
grid.addLayout(leftBox, 1, 0)
leftBox.addWidget(angle)
leftBox.addWidget(force)
# add the 'shoot' button
topBox = QHBoxLayout()
grid.addLayout(topBox, 0, 1)
topBox.addWidget(shootBtn)
topBox.addWidget(self.hits)
topBox.addWidget(self.hitsL)
topBox.addWidget(self.shotsLeft)
topBox.addWidget(self.shotsLeftL)
topBox.addStretch(1)
topBox.addWidget(restartBtn)
self.setLayout(grid)
angle.setValue(60)
force.setValue(25)
angle.setFocus() # give the LCDRange object keyboard focus
self.newGame()
def fire(self):
if self.cannonField.gameOver() or self.cannonField.isShooting():
return
self.shotsLeft.display(self.shotsLeft.intValue() - 1)
self.cannonField.shoot()
def hit(self):
self.hits.display(self.hits.intValue() + 1)
if self.shotsLeft.intValue() == 0:
self.cannonField.setGameOver()
else:
self.cannonField.newTarget()
def missed(self):
if self.shotsLeft.intValue() == 0:
self.cannonField.setGameOver()
def newGame(self):
self.shotsLeft.display(15)
self.hits.display(0)
self.cannonField.restartGame()
self.cannonField.newTarget()
def main():
app = QApplication(sys.argv) # required
w = GameBoard()
w.setGeometry(100, 100, 500, 355)
w.show()
sys.exit(app.exec_()) # start main event loop, exit when app closed
if __name__ == '__main__':
main()