snippetpythonMinor
Example of PyQt5 simple turn-based game code
Viewed 0 times
turnsimpleexamplepyqt5gamebasedcode
Problem
I've made my first turn-based game in PyQt5. I suppose that its idea can also be used by other novice GUI programmers.
There is a 5 by 5 squared unpainted field and 4 players. Each player starts at corner square and has his own colour. Player can move to adjacent square and fill it with his colour if it isn’t occupied by other player or isn’t filled in player's colour.
If player has nowhere to move he is randomly teleported to non-filled square. Game ends when all squares are filled. Player who has most squares filled with his colour wins.
Also I would like to hear any suggestions about code improvement. The problem parts are probably self.turn() and self.turn_loop() in MyApp class since they have a bit complicated "if/elif/else" logic.
Main module (PainterField.py):
```
#!/usr/bin/env python
'''
Game name: PainterFiled
Author: Igor Vasylchenko
As this module and its sub-modules use PyQt5 they are distributed and
may be used under the terms of the GNU General Public License version 3.0.
See http://www.gnu.org/copyleft/gpl.html
'''
import random
import sys
import traceback
from PyQt5.QtCore import (QRectF, QTimer, Qt)
from PyQt5.QtGui import (QBrush, QColor, QImage)
from PyQt5.QtWidgets import (QApplication, QGraphicsItem, QGraphicsScene,
QGraphicsView, QMainWindow, QPushButton)
# Custom classes
from FieldClasses import (PlayerQGraphics, SquareQGrapics)
from FieldFunctions import (create_obstacles, create_squares,
create_players, print_main, print_rules)
# Gui generated by Qt5
from gui import Ui_Field as Ui_MainWindow
'''Exceptions hadling block. Needed to track errors during
operation.'''
sys._excepthook = sys.excepthook
def exception_hook(exctype, value, traceback):
sys._excepthook(exctype, value, traceback)
sys.exit(1)
sys.excepthook = exception_hook
class MyApp(QMainWindow, Ui_MainWindow):
def __init__(self):
QMainWindow.__init__(self)
Ui_MainWindow.__init__(se
There is a 5 by 5 squared unpainted field and 4 players. Each player starts at corner square and has his own colour. Player can move to adjacent square and fill it with his colour if it isn’t occupied by other player or isn’t filled in player's colour.
If player has nowhere to move he is randomly teleported to non-filled square. Game ends when all squares are filled. Player who has most squares filled with his colour wins.
Also I would like to hear any suggestions about code improvement. The problem parts are probably self.turn() and self.turn_loop() in MyApp class since they have a bit complicated "if/elif/else" logic.
Main module (PainterField.py):
```
#!/usr/bin/env python
'''
Game name: PainterFiled
Author: Igor Vasylchenko
As this module and its sub-modules use PyQt5 they are distributed and
may be used under the terms of the GNU General Public License version 3.0.
See http://www.gnu.org/copyleft/gpl.html
'''
import random
import sys
import traceback
from PyQt5.QtCore import (QRectF, QTimer, Qt)
from PyQt5.QtGui import (QBrush, QColor, QImage)
from PyQt5.QtWidgets import (QApplication, QGraphicsItem, QGraphicsScene,
QGraphicsView, QMainWindow, QPushButton)
# Custom classes
from FieldClasses import (PlayerQGraphics, SquareQGrapics)
from FieldFunctions import (create_obstacles, create_squares,
create_players, print_main, print_rules)
# Gui generated by Qt5
from gui import Ui_Field as Ui_MainWindow
'''Exceptions hadling block. Needed to track errors during
operation.'''
sys._excepthook = sys.excepthook
def exception_hook(exctype, value, traceback):
sys._excepthook(exctype, value, traceback)
sys.exit(1)
sys.excepthook = exception_hook
class MyApp(QMainWindow, Ui_MainWindow):
def __init__(self):
QMainWindow.__init__(self)
Ui_MainWindow.__init__(se
Solution
List of major edits in PainterField module:
1) Removed useless import from beginning (main module doesn’t directly use custom classes).
2) Iteration over dictionary elements:
Had a lot of (see
Fixed with:
3) Used dictionary mapping instead of multiple elif statements in
4)
5)
-
Instead of
elif ID == 0:
key = self.key
...
xy = self.turn(0)
used:
-
-
"Computer ('ai') turn" section now is:
if xy is not None:
ai_players = sorted(self.players.keys())
ai_players.remove('You')
for ai in ai_players:
self.turn(self.players[ai])
So computer players now make turn in strict order.
List of major edits in FieldClasses module:
1) Bounding rectangles of
2)
3) In
used
which is self explanatory inside code)
4) Yet again beautiful dictionary mapping in
5) And the second part of
Brief edits in FieldFunctions:
1) Yet AGAIN dictionary mapping was used in
2) Slightly different version of
Conclusion:
I'm still opened to suggestions though it seems that code is quite optimized now. So probably next post will be about snake game clone, which I wrote 2 times faster because used this game as a template)))
1) Removed useless import from beginning (main module doesn’t directly use custom classes).
2) Iteration over dictionary elements:
Had a lot of (see
def draw_field()):for xy in squares.keys():
square = dict[xy]
do_smthFixed with:
for square in dict.squares():
do_smth3) Used dictionary mapping instead of multiple elif statements in
def keyPressEvent():keymap = {Qt.Key_Enter: self.start,
Qt.Key_Return: self.start,
Qt.Key_Escape: self.close,
Qt.Key_R: self.reset
}
# Player movement
if key in {Qt.Key_Left, Qt.Key_Right,
Qt.Key_Up, Qt.Key_Down}:
self.key = key
# Starting new game, exiting or resetting current field
elif key in keymap:
keymap[key]()4)
def results(), which creates text was edited a bit and moved to FieldFunctions.py5)
def turn() and def turn_loop() readability was improved:- Impoved iteration over dictionary elements as in p. 2.
-
Instead of
elif ID == 0:
key = self.key
...
xy = self.turn(0)
used:
elif player == self.players['You']:
key = self.key
...
xy = self.turn(self.players['You'])self.players now looks like {'You': PlayerQGraphics(), 'Player 1': PlayerQGraphics(), ...}-
random.choice() instead of random.sample(, 1)-
"Computer ('ai') turn" section now is:
if xy is not None:
ai_players = sorted(self.players.keys())
ai_players.remove('You')
for ai in ai_players:
self.turn(self.players[ai])
So computer players now make turn in strict order.
List of major edits in FieldClasses module:
1) Bounding rectangles of
QGraphicsItem subclasses now fit images and are change where needed by .prepareGeometryChange() method instead of .update()def boundingRect(self):
x, y = self.xy
return QRectF(x*30, y*30, 28, 28)2)
obstacles is now set as it is called multiple times inside nested function.3) In
def findDirections() instead of:for n in range(1, 5):
xy = self.prepareGoto(n, exceptColours, obstacles, squares)used
for key in {Qt.Key_Left, Qt.Key_Right,
Qt.Key_Up, Qt.Key_Down}:
xy = self.prepareGoto(exceptColours, key, obstacles, squares)which is self explanatory inside code)
4) Yet again beautiful dictionary mapping in
def prepareGoto():x, y = self.xy
moves_map = {Qt.Key_Up: (x, y-1),
Qt.Key_Down: (x, y+1),
Qt.Key_Left: (x-1, y),
Qt.Key_Right: (x+1, y)
}
xy = moves_map[key]5) And the second part of
def prepareGoto() also looks more readable since try: except statements handle exactly one operation:# Prevent movement outside the field bounds
try:
target_square = squares[xy]
except KeyError:
return None
# Prevent movement in other players. Can be replaced with
# or statement, but that looks ugly and might be a bit slower that elif.
if xy in obstacles:
xy = None
elif target_square.colour in exceptColours:
xy = None
return xyBrief edits in FieldFunctions:
1) Yet AGAIN dictionary mapping was used in
def create_players() to iterate over:colours_map = {'You': ((0,0), 'green'), 'Player 1': ((8,0), 'red'),
'Player 2': ((0,8), 'blue'), 'Player 3': ((8,8), 'black')}
for name, parameters in colours_map.items():
xy, colour = parameters
players[name] = PlayerQGraphics(xy, colour, icon='graphics/ai.png')
else: # else here looks a bit better that just one line of code after for loop
players['You'].icon = 'graphics/player.png'
return players2) Slightly different version of
def print_results() was moved from main module to FieldFunctions.pyConclusion:
I'm still opened to suggestions though it seems that code is quite optimized now. So probably next post will be about snake game clone, which I wrote 2 times faster because used this game as a template)))
Code Snippets
for xy in squares.keys():
square = dict[xy]
do_smthfor square in dict.squares():
do_smthkeymap = {Qt.Key_Enter: self.start,
Qt.Key_Return: self.start,
Qt.Key_Escape: self.close,
Qt.Key_R: self.reset
}
# Player movement
if key in {Qt.Key_Left, Qt.Key_Right,
Qt.Key_Up, Qt.Key_Down}:
self.key = key
# Starting new game, exiting or resetting current field
elif key in keymap:
keymap[key]()elif player == self.players['You']:
key = self.key
...
xy = self.turn(self.players['You'])def boundingRect(self):
x, y = self.xy
return QRectF(x*30, y*30, 28, 28)Context
StackExchange Code Review Q#142795, answer score: 3
Revisions (0)
No revisions yet.