Python不能做游戏?游戏实战之-----《ink spill》(附游戏完整源码)

        嗨喽~小伙伴们,大家早上好,中午好,晚上好呀,  

        通过前两章对Pygame的学习,我们了解了它的基本使用,附上链接:

        1. Python不能做游戏?一小时做出一个游戏!

        2. Python不能做游戏?Pygame中的对象你了解吗?

        现在,我们就开始真正动手写一个游戏。

        这个游戏名字为:ink spill,中文名:墨水溢出。这是Python中一个非常典型的游戏,我们首先来看看游戏长什么样子以及应该怎么玩

        小伙伴们看完后,应该差不多明白这个游戏的玩法了。

        现在,让我们站在“设计者”的角度来考虑,这个游戏应该怎么去制作。 

        一般说来,游戏制作要考虑三个方面的内容:

  • 游戏道具 (图片,音效等)
  • 逻辑控制 (游戏状态的逻辑控制)
  • UI设计 (游戏界面的设计)

        游戏道具 ,主要通过两种方式获得:一是加载图片,二是用代码直接绘制

        本游戏中,需要用到的图片有以下几个:

inkspilllogo.png​​

inkspillresetbutton.png

inkspillsettings.png

inkspillsettingsbutton.png

inkspillspot.png

         

        其余的,咱均用代码直接绘制

        分析一下游戏主界面:

        它主要由以下几个部分组成:

  • 界面正中央的大格子
  • 界面左边的生命“计”
  • 界面正下方的六个不同颜色的调色板
  • 界面右下方的“重新游戏”按钮“设置”按钮

        在编写游戏之前,我们考虑一个问题:如何设计游戏的难度

        大伙首先想到的肯定是,控制小格子的数量,数量越多,难度越大:

        除此之外,关于游戏难度,咱还可以创建一个 boxesToChange 变量,随机将某个小格子的周围格子变成与之同色,这样的格子数用boxesToChange来描述,这样,“简单难度”就将boxesToChange的值设大一点(使得同色的越多),“困难难度”就将boxesToChange的值设小一点(使得同色的越少)(或者直接设为0)。

        前面几章说过,一个游戏会有各种各样的游戏状态,比如角色的血量,武器类型等。首先,让我们思考一下,在这个游戏中,需要哪些变量来存储哪些游戏状态(先自己想一想......)

        不知道小伙伴们能想到多少个游戏状态,答案在下面的代码中:


# 根据设置界面,有不同的整个格子尺寸、小格子数量和生命

# 小格子大小
SMALL_BOX_SIZE = 60  # 大小以像素为单位
MEDIUM_BOX_SIZE = 20
LARGE_BOX_SIZE = 11

# 整个格子的大小
SMALL_BOARD_SIZE = 6  # 大小以一个小格子为单位
MEDIUM_BOARD_SIZE = 17
LARGE_BOARD_SIZE = 30

# 最多几次操作(生命)
SMALL_MAX_LIFE = 10
MEDIUM_MAX_LIFE = 30
LARGE_MAX_LIFE = 64

FPS = 30
WINDOW_WIDTH = 640
WINDOW_HEIGHT = 480
boxSize = MEDIUM_BOX_SIZE
PALETTE_GAP_SIZE = 10  # 调色板间隔大小
PALETTE_SIZE = 45
EASY = 0  # 难度:简单
MEDIUM = 1  # 难度:中等
HARD = 2  # 难度:困难

difficulty = MEDIUM  # 游戏以难度“中等”模式开始
maxLife = MEDIUM_MAX_LIFE
boardWidth = MEDIUM_BOARD_SIZE
boardHeight = MEDIUM_BOARD_SIZE

#            R    G    B
WHITE = (255, 255, 255)
DARKGRAY = (70, 70, 70)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
ORANGE = (255, 128, 0)
PURPLE = (255, 0, 255)

# 每个方案中的第一种颜色是背景色,接下来的六种是调色板颜色。
COLOR_SCHEMES = (((150, 200, 255), RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE),
                 ((0, 155, 104), (97, 215, 164), (228, 0, 69), (0, 125, 50), (204, 246, 0), (148, 0, 45),
                  (241, 109, 149)),
                 ((195, 179, 0), (255, 239, 115), (255, 226, 0), (147, 3, 167), (24, 38, 176), (166, 147, 0),
                  (197, 97, 211)),
                 ((85, 0, 0), (155, 39, 102), (0, 201, 13), (255, 118, 0), (206, 0, 113), (0, 130, 9), (255, 180, 115)),
                 ((191, 159, 64), (183, 182, 208), (4, 31, 183), (167, 184, 45), (122, 128, 212), (37, 204, 7),
                  (88, 155, 213)),
                 ((200, 33, 205), (116, 252, 185), (68, 56, 56), (52, 238, 83), (23, 149, 195), (222, 157, 227),
                  (212, 86, 185)))
# 对颜色的处理
for i in range(len(COLOR_SCHEMES)):
    assert len(COLOR_SCHEMES[i]) == 7, '颜色方案 %s 没有7种颜色!.' % (i)
# 背景色,调色板设置默认色
bgColor = COLOR_SCHEMES[0][0]
paletteColors = COLOR_SCHEMES[0][1:]

         你会看到,记录游戏状态,用到了非常多的变量。依据变量的名称,咱可以很容易推出这个变量的作用是什么。实际上,之后的游戏逻辑控制,就是在更改这些变量的值。       

        接着,我们来考虑编写整个游戏框架

        接下来编写的代码会非常之,请小伙伴们仔细体会每个函数的具体作用,这样才能更好的理解整个游戏。

        ​​​​​​​框架编写如下:


def main():
    # 需要用到函数之外的变量
    global FPS_CLOCK, DISPLAY_SURF, LOGO_IMAGE, SPOT_IMAGE, SETTINGS_IMAGE, SETTINGS_BUTTON_IMAGE, RESET_BUTTON_IMAGE
    
    # 初始化
    pygame.init()

    # 控制帧率
    FPS_CLOCK = pygame.time.Clock()
    
    # 创建窗口
    DISPLAY_SURF = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))

    # 加载图片
    LOGO_IMAGE = pygame.image.load('inkspilllogo.png')
    SPOT_IMAGE = pygame.image.load('inkspillspot.png')
    SETTINGS_IMAGE = pygame.image.load('inkspillsettings.png')
    SETTINGS_BUTTON_IMAGE = pygame.image.load('inkspillsettingsbutton.png')
    RESET_BUTTON_IMAGE = pygame.image.load('inkspillresetbutton.png')

    # 设置窗口标题
    pygame.display.set_caption('墨水溢出')

    # 生成界面
    mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty)
    life = maxLife

    # 记录上次点击界面下方调色板的颜色
    lastPaletteClicked = None

    while True:  # 主游戏循环
        paletteClicked = None
        resetGame = False

        # 画屏幕
        DISPLAY_SURF.fill(bgColor)

        # 加载图标和相关按钮
        drawLogoAndButtons()

        # 加载正中央的大格子并随机初始化每个小格子的颜色
        drawBoard(mainBoard)

        # 加载左侧的"生命计"
        drawLifeMeter(life)

        # 加载屏幕底部的六个调色板
        drawPalettes()

        # 判断玩家是否想要退出游戏
        checkForQuit()

        代码中有许多函数如

        generateRandomBoard(),

        我们还未实现,下面我们来实现这些函数:

一. generateRandomBoard() :

        产生游戏主窗口-----中央的整个大格子: 


def generateRandomBoard(width, height, difficulty=MEDIUM):

    # 为整个大格子中的每个小格子创建具有随机颜色的数据结构。
    board = []
    for x in range(width):
        column = []
        for y in range(height):
            column.append(random.randint(0, len(paletteColors) - 1))
        board.append(column)

    # 通过将一些小格子设置为与相邻格子相同的颜色,使同色整个大格子更容易。

    # 确定要更改的小格子数。
    if difficulty == EASY:
        if boxSize == SMALL_BOX_SIZE:
            boxesToChange = 100
        else:
            boxesToChange = 1500
    elif difficulty == MEDIUM:
        if boxSize == SMALL_BOX_SIZE:
            boxesToChange = 5
        else:
            boxesToChange = 200
    else:
        boxesToChange = 0

    # 挂邻居格子的颜色:
    for i in range(boxesToChange):
        # 随机选择要复制其颜色的框
        x = random.randint(1, width - 2)
        y = random.randint(1, height - 2)

        # 随机选择要更改的邻居格子
        direction = random.randint(0, 3)
        if direction == 0:  # 左,下
            board[x - 1][y] = board[x][y]
            board[x][y - 1] = board[x][y]
        elif direction == 1:  # 右,上
            board[x + 1][y] = board[x][y]
            board[x][y + 1] = board[x][y]
        elif direction == 2:  # 左,下
            board[x][y - 1] = board[x][y]
            board[x + 1][y] = board[x][y]
        else:  # 左,上
            board[x][y + 1] = board[x][y]
            board[x - 1][y] = board[x][y]

    return board

二. drawLogoAndButtons() :

        给界面加上部分按钮和图标:

def drawLogoAndButtons():
    # 绘制墨水溢出徽标、设置和重置按钮。
    # bait()函数用于将图片加载到DISPALY_SURF这个Surface对象上
    DISPLAY_SURF.blit(LOGO_IMAGE, (WINDOW_WIDTH - LOGO_IMAGE.get_width(), 0))
    DISPLAY_SURF.blit(SETTINGS_BUTTON_IMAGE,
                      (WINDOW_WIDTH - SETTINGS_BUTTON_IMAGE.get_width(),
                       WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height()))
    DISPLAY_SURF.blit(RESET_BUTTON_IMAGE, (WINDOW_WIDTH - RESET_BUTTON_IMAGE.get_width(),
                                           WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height() - RESET_BUTTON_IMAGE.get_height()))

        代码中使用了大量坐标运算控制图片的位置,请小伙伴们细细体会。 

三. drawBoard(mainBoard) :

        加载界面正中央的大格子随机初始化每个小格子的颜色:

def drawBoard(board, transparency=255):  # 透明度设置默认为255
    # 彩色方块将绘制到临时曲面,然后绘制到DISPLAY_SURF曲面。这样我们就可以在DISPLAY_SURF的顶部绘制具有透明度的正方形了。
    tempSurf = pygame.Surface(DISPLAY_SURF.get_size())  # 获取窗口尺寸
    tempSurf = tempSurf.convert_alpha()   # 支持透明
    tempSurf.fill((0, 0, 0, 0))   # 先全部处理为黑色

    for x in range(boardWidth):
        for y in range(boardHeight):
            # 获取每个小格子左上角的坐标
            left, top = leftTopPixelCoordOfBox(x, y)
            r, g, b = paletteColors[board[x][y]]
            pygame.draw.rect(tempSurf, (r, g, b, transparency), (left, top, boxSize, boxSize))
    left, top = leftTopPixelCoordOfBox(0, 0)
    # 为防止边缘的小格子颜色与窗口背景色相同,在整个大格子外面包一层黑色细线,厚度为1px
    pygame.draw.rect(tempSurf, BLACK, (left - 1, top - 1, boxSize * boardWidth + 1, boxSize * boardHeight + 1), 1)
    DISPLAY_SURF.blit(tempSurf, (0, 0))

        代码中调用了leftTopPixelCoordOfBox(x,y),实现如下:

def leftTopPixelCoordOfBox(boxx, boxy):
    # 返回某个小格子最左上方像素的x和y。
    # 注意整个大格子位于窗口的正中央
    x_margin = int((WINDOW_WIDTH - (boardWidth * boxSize)) / 2)  # x_margin 为整个大格子最左边与窗口最左边的间距
    y_margin = int((WINDOW_HEIGHT - (boardHeight * boxSize)) / 2)  # y_margin 为整个大格子最上边与窗口最上边的间距
    return boxx * boxSize + x_margin, boxy * boxSize + y_margin

四. drawLifeMeter(life) :

        绘制游戏界面左边的生命“计”并进行简单的生命“逻辑计算”

def drawLifeMeter(currentLife):
    # '生命计' 竖直放置,与上下边缘各相距20px
    lifeBoxSize = int((WINDOW_HEIGHT - 40) / maxLife)

    # 绘制"生命计"的背景色,'生命计'的左上角位于坐标(20,20),宽20px,高20 + (maxLife * lifeBoxSize) px
    pygame.draw.rect(DISPLAY_SURF, bgColor, (20, 20, 20, 20 + (maxLife * lifeBoxSize)))

    for i in range(maxLife):
        if currentLife >= (maxLife - i):  # 画一个实心的红色方框
            pygame.draw.rect(DISPLAY_SURF, RED, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize))
        # 加1px白色的边框
        pygame.draw.rect(DISPLAY_SURF, WHITE, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize), 1)

        在图形的位置定位上,我们用了大量的坐标运算,请小伙伴们细细体会。 

五. drawPalettes() :

        给界面添上正下方的六个调色板并进行简单的逻辑控制: 

def drawPalettes():
    # 在屏幕底部绘制六个调色板
    numColors = len(paletteColors)
    x_margin = int((WINDOW_WIDTH - ((PALETTE_SIZE * numColors) + (PALETTE_GAP_SIZE * (numColors - 1)))) / 2)
    for i in range(numColors):
        left = x_margin + (i * PALETTE_SIZE) + (i * PALETTE_GAP_SIZE)
        top = WINDOW_HEIGHT - PALETTE_SIZE - 10
        pygame.draw.rect(DISPLAY_SURF, paletteColors[i], (left, top, PALETTE_SIZE, PALETTE_SIZE))
        # 为了美观,在格子外2px再加2px厚度的对应颜色的区域
        pygame.draw.rect(DISPLAY_SURF, bgColor, (left + 2, top + 2, PALETTE_SIZE - 4, PALETTE_SIZE - 4), 2)

六.  checkForQuit()

        玩家想要退出游戏时的处理:

def checkForQuit():
    # 如果存在任何退出事件,则终止程序
    for event in pygame.event.get(QUIT):  # 获取所有退出事件
        pygame.quit()  # 如果存在任何退出事件,则终止
        sys.exit()
    for event in pygame.event.get(KEYUP):  # 获取所有KEYUP(按键按下)事件
        if event.key == K_ESCAPE:
            pygame.quit()  # 如果KEYUP(按键按下)事件是针对Esc键的,则终止
            sys.exit()
        pygame.event.post(event)  # 将其他KEYUP(按键按下)事件对象放回原处

         接着,我们来考虑游戏的主要逻辑控制

        由于我们并未创建按钮,所以当“鼠标点击”这个事件发生时,我们尝试着获取其点击的位置坐标(利用Event对象的 pos 属性:mousex, mousey = event.pos),将这个坐标与界面上某个图标的位置区域比较(使用pygame.Rest.collidepoint(mousex, mousey)函数),如果在这个区域内,就说明玩家想要点击该图标来实现对应的功能(如鼠标点击“reset”图标表示玩家想重新开始游戏),我们编写代码做出响应即可。基于这样的思想,咱可以将main()函数补充完整: 

def main():
    global FPS_CLOCK, DISPLAY_SURF, LOGO_IMAGE, SPOT_IMAGE, SETTINGS_IMAGE, SETTINGS_BUTTON_IMAGE, RESET_BUTTON_IMAGE

    pygame.init()
    FPS_CLOCK = pygame.time.Clock()
    DISPLAY_SURF = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))

    # 加载图片
    LOGO_IMAGE = pygame.image.load('inkspilllogo.png')
    SPOT_IMAGE = pygame.image.load('inkspillspot.png')
    SETTINGS_IMAGE = pygame.image.load('inkspillsettings.png')
    SETTINGS_BUTTON_IMAGE = pygame.image.load('inkspillsettingsbutton.png')
    RESET_BUTTON_IMAGE = pygame.image.load('inkspillresetbutton.png')

    # 设置窗口标题
    pygame.display.set_caption('墨水溢出')
    mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty)
    life = maxLife
    lastPaletteClicked = None

    while True:  # 主游戏循环
        paletteClicked = None
        resetGame = False

        # 画屏幕
        DISPLAY_SURF.fill(bgColor)
        # 加载图标和相关按钮
        drawLogoAndButtons()
        # 加载正中央的大格子并随机初始化每个小格子的颜色
        drawBoard(mainBoard)
        # 加载左侧的"生命计"
        drawLifeMeter(life)
        # 加载屏幕底部的六个调色板
        drawPalettes()
        # 判断玩家是否想要退出游戏
        checkForQuit()

        for event in pygame.event.get():  # 事件处理循环
            if event.type == MOUSEBUTTONUP:  # 如果事件是鼠标点击
                mousex, mousey = event.pos   # 获取鼠标点击的坐标
                # 如果点击的是"SETTINGS"字样处
                if pygame.Rect(WINDOW_WIDTH - SETTINGS_BUTTON_IMAGE.get_width(),
                               WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height(),
                               SETTINGS_BUTTON_IMAGE.get_width(),
                               SETTINGS_BUTTON_IMAGE.get_height()).collidepoint(mousex, mousey):
                    # 就展示设置界面
                    resetGame = showSettingsScreen()   # showSettingsScreen() 待实现
                # 如果点击的是"RESET"字样处
                elif pygame.Rect(WINDOW_WIDTH - RESET_BUTTON_IMAGE.get_width(),
                                 WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height() - RESET_BUTTON_IMAGE.get_height(),
                                 RESET_BUTTON_IMAGE.get_width(),
                                 RESET_BUTTON_IMAGE.get_height()).collidepoint(mousex, mousey):
                    # 就重新开始游戏
                    resetGame = True
                else:
                    # 检查是否点击了调色板按钮,如果点击了,返回六个调色板中点击的那一个颜色的索引
                    paletteClicked = getColorOfPaletteAt(mousex, mousey)  # getColorOfPaletteAt()待实现

        if paletteClicked is not None and paletteClicked != lastPaletteClicked:

            # 单击的调色板按钮与上次单击的调色板按钮不同,防止鼠标意外单击同一调色板两次
            lastPaletteClicked = paletteClicked

            # 对大格子填充颜色,floodAnimation()待实现
            floodAnimation(mainBoard, paletteClicked)
            # 点击一次,"生命"减少1
            life -= 1

            resetGame = False
            if hasWon(mainBoard):  # 如果赢了,hasWon()待实现
                for i in range(4):  # 成功的界面效果:闪烁边框4次
                    flashBorderAnimation(WHITE, mainBoard)   # flashBorderAnimation()待实现
                # "闪光"结束后,重新开始游戏
                resetGame = True
                # 暂停2s后再开始游戏
                pygame.time.wait(2000)
            elif life == 0:
                # 生命降为零,玩家失败
                drawLifeMeter(0)
                # 更新界面
                pygame.display.update()
                # 等待0.4s
                pygame.time.wait(400)
                # 失败的结束效果:用黑色"闪光"4次
                for i in range(4):
                    flashBorderAnimation(BLACK, mainBoard)。 # flashBorderAnimation()待实现
                resetGame = True
                # 暂停2s后开始重新游戏
                pygame.time.wait(2000)

        if resetGame:
            # 重新开始游戏
            mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty)
            life = maxLife
            lastPaletteClicked = None
        # 更新界面
        pygame.display.update()
        # 控制帧率
        FPS_CLOCK.tick(FPS)

        上述主要逻辑控制代码中,还有部分函数未实现:

        showSettingsScreen() :展示“设置”界面并提供相应逻辑控制

 

def showSettingsScreen():
    # 获取全局变量
    global difficulty, boxSize, boardWidth, boardHeight, maxLife, paletteColors, bgColor

    # 此函数中的像素坐标是通过将inkspillsettings.png图像加载到图形编辑器中并从中读取像素坐标获得的

    origDifficulty = difficulty
    origBoxSize = boxSize
    screenNeedsRedraw = True

    while True:
        if screenNeedsRedraw:
            DISPLAY_SURF.fill(bgColor)
            # 加载"设置"图片
            DISPLAY_SURF.blit(SETTINGS_IMAGE, (0, 0))

            # 将"墨迹"图片标记放在选定的颜色左边
            if difficulty == EASY:
                DISPLAY_SURF.blit(SPOT_IMAGE, (30, 4))
            if difficulty == MEDIUM:
                DISPLAY_SURF.blit(SPOT_IMAGE, (8, 41))
            if difficulty == HARD:
                DISPLAY_SURF.blit(SPOT_IMAGE, (30, 76))

            # 将墨迹标记放置在选定尺寸旁边
            if boxSize == SMALL_BOX_SIZE:
                DISPLAY_SURF.blit(SPOT_IMAGE, (22, 150))
            if boxSize == MEDIUM_BOX_SIZE:
                DISPLAY_SURF.blit(SPOT_IMAGE, (11, 185))
            if boxSize == LARGE_BOX_SIZE:
                DISPLAY_SURF.blit(SPOT_IMAGE, (24, 220))

            # 加载设置界面右边的颜色选择框
            for i in range(len(COLOR_SCHEMES)):
                drawColorSchemeBoxes(500, i * 60 + 30, i)  # 待实现

            # 更新界面
            pygame.display.update()

        screenNeedsRedraw = False  # 默认情况下,不重新绘制屏幕
        # 事件处理循环
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == KEYUP:
                if event.key == K_ESCAPE:
                    # 设置屏幕上的Esc键返回游戏
                    return not (origDifficulty == difficulty and origBoxSize == boxSize)
            elif event.type == MOUSEBUTTONUP:
                screenNeedsRedraw = True  # 屏幕应该重新绘制
                mousex, mousey = event.pos  # 鼠标点击的坐标

                # 检查难度按钮上是否有咔嗒声
                if pygame.Rect(74, 16, 111, 30).collidepoint(mousex, mousey):
                    difficulty = EASY
                elif pygame.Rect(53, 50, 104, 29).collidepoint(mousex, mousey):
                    difficulty = MEDIUM
                elif pygame.Rect(72, 85, 65, 31).collidepoint(mousex, mousey):
                    difficulty = HARD

                # 检查尺寸按钮上是否有点击
                elif pygame.Rect(63, 156, 84, 31).collidepoint(mousex, mousey):
                    # 小板尺寸设置:
                    boxSize = SMALL_BOX_SIZE
                    boardWidth = SMALL_BOARD_SIZE
                    boardHeight = SMALL_BOARD_SIZE
                    maxLife = SMALL_MAX_LIFE
                elif pygame.Rect(52, 192, 106, 32).collidepoint(mousex, mousey):
                    # 中板尺寸设置:
                    boxSize = MEDIUM_BOX_SIZE
                    boardWidth = MEDIUM_BOARD_SIZE
                    boardHeight = MEDIUM_BOARD_SIZE
                    maxLife = MEDIUM_MAX_LIFE
                elif pygame.Rect(67, 228, 58, 37).collidepoint(mousex, mousey):
                    # 大板尺寸设置:
                    boxSize = LARGE_BOX_SIZE
                    boardWidth = LARGE_BOARD_SIZE
                    boardHeight = LARGE_BOARD_SIZE
                    maxLife = LARGE_MAX_LIFE
                elif pygame.Rect(178, 418, 215, 34).collidepoint(mousex, mousey):
                    # 点击“Back To Game”按钮
                    return not (origDifficulty == difficulty and origBoxSize == boxSize)

                for i in range(len(COLOR_SCHEMES)):
                    # 单击颜色方案按钮
                    if pygame.Rect(500, 30 + i * 60, MEDIUM_BOX_SIZE * 3, MEDIUM_BOX_SIZE * 2).collidepoint(mousex,
                                                                                                            mousey):
                        bgColor = COLOR_SCHEMES[i][0]
                        paletteColors = COLOR_SCHEMES[i][1:]

         上述代码中调用了 drawColorSchemeBoxes():实现如下:

def drawColorSchemeBoxes(x, y, schemeNum):
    # 绘制“设置”屏幕上显示的颜色方案框
    for boxy in range(2):
        for boxx in range(3):
            pygame.draw.rect(DISPLAY_SURF, COLOR_SCHEMES[schemeNum][3 * boxy + boxx + 1],
                             (x + MEDIUM_BOX_SIZE * boxx, y + MEDIUM_BOX_SIZE * boxy, MEDIUM_BOX_SIZE, MEDIUM_BOX_SIZE))
            if paletteColors == COLOR_SCHEMES[schemeNum][1:]:
                # 将"墨迹"图片放置在所选配色方案旁边
                DISPLAY_SURF.blit(SPOT_IMAGE, (x - 50, y))

         getColorOfPaletteAt(mousex, mousey) :获取被点击的调色板颜色的索引:

def getColorOfPaletteAt(x, y):
    # 返回x和y参数覆盖的调色板颜色的索引
    numColors = len(paletteColors)
    xmargin = int((WINDOW_WIDTH - ((PALETTE_SIZE * numColors) + (PALETTE_GAP_SIZE * (numColors - 1)))) / 2)
    # 调色板底端距窗口底端10px
    top = WINDOW_HEIGHT - PALETTE_SIZE - 10
    for i in range(numColors):
        # 六个调色板左上角的坐标:(left,top)
        left = xmargin + (i * PALETTE_SIZE) + (i * PALETTE_GAP_SIZE)
        r = pygame.Rect(left, top, PALETTE_SIZE, PALETTE_SIZE)
        # 找出鼠标单击区域是否在任何调色板内
        if r.collidepoint(x, y):
            return i  # 如果在,则返回所点击的调色板的序号
    # 如果x和y不在任何调色板上,则返回None
    return None

         hasWon(): 判断玩家是否胜利,实现如下:

def hasWon(board):
    # 如果整个棋盘颜色相同,则表示玩家获胜
    for x in range(boardWidth):
        for y in range(boardHeight):
            # 只要发现一个颜色与左上角的颜色不同,玩家还没有赢
            if board[x][y] != board[0][0]:
                return False
    return True

         floodAnimation(mainBoard, paletteClicked):用paletteClicked对应的颜色填充mainBoard(中央的大格子):

def floodAnimation(board, paletteClicked, animationSpeed=25):
    origBoard = copy.deepcopy(board)  # 深拷贝整个大格子
    # 从左上角处的格子开始同色填充,floodFill()待实现
    floodFill(board, board[0][0], paletteClicked, 0, 0)

    for transparency in range(0, 255, animationSpeed):
        # “新”大格子在大格子上慢慢变得不透明
        drawBoard(origBoard)
        # 更新透明度
        drawBoard(board, transparency)
        # 更新界面
        pygame.display.update()
        # 控制帧率
        FPS_CLOCK.tick(FPS)

         floodFill() :填充算法,实现如下:

def floodFill(board, oldColor, newColor, x, y):
    # 洪水填充算法
    if oldColor == newColor or board[x][y] != oldColor:
        # 如果左上角的颜色和点击颜色没有相邻,直接返回
        return
    board[x][y] = newColor  # 更改当前格子的颜色
    # 对任何相邻格子进行递归调用:
    if x > 0:
        floodFill(board, oldColor, newColor, x - 1, y)
    if x < boardWidth - 1:
        floodFill(board, oldColor, newColor, x + 1, y)
    if y > 0:
        floodFill(board, oldColor, newColor, x, y - 1)
    if y < boardHeight - 1:
        floodFill(board, oldColor, newColor, x, y + 1)

         flashBorderAnimation(Color, mainBoard):游戏胜利或失败的结束画面:

def flashBorderAnimation(color, board, animationSpeed=30):
    # "闪光"效果结束后,需要回到原来(游戏刚结束)的界面,此处先保存原来的界面
    origSurf = DISPLAY_SURF.copy()
    flashSurf = pygame.Surface(DISPLAY_SURF.get_size())
    flashSurf = flashSurf.convert_alpha()
    # 实现"闪光"效果
    for start, end, step in ((0, 256, 1), (255, 0, -1)):
        # 外循环的第一次迭代将内循环的透明度设置为从0到255,第二次迭代将透明度设置为从255到0。这就是“闪光”。
        for transparency in range(start, end, animationSpeed * step):
            DISPLAY_SURF.blit(origSurf, (0, 0))
            r, g, b = color
            flashSurf.fill((r, g, b, transparency))
            DISPLAY_SURF.blit(flashSurf, (0, 0))
            drawBoard(board)  # 在透明层的顶部绘制板
            # 更新界面
            pygame.display.update()
            # 控制帧率
            FPS_CLOCK.tick(FPS)
    # 绘制原来(游戏刚结束)的界面
    DISPLAY_SURF.blit(origSurf, (0, 0))

         ......我猜,小伙伴们可能已经懵了......但是做游戏,考虑的东西会非常之多,也许咱只有好好的理解每一个细节,才能真正做出好的游戏。

        最后,附上游戏完整代码:


import random, sys, copy, pygame
from pygame.locals import *  # 将所有的 Pygame 常量导入

# 根据设置界面,有不同的大格子尺寸、小格子数量和寿命

# 小格子大小
SMALL_BOX_SIZE = 60  # 大小以像素为单位
MEDIUM_BOX_SIZE = 20
LARGE_BOX_SIZE = 11

# 整个格子的大小
SMALL_BOARD_SIZE = 6  # 大小以一个小格子为单位
MEDIUM_BOARD_SIZE = 17
LARGE_BOARD_SIZE = 30

# 最多几次操作(生命)
SMALL_MAX_LIFE = 10
MEDIUM_MAX_LIFE = 30
LARGE_MAX_LIFE = 64

FPS = 30
WINDOW_WIDTH = 640
WINDOW_HEIGHT = 480
boxSize = MEDIUM_BOX_SIZE
PALETTE_GAP_SIZE = 10  # 调色板间隔大小
PALETTE_SIZE = 45
EASY = 0  # 难度:简单
MEDIUM = 1  # 难度:中等
HARD = 2  # 难度:困难

difficulty = MEDIUM  # 游戏以难度“中等”模式开始
maxLife = MEDIUM_MAX_LIFE
boardWidth = MEDIUM_BOARD_SIZE
boardHeight = MEDIUM_BOARD_SIZE

#            R    G    B
WHITE = (255, 255, 255)
DARKGRAY = (70, 70, 70)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
ORANGE = (255, 128, 0)
PURPLE = (255, 0, 255)

# 每个方案中的第一种颜色是背景色,接下来的六种是调色板颜色。
COLOR_SCHEMES = (((150, 200, 255), RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE),
                 ((0, 155, 104), (97, 215, 164), (228, 0, 69), (0, 125, 50), (204, 246, 0), (148, 0, 45),
                  (241, 109, 149)),
                 ((195, 179, 0), (255, 239, 115), (255, 226, 0), (147, 3, 167), (24, 38, 176), (166, 147, 0),
                  (197, 97, 211)),
                 ((85, 0, 0), (155, 39, 102), (0, 201, 13), (255, 118, 0), (206, 0, 113), (0, 130, 9), (255, 180, 115)),
                 ((191, 159, 64), (183, 182, 208), (4, 31, 183), (167, 184, 45), (122, 128, 212), (37, 204, 7),
                  (88, 155, 213)),
                 ((200, 33, 205), (116, 252, 185), (68, 56, 56), (52, 238, 83), (23, 149, 195), (222, 157, 227),
                  (212, 86, 185)))
# 对颜色的处理
for i in range(len(COLOR_SCHEMES)):
    assert len(COLOR_SCHEMES[i]) == 7, '颜色方案 %s 没有7种颜色!.' % (i)
# 背景色,调色板设置默认色
bgColor = COLOR_SCHEMES[0][0]
paletteColors = COLOR_SCHEMES[0][1:]


def main():
    global FPS_CLOCK, DISPLAY_SURF, LOGO_IMAGE, SPOT_IMAGE, SETTINGS_IMAGE, SETTINGS_BUTTON_IMAGE, RESET_BUTTON_IMAGE

    pygame.init()
    FPS_CLOCK = pygame.time.Clock()
    DISPLAY_SURF = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))

    # 加载图片
    LOGO_IMAGE = pygame.image.load('inkspilllogo.png')
    SPOT_IMAGE = pygame.image.load('inkspillspot.png')
    SETTINGS_IMAGE = pygame.image.load('inkspillsettings.png')
    SETTINGS_BUTTON_IMAGE = pygame.image.load('inkspillsettingsbutton.png')
    RESET_BUTTON_IMAGE = pygame.image.load('inkspillresetbutton.png')

    # 设置窗口标题
    pygame.display.set_caption('墨水溢出')
    mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty)
    life = maxLife
    lastPaletteClicked = None

    while True:  # 主游戏循环
        paletteClicked = None
        resetGame = False

        # 画屏幕
        DISPLAY_SURF.fill(bgColor)
        # 加载图标和相关按钮
        drawLogoAndButtons()
        # 加载正中央的大格子并随机初始化每个小格子的颜色
        drawBoard(mainBoard)
        # 加载左侧的"生命计"
        drawLifeMeter(life)
        # 加载屏幕底部的六个调色板
        drawPalettes()
        # 判断玩家是否想要退出游戏
        checkForQuit()

        for event in pygame.event.get():  # 事件处理循环
            if event.type == MOUSEBUTTONUP:  # 如果事件是鼠标点击
                mousex, mousey = event.pos   # 获取鼠标点击的坐标
                # 如果点击的是"SETTINGS"字样处
                if pygame.Rect(WINDOW_WIDTH - SETTINGS_BUTTON_IMAGE.get_width(),
                               WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height(),
                               SETTINGS_BUTTON_IMAGE.get_width(),
                               SETTINGS_BUTTON_IMAGE.get_height()).collidepoint(mousex, mousey):
                    # 就展示设置界面
                    resetGame = showSettingsScreen()
                # 如果点击的是"RESET"字样处
                elif pygame.Rect(WINDOW_WIDTH - RESET_BUTTON_IMAGE.get_width(),
                                 WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height() - RESET_BUTTON_IMAGE.get_height(),
                                 RESET_BUTTON_IMAGE.get_width(),
                                 RESET_BUTTON_IMAGE.get_height()).collidepoint(mousex, mousey):
                    # 就重新开始游戏
                    resetGame = True
                else:
                    # 检查是否点击了调色板按钮
                    paletteClicked = getColorOfPaletteAt(mousex, mousey)

        if paletteClicked is not None and paletteClicked != lastPaletteClicked:

            # 单击的调色板按钮与上次单击的调色板按钮不同,防止播放器意外单击同一调色板两次
            lastPaletteClicked = paletteClicked

            # 填充颜色
            floodAnimation(mainBoard, paletteClicked)
            # 点击一次,"生命"减少1
            life -= 1

            resetGame = False
            if hasWon(mainBoard):  # 如果赢了
                for i in range(4):  # 成功的界面效果:闪烁边框4次
                    flashBorderAnimation(WHITE, mainBoard)
                # "闪光"结束后,重新开始游戏
                resetGame = True
                # 暂停2s后再开始游戏
                pygame.time.wait(2000)
            elif life == 0:
                # 生命降为零,玩家失败
                drawLifeMeter(0)
                # 更新界面
                pygame.display.update()
                # 等待0.4s
                pygame.time.wait(400)
                # 失败的结束效果:用黑色"闪光"4次
                for i in range(4):
                    flashBorderAnimation(BLACK, mainBoard)
                resetGame = True
                # 暂停2s后开始重新游戏
                pygame.time.wait(2000)

        if resetGame:
            # 重新开始游戏
            mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty)
            life = maxLife
            lastPaletteClicked = None
        # 更新界面
        pygame.display.update()
        # 控制帧率
        FPS_CLOCK.tick(FPS)


def checkForQuit():
    # 如果存在任何退出事件,则终止程序
    for event in pygame.event.get(QUIT):  # 获取所有退出事件
        pygame.quit()  # 如果存在任何退出事件,则终止
        sys.exit()
    for event in pygame.event.get(KEYUP):  # 获取所有KEYUP(按键按下)事件
        if event.key == K_ESCAPE:
            pygame.quit()  # 如果KEYUP(按键按下)事件是针对Esc键的,则终止
            sys.exit()
        pygame.event.post(event)  # 将其他KEYUP(按键按下)事件对象放回原处


def hasWon(board):
    # 如果整个棋盘颜色相同,则表示玩家获胜
    for x in range(boardWidth):
        for y in range(boardHeight):
            # 只要发现一个颜色与左上角的颜色不同,玩家还没有赢
            if board[x][y] != board[0][0]:
                return False
    return True


def showSettingsScreen():
    # 获取全局变量
    global difficulty, boxSize, boardWidth, boardHeight, maxLife, paletteColors, bgColor

    # 此函数中的像素坐标是通过将inkspillsettings.png图像加载到图形编辑器中并从中读取像素坐标获得的

    origDifficulty = difficulty
    origBoxSize = boxSize
    screenNeedsRedraw = True

    while True:
        if screenNeedsRedraw:
            DISPLAY_SURF.fill(bgColor)
            # 加载"设置"图片
            DISPLAY_SURF.blit(SETTINGS_IMAGE, (0, 0))

            # 将"墨迹"图片标记放在选定的颜色左边
            if difficulty == EASY:
                DISPLAY_SURF.blit(SPOT_IMAGE, (30, 4))
            if difficulty == MEDIUM:
                DISPLAY_SURF.blit(SPOT_IMAGE, (8, 41))
            if difficulty == HARD:
                DISPLAY_SURF.blit(SPOT_IMAGE, (30, 76))

            # 将墨迹标记放置在选定尺寸旁边
            if boxSize == SMALL_BOX_SIZE:
                DISPLAY_SURF.blit(SPOT_IMAGE, (22, 150))
            if boxSize == MEDIUM_BOX_SIZE:
                DISPLAY_SURF.blit(SPOT_IMAGE, (11, 185))
            if boxSize == LARGE_BOX_SIZE:
                DISPLAY_SURF.blit(SPOT_IMAGE, (24, 220))

            # 加载设置界面右边的颜色选择框
            for i in range(len(COLOR_SCHEMES)):
                drawColorSchemeBoxes(500, i * 60 + 30, i)

            # 更新界面
            pygame.display.update()

        screenNeedsRedraw = False  # 默认情况下,不重新绘制屏幕
        # 事件处理循环
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == KEYUP:
                if event.key == K_ESCAPE:
                    # 设置屏幕上的Esc键返回游戏
                    return not (origDifficulty == difficulty and origBoxSize == boxSize)
            elif event.type == MOUSEBUTTONUP:
                screenNeedsRedraw = True  # 屏幕应该重新绘制
                mousex, mousey = event.pos  # 鼠标点击的坐标

                # 检查难度按钮上是否有咔嗒声
                if pygame.Rect(74, 16, 111, 30).collidepoint(mousex, mousey):
                    difficulty = EASY
                elif pygame.Rect(53, 50, 104, 29).collidepoint(mousex, mousey):
                    difficulty = MEDIUM
                elif pygame.Rect(72, 85, 65, 31).collidepoint(mousex, mousey):
                    difficulty = HARD

                # 检查尺寸按钮上是否有点击
                elif pygame.Rect(63, 156, 84, 31).collidepoint(mousex, mousey):
                    # 小板尺寸设置:
                    boxSize = SMALL_BOX_SIZE
                    boardWidth = SMALL_BOARD_SIZE
                    boardHeight = SMALL_BOARD_SIZE
                    maxLife = SMALL_MAX_LIFE
                elif pygame.Rect(52, 192, 106, 32).collidepoint(mousex, mousey):
                    # 中板尺寸设置:
                    boxSize = MEDIUM_BOX_SIZE
                    boardWidth = MEDIUM_BOARD_SIZE
                    boardHeight = MEDIUM_BOARD_SIZE
                    maxLife = MEDIUM_MAX_LIFE
                elif pygame.Rect(67, 228, 58, 37).collidepoint(mousex, mousey):
                    # 大板尺寸设置:
                    boxSize = LARGE_BOX_SIZE
                    boardWidth = LARGE_BOARD_SIZE
                    boardHeight = LARGE_BOARD_SIZE
                    maxLife = LARGE_MAX_LIFE
                elif pygame.Rect(178, 418, 215, 34).collidepoint(mousex, mousey):
                    # 点击“Back To Game”按钮
                    return not (origDifficulty == difficulty and origBoxSize == boxSize)

                for i in range(len(COLOR_SCHEMES)):
                    # 单击颜色方案按钮
                    if pygame.Rect(500, 30 + i * 60, MEDIUM_BOX_SIZE * 3, MEDIUM_BOX_SIZE * 2).collidepoint(mousex,
                                                                                                            mousey):
                        bgColor = COLOR_SCHEMES[i][0]
                        paletteColors = COLOR_SCHEMES[i][1:]


def drawColorSchemeBoxes(x, y, schemeNum):
    # 绘制“设置”屏幕上显示的颜色方案框
    for boxy in range(2):
        for boxx in range(3):
            pygame.draw.rect(DISPLAY_SURF, COLOR_SCHEMES[schemeNum][3 * boxy + boxx + 1],
                             (x + MEDIUM_BOX_SIZE * boxx, y + MEDIUM_BOX_SIZE * boxy, MEDIUM_BOX_SIZE, MEDIUM_BOX_SIZE))
            if paletteColors == COLOR_SCHEMES[schemeNum][1:]:
                # 将"墨迹"图片放置在所选配色方案旁边
                DISPLAY_SURF.blit(SPOT_IMAGE, (x - 50, y))


def flashBorderAnimation(color, board, animationSpeed=30):
    # "闪光"效果结束后,需要回到原来(游戏刚结束)的界面,此处先保存原来的界面
    origSurf = DISPLAY_SURF.copy()
    flashSurf = pygame.Surface(DISPLAY_SURF.get_size())
    flashSurf = flashSurf.convert_alpha()
    # 实现"闪光"效果
    for start, end, step in ((0, 256, 1), (255, 0, -1)):
        # 外循环的第一次迭代将内循环的透明度设置为从0到255,第二次迭代将透明度设置为从255到0。这就是“闪光”。
        for transparency in range(start, end, animationSpeed * step):
            DISPLAY_SURF.blit(origSurf, (0, 0))
            r, g, b = color
            flashSurf.fill((r, g, b, transparency))
            DISPLAY_SURF.blit(flashSurf, (0, 0))
            drawBoard(board)  # 在透明层的顶部绘制板
            # 更新界面
            pygame.display.update()
            # 控制帧率
            FPS_CLOCK.tick(FPS)
    # 绘制原来(游戏刚结束)的界面
    DISPLAY_SURF.blit(origSurf, (0, 0))


def floodAnimation(board, paletteClicked, animationSpeed=25):
    origBoard = copy.deepcopy(board)  # 深拷贝整个大格子
    # 从左上角处的格子开始同色填充
    floodFill(board, board[0][0], paletteClicked, 0, 0)

    for transparency in range(0, 255, animationSpeed):
        # “新”大格子在大格子上慢慢变得不透明
        drawBoard(origBoard)
        # 更新透明度
        drawBoard(board, transparency)
        # 更新界面
        pygame.display.update()
        # 控制帧率
        FPS_CLOCK.tick(FPS)


def generateRandomBoard(width, height, difficulty=MEDIUM):
    # 为整个大格子中的每个小格子创建具有随机颜色的数据结构。
    board = []
    for x in range(width):
        column = []
        for y in range(height):
            column.append(random.randint(0, len(paletteColors) - 1))
        board.append(column)

    # 通过将一些小格子设置为与相邻格子相同的颜色,使解决整个大格子更容易。

    # 确定要更改的小格子数。
    if difficulty == EASY:
        if boxSize == SMALL_BOX_SIZE:
            boxesToChange = 100
        else:
            boxesToChange = 1500
    elif difficulty == MEDIUM:
        if boxSize == SMALL_BOX_SIZE:
            boxesToChange = 5
        else:
            boxesToChange = 200
    else:
        boxesToChange = 0

    # 挂邻居格子的颜色:
    for i in range(boxesToChange):
        # 随机选择要复制其颜色的框
        x = random.randint(1, width - 2)
        y = random.randint(1, height - 2)

        # 随机选择要更改的邻居格子
        direction = random.randint(0, 3)
        if direction == 0:  # 左,下
            board[x - 1][y] = board[x][y]
            board[x][y - 1] = board[x][y]
        elif direction == 1:  # 右,上
            board[x + 1][y] = board[x][y]
            board[x][y + 1] = board[x][y]
        elif direction == 2:  # 左,下
            board[x][y - 1] = board[x][y]
            board[x + 1][y] = board[x][y]
        else:  # 左,上
            board[x][y + 1] = board[x][y]
            board[x - 1][y] = board[x][y]
    return board


def drawLogoAndButtons():
    # 绘制墨水溢出徽标、设置和重置按钮。
    DISPLAY_SURF.blit(LOGO_IMAGE, (WINDOW_WIDTH - LOGO_IMAGE.get_width(), 0))
    DISPLAY_SURF.blit(SETTINGS_BUTTON_IMAGE,
                      (WINDOW_WIDTH - SETTINGS_BUTTON_IMAGE.get_width(),
                       WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height()))
    DISPLAY_SURF.blit(RESET_BUTTON_IMAGE, (WINDOW_WIDTH - RESET_BUTTON_IMAGE.get_width(),
                                           WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height() - RESET_BUTTON_IMAGE.get_height()))


def drawBoard(board, transparency=255):  # 透明度设置默认为255
    # 彩色方块将绘制到临时曲面,然后绘制到DISPLAY_SURF曲面。这样我们就可以在DISPLAY_SURF的顶部绘制具有透明度的正方形了。
    tempSurf = pygame.Surface(DISPLAY_SURF.get_size())  # 获取窗口尺寸
    tempSurf = tempSurf.convert_alpha()   # 支持透明
    tempSurf.fill((0, 0, 0, 0))   # 先全部处理为黑色

    for x in range(boardWidth):
        for y in range(boardHeight):
            left, top = leftTopPixelCoordOfBox(x, y)
            r, g, b = paletteColors[board[x][y]]
            pygame.draw.rect(tempSurf, (r, g, b, transparency), (left, top, boxSize, boxSize))
    left, top = leftTopPixelCoordOfBox(0, 0)
    # 为防止边缘的小格子颜色与窗口背景色相同,在整个大格子外面包一层黑色细线,厚度为1px
    pygame.draw.rect(tempSurf, BLACK, (left - 1, top - 1, boxSize * boardWidth + 1, boxSize * boardHeight + 1), 1)
    DISPLAY_SURF.blit(tempSurf, (0, 0))


def drawPalettes():
    # 在屏幕底部绘制六个调色板
    numColors = len(paletteColors)
    x_margin = int((WINDOW_WIDTH - ((PALETTE_SIZE * numColors) + (PALETTE_GAP_SIZE * (numColors - 1)))) / 2)
    for i in range(numColors):
        left = x_margin + (i * PALETTE_SIZE) + (i * PALETTE_GAP_SIZE)
        top = WINDOW_HEIGHT - PALETTE_SIZE - 10
        pygame.draw.rect(DISPLAY_SURF, paletteColors[i], (left, top, PALETTE_SIZE, PALETTE_SIZE))
        # 为了美观,在格子外2px再加2px厚度的对应颜色的区域
        pygame.draw.rect(DISPLAY_SURF, bgColor, (left + 2, top + 2, PALETTE_SIZE - 4, PALETTE_SIZE - 4), 2)


def drawLifeMeter(currentLife):
    # '生命计' 竖直放置,与上下边缘各相距20px
    lifeBoxSize = int((WINDOW_HEIGHT - 40) / maxLife)

    # 绘制"生命计"的背景色,'生命计'的左上角位于坐标(20,20),宽20px,高20 + (maxLife * lifeBoxSize) px
    pygame.draw.rect(DISPLAY_SURF, bgColor, (20, 20, 20, 20 + (maxLife * lifeBoxSize)))

    for i in range(maxLife):
        if currentLife >= (maxLife - i):  # 画一个实心的红色方框
            pygame.draw.rect(DISPLAY_SURF, RED, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize))
        # 加1px白色的边框
        pygame.draw.rect(DISPLAY_SURF, WHITE, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize), 1)


def getColorOfPaletteAt(x, y):
    # 返回x和y参数覆盖的调色板颜色的索引
    numColors = len(paletteColors)
    xmargin = int((WINDOW_WIDTH - ((PALETTE_SIZE * numColors) + (PALETTE_GAP_SIZE * (numColors - 1)))) / 2)
    # 调色板底端距窗口底端10px
    top = WINDOW_HEIGHT - PALETTE_SIZE - 10
    for i in range(numColors):
        # 六个调色板左上角的坐标:(left,top)
        left = xmargin + (i * PALETTE_SIZE) + (i * PALETTE_GAP_SIZE)
        r = pygame.Rect(left, top, PALETTE_SIZE, PALETTE_SIZE)
        # 找出鼠标单击区域是否在任何调色板内
        if r.collidepoint(x, y):
            return i  # 如果在,则返回所点击的调色板的序号
    # 如果x和y不在任何调色板上,则返回None
    return None


def floodFill(board, oldColor, newColor, x, y):
    # 洪水填充算法
    if oldColor == newColor or board[x][y] != oldColor:
        # 如果左上角的颜色和点击颜色没有相邻,直接返回
        return
    board[x][y] = newColor  # 更改当前格子的颜色
    # 对任何相邻格子进行递归调用:
    if x > 0:
        floodFill(board, oldColor, newColor, x - 1, y)
    if x < boardWidth - 1:
        floodFill(board, oldColor, newColor, x + 1, y)
    if y > 0:
        floodFill(board, oldColor, newColor, x, y - 1)
    if y < boardHeight - 1:
        floodFill(board, oldColor, newColor, x, y + 1)


def leftTopPixelCoordOfBox(boxx, boxy):
    # 返回某个小格子最左上方像素的x和y。
    # 注意整个大格子位于窗口的正中央
    x_margin = int((WINDOW_WIDTH - (boardWidth * boxSize)) / 2)  # x_margin 为整个大格子最左边与窗口最左边的间距
    y_margin = int((WINDOW_HEIGHT - (boardHeight * boxSize)) / 2)  # y_margin 为整个大格子最上边与窗口最上边的间距
    return boxx * boxSize + x_margin, boxy * boxSize + y_margin


if __name__ == '__main__':
    main()

         喜欢的小伙伴们点个赞鼓励支持一下吧~

        

  • 15
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

易果啥笔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值