Python mini game course design report, game design based on Python

This article mainly introduces the graduation thesis about python games, which has certain reference value. Friends in need can refer to it. I hope you will gain a lot after reading this article. Let the editor take you to understand it together.

Complete source code: Python GUI tkinter development Lianliankan mini game source code

Three elements of the game

map

  • The map background is a 10*10 grid
  • Each square is randomly filled with a vegetable or fruit

Sound effects

  • Background music
  • Music for mouse click on vegetables or fruits

game rules

  • Click two squares in succession
  • If the pictures in the squares are the same and can be connected, the two pictures will be eliminated.
  • The game ends when all the pictures in the squares are eliminated.

Build game window

    def window_center(self, width, height):
        # 创建居中的窗口
        screenwidth = self.windows.winfo_screenwidth()  # 获取桌面屏幕的宽度
        screenheight = self.windows.winfo_screenheight()  # 获取桌面屏幕的高度
        size = "%dx%d+%d+%d" % (
        width, height, screenwidth / 2 - width / 2, screenheight / 2 - height / 2)  # 宽x高+X轴位置+Y轴位置
        self.windows.geometry(size)

Note: For this method of
tk.geometry() , we generally follow the standard form of parameters such as "400x400+20+20", but the multiplication sign here is the lowercase letter x, not X and *, nor ×.
It is also required that it must be an integer without a decimal point.

Add menu

Two menus:

  • drop-down menu
  • Pop-up menu
def add_components(self):
	# 创建菜单
	self.menubar = tk.Menu(self.windows, bg="lightgrey", fg="black")
	self.file_menu = tk.Menu(self.menubar, bg="lightgrey", fg="black")
	self.file_menu.add_command(label="新游戏", command=self.file_menu_clicked, accelerator="Ctrl+N")
	self.menubar.add_cascade(label="游戏", menu=self.file_menu)
	self.windows.configure(menu=self.menubar)

Add background music

	def play_music(self, music, volume=0.5):
	    pygame.mixer.music.load(music)
	    pygame.mixer.music.set_volume(volume)
	    pygame.mixer.music.play()
	
	def stop_music(self):
	    pygame.mixer.music.stop()

Add game background canvas

  • Define canvas
  • Draw pictures using canvas
def add_components(self):
	# 创建背景画布的canvas
	self.canvas = tk.Canvas(self.windows, bg="white", width=800, height=750)
	self.canvas.pack()
	
 def draw_background(self):
        self.background_im = ImageTk.PhotoImage(file="images/bg.png")
        self.canvas.create_image((0,0),anchor='nw', image=self.background_im) # 从0,0点开始 nw左上角对齐

Design game map

Analyze the graph, which consists of rows and columns, each with 10 cells, and a total of 100 areas. How to use the python for statement .

Insert image description here

Array implementation

    def init_map(self):
        """
        初始化地图数组
        0,1,2...24
        :return:
        """
        records = []
        for i in range(0, self._iconCount):
            for j in range(0, 4):
                records.append(i)
        np.random.shuffle(records)  # 所有元素随机排序
        self._map = np.array(records).reshape(10, 10)

The relationship between points and coordinates

The game's background image, with space above and to the left. When choosing the position, you need to consider at least the width, height and border of the small picture, and even more horizontally and vertically when choosing the coordinate points.
Insert image description here

class MainWindow:
	# 省略之前的代码 函数 。。。
	# 以下新增
    def getX(self, row):
        """
        获取row的X轴的起始坐标
        :return:
        """
        return self._margin + row * self._iconWidth

    def getY(self, column):
        """
        获取column的Y轴的起始坐标
        :return:
        """
        return self._margin + column * self._iconHeight

    def get_origin_Coordinate(self, row, column):
        """
        获取点位的左上角原点坐标
        """
        return self.getX(row), self.getY(column)

    def get_gamePoint(self, x, y):
        """
        获取玩家点击的x,y坐标在游戏地图上的点位
        :param x:
        :param y:
        :return:
        """
        for row in range(0, self._gameSize):
            x1 = self.getX(row)
            x2 = self.getX(row + 1)
            if x1 <= x < x2:
                point_row = row
        for column in range(0, self._gameSize):
            j1 = self.getY(column)
            j2 = self.getY(column + 1)
            if j1 <= y < j2:
                point_column = column
        return Point(point_row,point_column)

class Point:
    # 游戏中的点位
    def __init__(self,row,column):
        self.row=row
        self.column = column

    def isEqual(self, point):
        if self.row == point.row and self.column == point.column:
             return True
        else:
            return False

Extract game material icons

Extract these small pictures of fruits and vegetables
Insert image description here

Idea analysis

Insert image description here
The first picture, 0,0, w,h in the lower right corner.
The second picture, w,0, 2w,h, in the lower right corner.

class MainWindow:
	# 省略之前的代码 函数 。。。
	# 以下新增
    def extractSmalllconList(self):
        # 提取小图片素材到icons列表中
        image_source = Image.open("images/fruits.png")
        for index in range(0,self._iconCount):
            # 裁剪图片,指定图片的左上角和右下角
            region = image_source.crop((index*self._iconWidth,0,(index+1)*self._iconWidth,self._iconHeight))
            self._icons.append(ImageTk.PhotoImage(region))

Analysis of small icon drawing ideas

It is currently a map with a total of 100 10*10 grids, and each position is called a point.
The origin on the upper left side of each one is the starting position of the small icon. Then put the cut picture above into the grid according to the origin position to form an image.
According to the random function, each small icon is randomly placed in the 100 grids to draw a map.
Insert image description here

Image plotted on map

def __init__(self):
	# 添加新的代码
	
 	# 准备小图标的图片
 	self.extractSmalllconList()
 		
def file_menu_clicked(self):
	self.stop_music()
	self.init_map()
	self.draw_map() # 把绘制地图放在菜单点击事件中

 def draw_map(self):
        # 根据地图绘制小图标
        for row in range(0,self._gameSize):
            for column in range(0, self._gameSize):
                x,y = self.get_origin_Coordinate(row, column)
                self.canvas.create_image((x,y), image=self._icons[self._map[row][column]], anchor='nw')
	

Add game actions and eliminate small icons

  1. Add click sound effects
  2. After clicking the small icon, there will be a red border indicating the selected status.
  3. When clicking again at the same position as the first click, uncheck the state
  4. If you click again on a position other than the first click, it will be judged whether the pictures are the same. If they are the same, it will be judged whether they are connected. Connectivity is eliminated, not the unchecked state of connectivity.
  5. If they are not the same picture, uncheck the status.

audioput sound effects
Insert image description here

class MainWindow():
 	# 省略之前的代码 函数 。。。
	# 以下新增
    _isFirst = True  # 第一次点击小头像
    _isGameStart = False  # 游戏是否开始
    
    NONE_LINK = 0  # 不连通
    LINK_LINK = 1  # 连通
    NEIGHBOR_LINK = 10  # 相邻连通
    
    EMPTY = -1

    def addComponents(self):
        # 省略之前的代码 函数 。。。
		# 以下新增
		
        # 添加绑定事件
        self.canvas.bind('<Button-1>', self.clickCanvas) # 绑定鼠标左键
        self.canvas.bind('<Button-2>', self.eggClickCanvas) # 鼠标中键
   
	def clickCanvas(self, event):
		if self._isGameStart:
		    point = self.getGamePoint(event.x, event.y)
		    if self._isFirst:
		        print('第一次点击')
		        self.playMusic('audio/click1.mp3')
		        self._isFirst = False
		        self.drawSelectedArea(point) # 选择的点位标红框
		        self._formerPoint = point
		    else:
		        print('第二次点击')
		        self.playMusic('audio/click2.mp3')
		        if point.isEqual(self._formerPoint):
		            print('两次点击的点位相同')
		            self.canvas.delete('rectRedOne') # 删除红框
		            self._isFirst = True
		        else:
		            print('两次点击的点位不同')
		            type = self.getLinkType(self._formerPoint, point)
		            if type['type'] != self.NONE_LINK:
		                self.clearLinkedBlocks(self._formerPoint, point)
		                self.canvas.delete('rectRedOne')
		                self._isFirst = True

    def drawSelectedArea(self, point):
        """选择的点位标红框"""
        lt_x, lt_y = self.getOriginCoordinate(point.row, point.column) # 左上角
        rb_x, rb_y = self.getOriginCoordinate(point.row + 1, point.column + 1) # 右下角,下一行下一列格子的左上角位置
        self.canvas.create_rectangle(lt_x, lt_y, rb_x, rb_y, outline='red', tags='rectRedOne')

    def getLinkType(self, p1, p2):
        """取得两个点位的连通情况"""
        if self._map[p1.row][p1.column] != self._map[p2.row][p2.column]:
            return {'type': self.NONE_LINK}

        if self.isNeighbor(p1, p2):
            print('两个小头像是相邻连通')
            return {'type': self.NEIGHBOR_LINK}

    def isNeighbor(self, p1, p2):
        """判断两个点位是否相邻"""
        # 垂直方向
        if p1.column == p2.column:
            # 大小判断
            if p2.row < p1.row:
                if p2.row + 1 == p1.row:
                    return True
            else:
                if p1.row + 1 == p2.row:
                    return True
        # 水平方向
        if p1.row == p2.row:
            # 大小判断
            if p2.column < p1.column:
                if p2.column + 1 == p1.column:
                    return True
            else:
                if p1.column + 1 == p2.column:
                    return True

        return False

    def clearLinkedBlocks(self, p1, p2):
        """消除两个点位的小头像"""
        print('消除选中的两个点位上的小头像')
        self.canvas.delete('image%d%d' % (p1.row, p1.column))
        self.canvas.delete('image%d%d' % (p2.row, p2.column))

        self._map[p1.row][p1.column] = self.EMPTY
        self._map[p2.row][p2.column] = self.EMPTY

        self.playMusic('audio/link.mp3')

Game play rules analysis

1. Adjacent and connected

Insert image description here

2. Connected by straight lines

After the first and second click, it is judged from the small point to the big point, and gradually pans to determine whether it exists. If it is empty, it continues to pan. If it reaches the second click position, it is judged to be connected. The same goes for the vertical direction.
Insert image description here

3. Connection of one corner

When there is a corner between P1 and P2, take the intersection point of the row of P1 and the column of P2. The intersection point is P3. Determine whether P1 and P3 are directly connected horizontally and whether P2 and P3 are directly connected vertically. If these two conditions are met, Just judge that P1 and P2 are connected.

Insert image description here

4. Two corners connected
  • The positions of the two icons P1, P2
  • Find two positions P3 and P4, and see whether P1 and P3 are directly connected, whether P2 and P4 are directly connected, and whether P3 and P4 are directly connected. If three conditions are met, P1 and P2 are directly connected.
  • Based on this idea, find the positions of P3 and P4. Start traversing to the right from (0,3), when an empty point is encountered, create P3, start a sub-loop, start traversing to the right from (0,6), find an empty point and create P4, according to the direct connection logic, determine whether P3 and P4 connect. When there is no P3, P4 connection, the cycle continues.
    Insert image description here
5. Three corners connected

Insert image description here

6. More corners connected

An algorithm is needed to specifically solve the multi-angle case.
Insert image description here

Direct connection algorithm implementation

class MainWindow():
 	# 省略之前的代码 函数 。。。
	# 以下新增
    LINE_LINK = 11  # 直线相连


	def isStraightLink(self, p1, p2):
		"""判断两个点位是否直线相连"""
	    # 水平方向判断
	    if p1.row == p2.row:
	        if p1.column > p2.column: # 找小的
	            start = p2.column
	            end = p1.column
	        else:
	            start = p1.column
	            end = p2.column
	        for column in range(start + 1, end):
	            if self._map[p1.row][column] != self.EMPTY: # p1.row 行 一样的
	                return False
	        return True
	    # 垂直方向判断
	    elif p1.column == p2.column:
	        if p1.row > p2.row: # 找小的
	            start = p2.row
	            end = p1.row
	        else:
	            start = p1.row
	            end = p2.row
	        for row in range(start + 1, end):
	            if self._map[row][p1.column] != self.EMPTY:  # p1.column列 一样的
	                return False
	        return True
	
	def getLinkType(self, p1, p2):
	    """取得两个点位的连通情况"""
	    if self._map[p1.row][p1.column] != self._map[p2.row][p2.column]:
	        return {'type': self.NONE_LINK}
		
	    if self.isNeighbor(p1, p2):
	        print('两个小头像是相邻连通')
	        return {'type': self.NEIGHBOR_LINK}
	    elif self.isStraightLink(p1, p2):  # 写的是这个函数 直连算法
	        print('两个小头像是直线相连')
	        return {'type': self.LINE_LINK}

Implementation of an angle connection algorithm

Between P1 and P2, find an intersection point P3 and
there will be two points. One of them is enough!
Insert image description here

def isEmptyInMap(self, point):
	"""判断一个点位是否为空"""
    if self._map[point.row][point.column] == self.EMPTY:
        return True
    else:
        return False

def isOneCornerLink(self, p1, p2):
	"""一个角相连算法"""
    pointCorner = Point(p1.row, p2.column)
    if self.isEmptyInMap(pointCorner) and self.isStraightLink(p1, pointCorner) and self.isStraightLink(p2, pointCorner):
        return pointCorner

    pointCorner = Point(p2.row, p1.column)
    if self.isEmptyInMap(pointCorner) and self.isStraightLink(p1, pointCorner) and self.isStraightLink(p2, pointCorner):
        return pointCorner

    return False

def getLinkType(self, p1, p2):
	"""取得两个点位的连通情况"""
    if self._map[p1.row][p1.column] != self._map[p2.row][p2.column]:
        return {'type': self.NONE_LINK}

    if self.isNeighbor(p1, p2):
        print('两个小头像是相邻连通')
        return {'type': self.NEIGHBOR_LINK}
    elif self.isStraightLink(p1, p2):
        print('两个小头像是直线相连')
        return {'type': self.LINE_LINK}
    elif self.isOneCornerLink(p1, p2): # 添加 一个角相连算法
    	return {'type': self.ONE_LINK}

Implementation of algorithm for connecting two corners

  1. When point P1 is connected to point P3, point P2 is connected to point P4, and point P3 is connected to point P4, it is determined that P1 is connected to P2.
  2. Draw two lines, (0,3) is regarded as P3, (0,6) is regarded as P4, P3 starts to be traversed one by one, the points with icons are skipped, and the empty position point is found. At this time, P1 and P3 are connected with a horizontal straight line; start traversing P4, use the same method to find a straight line connecting P2 and P4, find a straight line connecting P4 and P3 in the vertical direction, and judge that P1 is connected to P2.
  3. Until the end of the traversal, if there are no connected two points, it is judged that they are not connected.
    Insert image description here
def isTwoCornerLink(self, p1, p2):
     """两个角相连算法"""
     # 水平方向判断
     for column in range(0, self._gameSize):
     	if column == p1.column or column == p2.column:
            continue
        pointCorner1 = Point(p1.row, column)
        pointCorner2 = Point(p2.row, column)
        if self.isStraightLink(p1, pointCorner1) \
                and self.isStraightLink(pointCorner1, pointCorner2) \
                and self.isStraightLink(pointCorner2, p2) \
                and self.isEmptyInMap(pointCorner1) \
                and self.isEmptyInMap(pointCorner2):
            return {'p1': pointCorner1, 'p2': pointCorner2}

    # 垂直方向判断
    for row in range(0, self._gameSize):
        if row == p1.row or row == p2.row:
            continue
        pointCorner1 = Point(row, p1.column)
        pointCorner2 = Point(row, p2.column)
        if self.isStraightLink(p1, pointCorner1) \
                and self.isStraightLink(pointCorner1, pointCorner2) \
                and self.isStraightLink(pointCorner2, p2) \
                and self.isEmptyInMap(pointCorner1) \
                and self.isEmptyInMap(pointCorner2):
            return {'p1': pointCorner1, 'p2': pointCorner2}

    return False

def getLinkType(self, p1, p2):
	"""取得两个点位的连通情况"""
    if self._map[p1.row][p1.column] != self._map[p2.row][p2.column]:
        return {'type': self.NONE_LINK}

    if self.isNeighbor(p1, p2):
        print('两个小头像是相邻连通')
        return {'type': self.NEIGHBOR_LINK}
    elif self.isStraightLink(p1, p2):
        print('两个小头像是直线相连')
        return {'type': self.LINE_LINK}
    elif self.isOneCornerLink(p1, p2): 
    	return {'type': self.ONE_LINK}
    elif self.isTwoCornerLink(p1, p2): # 两个角相连算法
     return {'type': self.TWO_LINK}

Optimized connectivity judgment algorithm

Astar algorithm principle:

Astar algorithm steps

  • Add starting point A to the open list (open list list to be checked)
  • Check the adjacent nodes of starting point A and add the traversable nodes to the open list.
  • Move A from open list to close list (close list closes the list and then searches for the node and no longer pays attention)
  • Find the node with the lowest cost from the open list. Cost: distance from the starting point to the current node + distance from the end of the current node. Distance from the starting point
    to the current node: the number of steps that have been taken.
    Distance from the end of the current node: the estimated number of steps.
  • Check whether the nodes adjacent to the lowest cost node are feasible
  • Repeat the above two steps until the end
    . End condition:
  1. The termination node is added to the close list
  2. open list is empty

Astar algorithm implementation

# -!- coding:utf-8 -!-


class Point:
    """游戏中的点位"""

    def __init__(self, row, column):
        self.row = row
        self.column = column

    def isEqual(self, point):
        if self.row == point.row and self.column == point.column:
            return True
        else:
            return False


class Node:
    """节点"""

    def __init__(self, point: Point, endPoint: Point):
        """
        初始化
        :param point: 点位
        :param endPoint: 终止点位
        """
        self.point = point  # 点位
        self.father = None  # 父节点
        self.g = 0  # 到起点的步数
        self.h = abs(endPoint.row - point.row) + abs(endPoint.column - point.column)  # 到终止节点的估算步数


class AStar:
    """
    A星算法
    """

    def __init__(self, map, startNode: Node, endNode: Node, passTag):
        """
        初始化函数
        :param map:地图
        :param startNode:开始节点
        :param endNode: 终止节点
        :param passTag: 可行走标记
        """
        self.openList = []  # 待探索节点列表
        self.closeList = []  # 已探索节点列表
        self.map = map  # 地图
        self.startNode = startNode  # 开始节点
        self.endNode = endNode  # 终止节点
        self.passTag = passTag  # 可行走标记

    def findMinFNode(self):
        """
        查找代价最低节点
        :return: Node
        """
        oneNode = self.openList[0]
        for node in self.openList:
            if node.g + node.h < oneNode.g + oneNode.h:
                oneNode = node
        return oneNode

    def nodeInCloseList(self, nearNode: Node):
        """
        节点是否在close list中
        :param nearNode: 待判断的节点
        :return: Node
        """
        for node in self.closeList:
            if node.point.isEqual(nearNode.point):
                return node
        return False

    def nodeInOpenList(self, nearNode: Node):
        """
        节点是否在open list中
        :param nearNode: 待判断的节点
        :return: Node
        """
        for node in self.openList:
            if node.point.isEqual(nearNode.point):
                return node
        return False

    def searchNearNode(self, minFNode: Node, offsetX, offsetY):
        """
        查找邻居节点
        :param minFNode: 最小代价节点
        :param offsetX: X轴偏移量
        :param offsetY: Y轴偏移量
        :return: Node 或 None
        """
        nearPoint = Point(minFNode.point.row + offsetX, minFNode.point.column + offsetY)
        nearNode = Node(nearPoint, self.endNode.point)

        # 越界检查
        if nearNode.point.row < 0 or nearNode.point.column < 0 or nearNode.point.row > len(
                self.map) - 1 or nearNode.point.column > len(self.map[0]) - 1:
            print('越界')
            return

        # 障碍检查
        if self.map[nearNode.point.row][nearNode.point.column] != self.passTag and not nearNode.point.isEqual(
                self.endNode.point):
            print('障碍')
            return

        # 判断是否在close list中
        if self.nodeInCloseList(nearNode):
            print('在close list中')
            return

        print("找到可行节点")

        if not self.nodeInCloseList(nearNode):
            self.openList.append(nearNode)
            nearNode.father = minFNode
            # 计算g值
            step = 1
            node = nearNode
            while node.point.isEqual(self.startNode.point):
                step += 1
                node = node.father
            nearNode.g = step
        return nearNode

    def start(self):
        if self.map[self.endNode.point.row][self.endNode.point.column] == self.passTag:
            return
        print("起点: ", self.startNode.point.column, self.startNode.point.row)
        print("终点: ", self.endNode.point.column, self.endNode.point.row)

        # 1.将起点加入open list
        self.openList.append(self.startNode)

        while True:
            # 2.从open list中查找代价最低的节点
            minFNode = self.findMinFNode()
            # 3.从open list中移除,并加入close list
            self.openList.remove(minFNode)
            self.closeList.append(minFNode)
            # 4.查找四个邻居节点
            self.searchNearNode(minFNode, 0, -1)  # 向上查找
            self.searchNearNode(minFNode, 1, 0)  # 向右查找
            self.searchNearNode(minFNode, 0, 1)  # 向下查找
            self.searchNearNode(minFNode, -1, 0)  # 向左查找

            # 5.判断是否终止
            endNode = self.nodeInCloseList(self.endNode)
            if endNode:
                print('两个节点是连通的')
                path = []
                node = endNode
                while not node.point.isEqual(self.startNode.point):
                    path.append(node)
                    if node.father:
                        node = node.father

                path.reverse()  # 逆向排序 得到起点到终点的路径
                return path

            if len(self.openList) == 0:
                print('两个节点不连通')
                return None

Call the Astar algorithm

def getLinkTypeAStar(self, p1, p2):
    """通过A星算法取得两个点位的连通情况"""
    if self._map[p1.row][p1.column] != self._map[p2.row][p2.column]:
        return {'type': self.NONE_LINK}

    startNode = Node(p1, p2)
    endNode = Node(p2, p2)
    pathList = AStar(self._map, startNode, endNode, self.EMPTY).start()
    if pathList:
        return {'type': self.LINK_LINK}
    else:
        return {'type': self.NONE_LINK}

def clickCanvas(self, event):
    if self._isGameStart:
        point = self.getGamePoint(event.x, event.y)
        if self._isFirst:
            print('第一次点击')
            self.playMusic('audio/click1.mp3')
            self._isFirst = False
            self.drawSelectedArea(point)
            self._formerPoint = point
        else:
            print('第二次点击')
            self.playMusic('audio/click2.mp3')
            if point.isEqual(self._formerPoint):
                print('两次点击的点位相同')
                self.canvas.delete('rectRedOne')
                self._isFirst = True
            else:
                print('两次点击的点位不同')
                type = self.getLinkTypeAStar(self._formerPoint, point) # 修改成AStar算法
                if type['type'] != self.NONE_LINK:
                    self.clearLinkedBlocks(self._formerPoint, point)
                    self.canvas.delete('rectRedOne')
                    self._isFirst = True

Add easter eggs to facilitate testing

Click the middle mouse button to delete a small icon directly

def clearOneBlock(self, p1):
    """消除玩家点击的小头像"""
    print('消除选中的一个点位上的小头像')
    self.canvas.delete('image%d%d' % (p1.row, p1.column))
    self._map[p1.row][p1.column] = self.EMPTY

def eggClickCanvas(self, event):
     """彩蛋功能"""
     if self._isGameStart:
         point = self.getGamePoint(event.x, event.y)
         self.clearOneBlock(point)

def addComponents(self):
    # 创建菜单
    # 省略之前的代码 函数 。。。
 # 以下新增
    self.canvas.bind('<Button-2>', self.eggClickCanvas) # 绑定事件  鼠标中键

Draw the shortest path and eliminate boxes

def drawPathPoint(self, point):
    """路径点位标绿框"""
    lt_x, lt_y = self.getOriginCoordinate(point.row, point.column)
    rb_x, rb_y = self.getOriginCoordinate(point.row + 1, point.column + 1)
    self.canvas.create_rectangle(lt_x, lt_y, rb_x, rb_y, outline='green',
                                 tags='path%d%d' % (point.row, point.column))

def getLinkTypeAStar(self, p1, p2):
    """通过A星算法取得两个点位的连通情况"""
    if self._map[p1.row][p1.column] != self._map[p2.row][p2.column]:
        return {'type': self.NONE_LINK}

    startNode = Node(p1, p2)
    endNode = Node(p2, p2)
    pathList = AStar(self._map, startNode, endNode, self.EMPTY).start()
    if pathList:
        # 绘制路径
        for node in pathList:
            self.drawPathPoint(node.point) # 添加 路径点位标绿框
        return {'type': self.LINK_LINK}
    else:
        return {'type': self.NONE_LINK}              

Guess you like

Origin blog.csdn.net/chatgpt001/article/details/133674378