python小游戏课程设计报告,基于python的游戏设计

这篇文章主要介绍了关于python小游戏的毕业论文,具有一定借鉴价值,需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获,下面让小编带着大家一起了解一下。

完整的源码:Python GUI tkinter 开发连连看小游戏 源码

游戏的三点要素
地图
地图背 景是10*10的方格
每个方格内随机填充一 个蔬菜或水果
音效
背景音乐
鼠标点击蔬菜或水果的音乐
游戏规则
连续点击两个方格
方格内图片相同且可连接就消除这两个图片
所有方格内图片消除后游戏完成结束
搭建游戏窗口
    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)
注意:
tk.geometry() 对于这个方法,我们一般按照标准形式是"400x400+20+20"这样的参数,但是这里面的乘号是小写字母x不是X和*,也不是×。
同样也要求必须是整数,不能带小数点

添加菜单
两种菜单:

下拉式菜单
弹出式菜单
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)
添加背景音乐
    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()
添加游戏背景画布
定义 canvas
使用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左上角对齐
 
设计游戏地图
分析一下图形,由行和列组成,各有10个小格,共有100个区域python for语句用法。

数组实现
    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)
点位与坐标的关系
游戏的背景图片,上面和左边都留有空白。取位置的至少需要考虑小图片的宽高和边框,更加横纵排位取坐标点。


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

提取游戏的素材图标
把这些水果和蔬菜的小图片,提取出来


思路分析

第一张图片,0,0 右下角 w,h
第二张图片,w,0 右下角 2w,h

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))
小图标绘制思路分析
当前是一个地图,10*10格子总共100个,每个位置称为一个点位。
每个各自左上的原点,是小图标开始的位置,再把上面切割好的图片,根据原点位置放入格子中,形成一个图像。
在根据随机函数,把每个小图标随机的放置在这100个格子中,绘制出地图。


图像绘制在地图上
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')
    

添加游戏动作,消除小图标
添加点击的音效
点击小图标后,有一个红色的边框,表示选中状态
当再次点击与第一次点击位置相同时,取消选中状态
再次点击不是第一次点击位置,判断图片是否相同,相同的话判断是否连通。连通消除,不是连通的取消选中状态。
不是相同图片,取消选中状态。
audio放入音效


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')

游戏玩法规则分析
1. 相邻相连


2. 直线相连
第一次与第二次点击,判断从点位小的向点位大的,逐步平移判断是否存在,空的就继续平移,能达到第二次点击位置就判断相连。垂直方向同理。


3.一个角的相连
P1和P2两个位置存在一个拐角的时候,取P1的行和P2的列的交点,交点为P3,判断P1与P3是否水平直连,P2与P3是否垂直直连,满足这两个条件,就判断P1与P2相连。

4.两个角相连
两个图标的位置P1,P2
找出两个位置P3,P4,看P1与P3是否直连,P2与P4是否是直连,P3与P4是否直连。如果满足三个条件,P1与P2直连。
根据这个思路,找出P3,P4的位置。从(0,3)开始向右遍历,碰到空点位,创建P3,开始子循环,(0,6)开始向右遍历,找到空点创建P4,根据直连逻辑,判断P3,P4是否连接。 当不存在P3,P4连接,继续循环。

5. 三个角相连


6.更多角相连
需要一种算法,专门解决多角情况。


直连算法实现
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}

一个角相连算法实现
P1与P2之间,找一个交叉点P3
会出现两个点,有一个满足就可以!


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}

两个角相连算法实现
当P1点与P3点相连,P2点与P4点相连,P3与P4相连,判定P1与P2相连。
画出两条线,(0,3)当成P3,(0,6)当成P4,P3开始逐个遍历,存在图标的点跳过,找到空位置点,此时P1与P3水平直线相连;开始遍历P4,方法相同找到P2与P4直线相连,找到一个P4与P3垂直方向直线相连,判断出P1与P2相连。
直到遍历结束,没有相连的两点,判断不相连。

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}

优化连通性判断算法
Astar算法原理:

Astar算法步骤
将起点A加入open list中 (open list 待检查的列表)
查看起点A相邻节点,把其中可走节点加入open list中
把A从open list移到 close list中 (close list 封闭列表 再寻找节点就不再关注了)
从 open list 中 查找代价最低的节点。代价:起点到当前节点的距离 + 当前节点道终点的距离
起点到当前节点的距离:已经走过的步数
当前节点道终点的距离:估算的步数
检查代价最低节点相邻节点 是否可行
重复以上两步直到结束
结束条件:
终止节点加到close list中
open list为空
Astar算法实现
# -!- 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
 

调用Astar算法
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

添加彩蛋 便于测试
点击鼠标中键,直接删除一个小图标

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) # 绑定事件  鼠标中键

绘制最短路径及消除框
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} 
————————————————
版权声明:本文为CSDN博主「chatgpt001」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/chatgpt001/article/details/133674378

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值