PyQt5–蛇形游戏
在这篇文章中,我们将看到如何使用PyQt5设计一个简单的蛇形游戏。
蛇形 游戏是一个视频游戏概念的通用名称,玩家操纵一条长度不断增长的线,线本身就是一个主要障碍。这个概念起源于1976年的街机游戏《Blockade》,由于Snake易于实现,因此在许多平台上出现了数以百计的版本(其中一些版本的标题中有蛇或虫的字样)。
实施步骤:
- 创建一个主窗口,为其添加状态栏,以显示分数,并创建一个board类的对象,将其作为中心部件。
- 创建一个名为board的类,它继承了QFrame。
- 在board类中创建一个定时器对象,在一定时间后调用定时器方法。
- 在定时器方法中调用蛇游戏的其他动作,如移动、吃的食物和蛇是否自杀。
- 创建一个按键事件方法,检查方向键是否被按下,并根据它来改变蛇的方向。
- 创建一个绘画事件方法,画出蛇和食物。
- 创建移动方法,根据方向移动蛇。
- 创建食物被吃掉的方法,检查蛇的当前位置,如果食物被吃掉,则删除当前的食物,增加蛇的长度,并在随机位置投放一个新的食物。
- 创建检查自杀的方法,检查蛇头的位置是否与身体的位置相似,如果匹配则停止计时器并显示信息。
下面是实现方法。
# importing libraries
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
import random
import sys
# creating game window
class Window(QMainWindow):
def __init__(self):
super(Window, self).__init__()
# creating a board object
self.board = Board(self)
# creating a status bar to show result
self.statusbar = self.statusBar()
# adding border to the status bar
self.statusbar.setStyleSheet(& quot
border: 2px solid black
& quot
)
# calling showMessage method when signal received by board
self.board.msg2statusbar[str].connect(self.statusbar.showMessage)
# adding board as a central widget
self.setCentralWidget(self.board)
# setting title to the window
self.setWindowTitle('Snake game')
# setting geometry to the window
self.setGeometry(100, 100, 600, 400)
# starting the board object
self.board.start()
# showing the main window
self.show()
# creating a board class
# that inherits QFrame
class Board(QFrame):
# creating signal object
msg2statusbar = pyqtSignal(str)
# speed of the snake
# timer countdown time
SPEED = 80
# block width and height
WIDTHINBLOCKS = 60
HEIGHTINBLOCKS = 40
# constructor
def __init__(self, parent):
super(Board, self).__init__(parent)
# creating a timer
self.timer = QBasicTimer()
# snake
self.snake = [[5, 10], [5, 11]]
# current head x head
self.current_x_head = self.snake[0][0]
# current y head
self.current_y_head = self.snake[0][1]
# food list
self.food = []
# growing is false
self.grow_snake = False
# board list
self.board = []
# direction
self.direction = 1
# called drop food method
self.drop_food()
# setting focus
self.setFocusPolicy(Qt.StrongFocus)
# square width method
def square_width(self):
return self.contentsRect().width() / Board.WIDTHINBLOCKS
# square height
def square_height(self):
return self.contentsRect().height() / Board.HEIGHTINBLOCKS
# start method
def start(self):
# msg for status bar
# score = current len - 2
self.msg2statusbar.emit(str(len(self.snake) - 2))
# starting timer
self.timer.start(Board.SPEED, self)
# paint event
def paintEvent(self, event):
# creating painter object
painter = QPainter(self)
# getting rectangle
rect = self.contentsRect()
# board top
boardtop = rect.bottom() - Board.HEIGHTINBLOCKS * self.square_height()
# drawing snake
for pos in self.snake:
self.draw_square(painter, rect.left() + pos[0] * self.square_width(),
boardtop + pos[1] * self.square_height())
# drawing food
for pos in self.food:
self.draw_square(painter, rect.left() + pos[0] * self.square_width(),
boardtop + pos[1] * self.square_height())
# drawing square
def draw_square(self, painter, x, y):
# color
color = QColor(0x228B22)
# painting rectangle
painter.fillRect(x + 1, y + 1, self.square_width() - 2,
self.square_height() - 2, color)
# key press event
def keyPressEvent(self, event):
# getting key pressed
key = event.key()
# if left key pressed
if key == Qt.Key_Left:
# if direction is not right
if self.direction != 2:
# set direction to left
self.direction = 1
# if right key is pressed
elif key == Qt.Key_Right:
# if direction is not left
if self.direction != 1:
# set direction to right
self.direction = 2
# if down key is pressed
elif key == Qt.Key_Down:
# if direction is not up
if self.direction != 4:
# set direction to down
self.direction = 3
# if up key is pressed
elif key == Qt.Key_Up:
# if direction is not down
if self.direction != 3:
# set direction to up
self.direction = 4
# method to move the snake
def move_snake(self):
# if direction is left change its position
if self.direction == 1:
self.current_x_head, self.current_y_head = self.current_x_head - 1, self.current_y_head
# if it goes beyond left wall
if self.current_x_head & lt
0:
self.current_x_head = Board.WIDTHINBLOCKS - 1
# if direction is right change its position
if self.direction == 2:
self.current_x_head, self.current_y_head = self.current_x_head + 1, self.current_y_head
# if it goes beyond right wall
if self.current_x_head == Board.WIDTHINBLOCKS:
self.current_x_head = 0
# if direction is down change its position
if self.direction == 3:
self.current_x_head, self.current_y_head = self.current_x_head, self.current_y_head + 1
# if it goes beyond down wall
if self.current_y_head == Board.HEIGHTINBLOCKS:
self.current_y_head = 0
# if direction is up change its position
if self.direction == 4:
self.current_x_head, self.current_y_head = self.current_x_head, self.current_y_head - 1
# if it goes beyond up wall
if self.current_y_head & lt
0:
self.current_y_head = Board.HEIGHTINBLOCKS
# changing head position
head = [self.current_x_head, self.current_y_head]
# inset head in snake list
self.snake.insert(0, head)
# if snake grow is False
if not self.grow_snake:
# pop the last element
self.snake.pop()
else:
# show msg in status bar
self.msg2statusbar.emit(str(len(self.snake)-2))
# make grow_snake to false
self.grow_snake = False
# time event method
def timerEvent(self, event):
# checking timer id
if event.timerId() == self.timer.timerId():
# call move snake method
self.move_snake()
# call food collision method
self.is_food_collision()
# call is suicide method
self.is_suicide()
# update the window
self.update()
# method to check if snake collides itself
def is_suicide(self):
# traversing the snake
for i in range(1, len(self.snake)):
# if collision found
if self.snake[i] == self.snake[0]:
# show game ended msg in status bar
self.msg2statusbar.emit(str(& quot
Game Ended & quot
))
# making background color black
self.setStyleSheet(& quot
background-color: black
& quot
)
# stopping the timer
self.timer.stop()
# updating the window
self.update()
# method to check if the food cis collied
def is_food_collision(self):
# traversing the position of the food
for pos in self.food:
# if food position is similar of snake position
if pos == self.snake[0]:
# remove the food
self.food.remove(pos)
# call drop food method
self.drop_food()
# grow the snake
self.grow_snake = True
# method to drop food on screen
def drop_food(self):
# creating random co-ordinates
x = random.randint(3, 58)
y = random.randint(3, 38)
# traversing if snake position is not equal to the
# food position so that food do not drop on snake
for pos in self.snake:
# if position matches
if pos == [x, y]:
# call drop food method again
self.drop_food()
# append food location
self.food.append([x, y])
# main method
if __name__ == '__main__':
app = QApplication([])
window = Window()
sys.exit(app.exec_())
代码解释 。
- 代码从创建一个新的类–Window开始。
- 这个类将是我们应用程序的主窗口。
-
- 接下来,调用 init() 方法。
-
- 在这个方法中,我们设置窗口的标题并配置其几何形状。
- 5.我们还调用UiComponents(),这是一个特殊的方法,它将在窗口中显示我们所有的小部件。
- 现在让我们来看看当我们运行这段代码时会发生什么。
- 首先,我们创建一个Window的实例,并将其标题设为 “Python”。
- 然后我们配置窗口的几何形状,将其大小设置为100×100像素,在屏幕上的位置设置为320×400像素(见图1-1)。
- 图 1-1: 配置了几何图形的 Python 窗口 接下来,我们调用 UiComponents() 。
- 这个方法将在窗口中显示我们所有的小部件(见图1-2)。
- 窗口对象 图 1-2: 显示所有部件的 Python 窗口 在这个例子中,我们的窗口中只有两个部件 – 文本框和按钮。
- 12.然而,你可以添加尽可能多的部件
- 代码创建了一个新的窗口,并将其标题设为 “Python”。
- 然后它将窗口的几何图形设置为(100, 100, 320, 400)。
- 最后,它在窗口对象上调用UiComponents()方法。
- 16.UiComponents()方法负责显示窗口中所有的小部件。
- 该代码首先通过调用show()来显示所有的部件。
- 在显示完所有的部件后,代码调用一个叫做updateWidget()的方法。
- 这个方法负责更新窗口中的每个部件。
- 代码开始创建一个QLabel对象,并将其几何形状设置为宽20、10、280像素,高60像素。
- 21.然后将标签的字体设置为Times New Roman,并启用粗体和斜体设置,同时启用下划线。
- 最后,头部的对齐方式被设置为Qt.AlignCenter。
- 接下来,代码创建了一个选择变量,并将其设置为0。
- 选择变量将存储用户在石头(0)和纸(1)之间的选择。
- 下一行代码创建了一个头部标签对象,并将其几何形状设置为20,10,280像素宽,60像素高。
- 然后,标签的字体被设置为Times New Roman,并启用粗体和斜体设置,同时禁用下划线。
- 最后,头部的对齐方式被设置为Qt.AlignLeftJustified。
- 接下来,我们使用QPushButton对象创建两个按钮。
- 一个按钮将用于选择石头或纸,而另一个将用于取消游戏。
- 我们首先创建一个名为 “rock “的QPushButton对象。
- 这个按钮将用于选择玩家是否要用石头玩。
- 该代码创建了一个QLabel对象,并将其几何形状设置为20×10像素,左上角为(280,60)像素。
- 然后,字体被设置为Times New Roman字体,粗体、斜体和下划线属性被设置为True。
- 最后,头部的对齐方式被设置为Qt.AlignCenter。
- 代码开始创建一个新的QGraphicsItem,它是用户界面的头部。
- 头部对象有一个GraphicsEffect属性,可以被设置为几种颜色效果之一。
- 在这个例子中,我们使用QGraphicsColorizeEffect类来改变头部对象的颜色为深青色。
- 接下来,我们创建一个新的QLabel对象,并将其几何尺寸设置为150 x 110像素,宽度为30像素,高度为50像素。
- 我们还将其字体属性设置为下划线(false)和斜体(false),这样它就不会显示任何文字。
- 最后,我们创建另一个名为user的QLabel对象,并将其几何尺寸设置为50 x 100像素,宽度为70像素,高度为70像素。
- 现在让我们看一下使用这些对象的一些代码。# 设置颜色 self.head.setGraphicsEffect(Qt.darkCyan) # 创建 vs 标签 self.vs = QLabel(“vs”, self) # 设置几何形状 self.vs.setGeometry(150, 110, 30, 50)
- 该代码将创建一个名为head的QGraphicsItem对象,并将图形效果设置为着色。
- 头部也将创建一个QLabel对象,并将其作为父对象。
- 标签将被赋予150 x 110像素的几何形状,宽度为30像素,高度为50像素。
- 接下来,标签的字体将被设置为斜体,并禁用下划线。
- 最后,用户的QLabel对象将被创建,尺寸与头部相同。
- 代码从创建一个用户界面开始。
- 用户界面由三个标签、一个计算机选择标签和三个按钮组成。
- 第一个按钮,岩石按钮,被设置为30×270像素的几何形状,边界为80像素,中心点为35像素。
-
- 第二个按钮,即纸张按钮,其几何尺寸为120×270像素,边界为80像素,中心点为35像素。
- 第三个按钮,即剪刀按钮,其几何尺寸为210×270像素,边界为80像素,中心点为35像素。
- 接下来,代码为每个按钮设置了动作。
- 对于 “岩石 “按钮,代码将其连接到一个动作,当它被点击时,在屏幕上打印出 “岩石”。
- 对于 “纸 “按钮,代码将其连接到一个动作,当它被点击时在屏幕上打印出 “纸”。
- 对于剪刀按钮,代码将其连接到一个动作,当它被点击时在屏幕上打印出 “剪刀”。
- 该代码创建了一个有三个按钮的用户界面。
- 第一个按钮,”岩石”,被配置为具有以下几何形状。30 x 270 x 80像素。
- 第二个按钮,”纸”,被配置为具有以下几何形状:120 x 270 x 80像素。
- 第三个按钮 “剪刀 “被配置为具有以下几何图形:210 x 270 x 80像素。
- 每个按钮都有一个与之相关的动作。
- 岩石按钮的动作与rock_action函数相连,当它被点击时将被执行。
- 纸张按钮的动作与paper_action函数相连,当它被点击时将被执行。
- 剪刀按钮的动作与sc_action函数相连,并在点击时执行。
- 代码从创建一个名为game_reset的QPushButton对象开始。
- 该按钮有以下属性: name: “game_reset” label: “重置 “图标。”ui/images/pushbutton.png” 接下来,代码使用setGeometry()设置按钮的几何形状。
- 坐标是(100,320,120,50)。
- 按钮的大小也是指定的(它将是100×32像素)。
- 最后,用setGraphicsEffect()给按钮添加一个颜色效果。
- 这个效果使用Qt的红色作为它的基础颜色。
- 下一步是为game_reset按钮创建一个动作处理程序。
- 当有人点击它时,这个处理程序将被调用。
- 该代码创建了一个QTimer对象,并给它附加了一个名为timeout()的动作。
- 这个动作将使定时器运行1000毫秒(1秒)。
- 在这段时间过后,showTime()函数将被执行。
- 这个函数只是在屏幕上显示一条信息:”定时器开始”。
- 该代码创建了一个名为game_reset的QPushButton,并将其几何形状设置为100 x 320像素,宽度为120像素,高度为50像素。
- 它还将按钮的颜色设置为红色。
- 接下来,代码创建了一个QGraphicsColorizeEffect对象,并将其颜色设置为Qt.red。
- 最后,代码为game_reset按钮添加了一个名为clicked的动作,将其连接到self.reset_action函数。
- 当用户点击game_reset按钮时,这个函数将被执行。
- 这段代码的最后一部分负责创建一个定时器对象,并为其添加一个名为timeout的动作,将其连接到self.showTime函数。
- 这个函数将
- 这段代码以初始化一些变量开始。
- 第一个变量是self.counter,它将跟踪石头、纸和剪刀按钮被点击的次数。
- 接下来,代码设置了三个按钮(石头、纸和剪刀)并定义了它们各自的动作。
- 当用户点击岩石按钮时,代码将self.choice设为1,并将用户标签的边框图片设为rock.png。
- 当用户点击paper按钮时,代码将self.choice设为2,并将用户标签的边框图片设为Paper.png。
- 最后,当用户点击剪刀按钮时,代码将self.choice设为3,并将用户标签的边框图像设为Scissor.png。
- 接下来是一些逻辑,检查谁赢得了用户之间的 “石头对布”、”石头对剪刀 “和 “布对剪刀 “的每场比赛。
- 如果这些比赛之一已经完成(由任何一个玩家点击这些按钮之一),那么什么也不会发生;屏幕上不会显示或以任何方式改变新的图像,因为没有必要这样做,因为两个玩家都是如此。
- 代码首先设置了一些变量来存储用户的选择信息。
- 这些变量在后面的代码中用于检查谁赢得了比赛。
- 接下来,代码检查是否有任何按钮被点击过。
- 如果有一个按钮被点击了,就会采取适当的行动。
- 如果没有按钮被点击,那么代码将设置三个按钮,并通过检查用户的选择变量来决定用户将选择哪一个。
- 一旦做出这个决定,适当的岩石图像、纸张图像或剪刀图像将被设置为用户的标签,计数器的值将减少1。