强化学习Agent系列(一)——PyGame游戏编程,Python 贪吃蛇制作实战教学

简介: 本文是关于使用Pygame库开发Python贪吃蛇游戏的实战教学,介绍了Pygame的基本使用、窗口初始化、事件处理、键盘控制移动、以及实现游戏逻辑和对象交互的方法。

强化学习Agent系列(一)——PyGame游戏编程,Python 贪吃蛇制作实战教学

文章目录

  • 强化学习Agent系列(一)——PyGame游戏编程,Python 贪吃蛇制作实战教学
  • 一、前言
    • 1、pygame介绍
    • 2、 安装Pygame
    • 3. Pygame常用模块
  • 二、pygame 入门
    • 1、窗口初始化与事件初认识
    • 2、创建绿色方块并键盘移动
    • 3、控制绿色方块吃掉红色果子
    • 4、控制绿色方块吃掉红色果子,身体长度加一节
  • 三、pygame初级

一、前言

大家好,未来的开发者们请上座
随着人工智能的发展,强化学习基本会再次来到人们眼前,遂想制作一下相关的教程。强化学习第一步基本离不开虚拟环境的搭建,下面用大家耳熟能详的贪吃蛇游戏为基础,制作一个Agent,完成对这个游戏的绝杀。
万里长城第一步:用python开发贪吃蛇游戏

1、pygame介绍

用Python进行游戏开发的首选模块就是PyGame。
pygame 是一个流行的跨平台Python模块,专为电子游戏设计,包含图像、声音等,创建在SDL(Simple DirectMedia Layer)基础上,允许实时电子游戏研发而不会被低级语言,如C语言或是更低级的汇编语言束缚。基于这样一个设想,所有需要的游戏功能和理念(主要是图像方面)都完全简化为游戏逻辑本身,所有的资源结构都可以由高级语言(如Python)提供。它提供了一套丰富的功能,使得游戏开发者可以轻松地创建图形、动画、音效和游戏逻辑。

以下是 pygame 的一些关键特性
注:不需要特地去记,一开始只需要知道pygame有哪些关键特性,哪些功能他可以实现即可。

  1. 显示
    pygame 提供了一种简单的方式来创建和管理游戏窗口以及渲染图形。
    它支持多种图像格式,包括 BMP、JPG、PNG、GIF 等。
  2. 事件
    pygame 有一个事件系统,可以处理键盘、鼠标、游戏手柄等输入设备的事件。
    开发者可以轻松地查询和响应用户的交互。
  3. 图形
    pygame 提供了基本的2D图形绘制功能,包括线条、形状、图像渲染等。
    它也支持图像的变换,如缩放、旋转和翻转。
  4. 音效和音乐
    pygame 可以播放音效和音乐文件,支持多种音频格式。
    它提供了音量控制、播放控制等基本的音频操作。
  5. 精灵
    pygame 的精灵(Sprite)系统可以帮助管理游戏中的对象。
    精灵类可以用来表示游戏中的每一个元素,如角色、物品等。
  6. 时钟
    pygame 的时钟(Clock)功能可以帮助控制游戏的帧率,确保游戏以均匀的速度运行。
  7. 碰撞检测
    pygame 提供了简单的碰撞检测功能,可以检测两个元素是否相互接触或重叠。
  8. 字体和文本
    pygame 支持字体渲染,可以在游戏中显示文本。
  9. 跨平台
    pygame 可以运行在多种操作系统上,包括 Windows、Mac OS X 和 Linux。
    安装 pygame 通常很简单,只需使用 pip 命令即可:

2、 安装Pygame

pip install pygame
AI 代码解读

一旦安装了 pygame,你就可以开始创建游戏窗口、加载图像、处理用户输入和创建游戏逻辑了。pygame 的社区活跃,有大量的教程和文档可供学习,这使得即使是编程新手也能够开始他们的游戏开发之旅。

3. Pygame常用模块

Pygame做游戏开发的优势在于不需要过多地考虑底层相关的内容,可以把工作中心放在游戏逻辑上。例如,PyGame中集成了很多和底层相关的模块,如访问显示设备、管理事件、使用字体等。

Pygame常用模块如下:

模块名 功能
pygame.display 访问显示设备。
pygame.draw 绘制形状、线和点。
pygame.event 管理事件。
pygame.font 使用字体。
pygame.image 加载和存储图片。
pygame.key 读取键盘按键。
pygame.mixer 声音。
pygame.mouse 鼠标。
pygame.movie 播放视频。
pygame.music 播放音乐。
pygame.rect 管理矩形区域。
pygame.sndarray 操作声音数据。
pygame.sprite 操作移动图像。
pygame.surface 管理图像和屏幕。
pygame.surfarray 管理点阵图像数据。
pygame.time 管理时间和帧信息。
pygame.transform 缩放和移动图像。

二、pygame 入门

1、窗口初始化与事件初认识

使用Pygame的display模块和event模块创建一个PyGame窗口,代码结构主要分三部分:

  1. 模块导入
  2. pygame窗口初始化
  3. 游戏主体

初始化涉及到的pygame与sys函数:

模块名 功能
pygame.init() 初始化所有的pygame模块,使用其他模块之前基本一定要调用init方法
pygame.display.set_mode() 初始化窗口的宽高,并返回游戏屏幕(后续对象都需要画到游戏屏幕上)
pygame.display.set_caption() 初始化窗口的名字
pygame.event.get() 键盘、鼠标、手柄 事件响应
pygame.QUIT 点击窗口右上角红X的事件
sys.exit() 程序退出
pygame.quit() 程序终止前调用,卸载所有pygame模块
# -*- coding: utf-8 -*-
# 一、模块导入
import sys         # 导入sys模块
import pygame       # 导入pygame模块

# 二、pygame窗口初始化
pygame.init()                           # 初始化pygame
size = width, height = 320, 240         # 设置窗口尺寸
screen = pygame.display.set_mode(size)  # 显示窗口
# 设置标题
pygame.display.set_caption('窗口初始化测试')

# 三、游戏主体
# 执行死循环,确保窗口一直显示,同时监听关闭按钮事件
while True:
    # 检查事件
    for event in pygame.event.get():    # 遍历所有事件
        if event.type == pygame.QUIT:     # 如果单击关闭按钮
            pygame.quit()       # 卸载所有pygame模块
            sys.exit()          # 终止当前程序
AI 代码解读

运行结果如下:
在这里插入图片描述
这样子你就创建了一个窗口,点击关闭按钮,程序捕捉到pygame.QUIT事件,pygame.quit()卸载完pygame模块后,运行 sys.exit() 退出程序。

这样子就开始了基于pygame制作游戏的第一步。窗口初始化

2、创建绿色方块并键盘移动

涉及到游戏基本就离不开控制角色移动,遂就将其当做第二课
运行结果如下:
在这里插入图片描述

该课程涉及到的pygame函数(不重复上一课学习到的了):

模块名 功能
pygame.time.Clock() 创建时钟对象,用于控制屏幕绘制速度 – 刷新频率
clock.tick(60) 时钟对象(一定要放在整个游戏循环最后,不然会起不到稳定控制游戏刷新速度的效果),设置频率为60。如果你的游戏逻辑和渲染非常快,tick 方法将会使程序暂停,以保持 60 FPS 的速度,如果速度慢于60则不会等待
pygame.key.get_pressed() 获取当前全部键盘key的状态,没按下的按钮值为False,按下的按钮值为True
screen.fill() 快捷填充背景色
pygame.draw.rect() 绘制矩阵(游戏屏幕,颜色,矩阵(x,y,width,height),width(矩阵粗细,没有则填充整个矩阵))
pygame.display.update() 更新屏幕画面

注:额外知识点补充游戏中的坐标系:

在这里插入图片描述
具体的逻辑代码如下
使用Pygame的创建绿色方块并键盘移动,代码结构主要分三部分:

  1. 模块导入
  2. pygame窗口初始化
  3. 游戏主体
    3. 1 游戏内对象初始化
    3. 2 监听按键、鼠标事件并设置事件内容,修改方块位置
    3. 3 依据方块的位置宽高,重新渲染画面和方块
# -*- coding: utf-8 -*-
# 一、模块导入
import sys         # 导入sys模块
import pygame       # 导入pygame模块

# 二、pygame窗口初始化
pygame.init()                           # 初始化pygame
size = width, height = 320, 240         # 设置窗口尺寸
screen = pygame.display.set_mode(size)  # 显示窗口
# 设置标题
pygame.display.set_caption('创建绿色方块并键盘移动')

# 三、游戏主体
# 执行死循环,确保窗口一直显示,同时监听关闭按钮事件

## 3.1 游戏内对象初始化
# 设置矩阵的属性
rect_color = (0, 255, 0)  # 绿色
rect_len = 20  # 矩阵的边长
rect_pos = [width // 2, height // 2]  # 矩阵的初始位置
rect_speed = [2, 2]  # 矩阵的速度

# 定义时钟对象
clock = pygame.time.Clock()

while True:
    # 3.2 监听按键、鼠标事件并设置事件内容,修改方块的值
    # 检查事件
    for event in pygame.event.get():    # 遍历所有事件
        if event.type == pygame.QUIT:     # 如果单击关闭按钮
            pygame.quit()       # 卸载所有pygame模块
            sys.exit()          # 终止当前程序

    # 检查按键,并对上下左右每一个按键,绑定事件
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        rect_pos[0] -= rect_speed[0]
    if keys[pygame.K_RIGHT]:
        rect_pos[0] += rect_speed[0]
    if keys[pygame.K_UP]:
        rect_pos[1] -= rect_speed[1]
    if keys[pygame.K_DOWN]:
        rect_pos[1] += rect_speed[1]

    # 3.3 依据方块的位置宽高,重新渲染画面和方块
    #确保方块不会移出屏幕
    rect_pos[0]=max(0,min(width-rect_len,rect_pos[0]))
    rect_pos[1]=max(0,min(height-rect_len,rect_pos[1]))

    # 填充背景色(每次用黑色的背景覆盖掉旧有的画面)
    screen.fill((0,0,0))

    #画矩阵
    pygame.draw.rect(screen,rect_color,(*rect_pos,rect_len,rect_len))

    # 更新屏幕
    pygame.display.update()

    # 控制游戏刷新速度
    clock.tick(60)
AI 代码解读

上述代码中,添加了键盘事件检测。pygame.key.get_pressed()能够获取键盘状态,查询目标按钮的状态并添加相应的事件,如:

    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        rect_pos[0] -= rect_speed[0]
    if keys[pygame.K_RIGHT]:
        rect_pos[0] += rect_speed[0]
    if keys[pygame.K_UP]:
        rect_pos[1] -= rect_speed[1]
    if keys[pygame.K_DOWN]:
        rect_pos[1] += rect_speed[1]
AI 代码解读

如果按下left,则rect的x值减少,按下right,则rect的x值增加。如果按下up,则y值减少,按下down,y值增加。具体对应关系可参考上面的pygame中的游戏坐标系进行理解

3、控制绿色方块吃掉红色果子

游戏基本一定会涉及到游戏内物体的交互,一个游戏的玩法核心就是交互,在设计游戏玩法的时候,各位帅哥美女们可以参考下面,设计一下交互规则。

创建交互规则如下:

  • 1、玩家控制绿色方块移动
  • 2、如果地图中没有红色果子,在地图任意位置,随机生成一个果子
  • 3、绿色方块的位置与红色果子位置一致时,红色果子刷新位置

具体的逻辑代码如下
初始化随机生成一个果子的位置,并渲染出来
当二者左上角的位置距离<8 px的时候,果子刷新位置

请添加图片描述
该课程涉及到的pygame函数(不重复上一课学习到的了):

模块名 功能
random.randint(0, 30) 随机从0到30这31个数抽一个数
np.linalg.norm(3,4)= 5 求一个向量的长度,与sqrt(pow(x, 2) + pow(y, 2))等价
*迭代对象(比如程序中的:*foodPosition) 列表解包,比如:将foodPosition拆成零散的变量,而不是之前的元组

额外补充知识:
在这里插入图片描述

具体代码如下:

# -*- coding: utf-8 -*-
# 一、模块导入
import sys  # 导入sys模块
import pygame  # 导入pygame模块
import random  # 导入random模块
import numpy as np
# 二、pygame窗口初始化
pygame.init()  # 初始化pygame
size = width, height = 320, 240  # 设置窗口尺寸
screen = pygame.display.set_mode(size)  # 显示窗口
# 设置标题
pygame.display.set_caption('3、控制绿色方块吃掉红色果子')

# 三、游戏主体
# 执行死循环,确保窗口一直显示,同时监听关闭按钮事件

## 3.1 游戏内对象初始化
# 设置矩阵的属性
rect_color = (0, 255, 0)  # 绿色
rect_len = 20  # 矩阵的边长
rect_pos = [width // 2, height // 2]  # 矩阵的初始位置
rect_speed = [2, 2]  # 矩阵的速度

# 设置果子的初始属性
food_color = (255,0,0 )  # 果子颜色:红色
food_len = 15           # 果子大小:15像素
foodPosition = (random.randint(0, width - 30) , random.randint(0, height - 30) ) # 果子的初始位置

# 定义时钟对象
clock = pygame.time.Clock()

while True:
    # 3.2 监听按键、鼠标事件并设置事件内容,修改方块的值
    # 检查事件
    for event in pygame.event.get():  # 遍历所有事件
        if event.type == pygame.QUIT:  # 如果单击关闭按钮
            pygame.quit()  # 卸载所有pygame模块
            sys.exit()  # 终止当前程序

    # 检查按键,并对上下左右每一个按键,绑定事件
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        rect_pos[0] -= rect_speed[0]
    if keys[pygame.K_RIGHT]:
        rect_pos[0] += rect_speed[0]
    if keys[pygame.K_UP]:
        rect_pos[1] -= rect_speed[1]
    if keys[pygame.K_DOWN]:
        rect_pos[1] += rect_speed[1]

    if np.linalg.norm((rect_pos[0]-foodPosition[0],rect_pos[1]-foodPosition[1]))<8:
        foodPosition = (random.randint(0, width - 30) , random.randint(0, height - 30) ) # 果子的初始位置
    # 3.3 依据方块的位置宽高,重新渲染画面和方块
    # 确保方块不会移出屏幕
    rect_pos[0] = max(0, min(width - rect_len, rect_pos[0]))
    rect_pos[1] = max(0, min(height - rect_len, rect_pos[1]))

    # 填充背景色(每次用黑色的背景覆盖掉旧有的画面)
    screen.fill((0, 0, 0))

    # 画果子
    pygame.draw.rect(screen, food_color, (*foodPosition, food_len, food_len))

    # 画矩阵
    pygame.draw.rect(screen, rect_color, (*rect_pos, rect_len, rect_len))

    # 更新屏幕
    pygame.display.update()

    # 控制游戏刷新速度
    clock.tick(60)
AI 代码解读

4、控制绿色方块吃掉红色果子,身体长度加一节

定义游戏名字:贪吃蛇
创建交互规则如下:

  • 1、玩家控制绿色蛇移动
  • 2、如果地图中没有红色果子,在地图任意位置,随机生成一个果子
  • 3、绿色方块的位置与红色果子位置一致时,蛇身长度长一节
  • 4、绿色方块的位置与红色果子位置一致时,红色果子刷新位置

翻译成代码逻辑:

初始化随机生成一个果子的位置,并渲染出来
蛇吃果子代码逻辑:当二者重叠超过百分之七十时候,果子刷新位置
蛇移动代码原理:每次在监听键盘,如果有移动操作导致头部发生改变,那么在新的位置绘制一个矩形,并插入蛇的身体列表开头做为新的头部。
如果这一步没有吃到果子则弹出尾巴,有就不弹出。
在这里插入图片描述

该课程涉及到的pygame函数(不重复上一课学习到的了):

模块名 功能
pygame.Rect(x,y,width,height) 生成pygame中的Rect对象(后面碰撞检测会用到)
rect1.clip(rect2) 求两个Rect对象的重叠矩阵

代码逻辑如下:

# -*- coding: utf-8 -*-
# 一、模块导入
import sys  # 导入sys模块
import pygame  # 导入pygame模块
import random  # 导入random模块
import numpy as np

# 二、pygame窗口初始化
pygame.init()  # 初始化pygame
size = width, height = 320, 240  # 设置窗口尺寸
screen = pygame.display.set_mode(size)  # 显示窗口
# 设置标题
pygame.display.set_caption('3、控制绿色方块吃掉红色果子')

# 三、游戏主体
# 执行死循环,确保窗口一直显示,同时监听关闭按钮事件

## 3.1 游戏内对象初始化
# 设置矩阵的属性
rect_color = (0, 255, 0)  # 绿色
rect_len = 20  # 矩阵的边长
snake_len = 3
body0 = pygame.Rect(width // 2, height // 2, rect_len, rect_len)  # 矩形
rect_snake_body = [body0]  # 矩阵的初始位置

rect_speed = [2, 2]  # 矩阵的速度

# 设置果子的初始属性
food_color = (255, 0, 0)  # 果子颜色:红色
food_len = 15  # 果子大小:15像素
foodPosition = (random.randint(0, width - 30), random.randint(0, height - 30))  # 果子的初始位置
food_body = pygame.Rect(*foodPosition, food_len, food_len)

# 定义时钟对象
clock = pygame.time.Clock()

while True:
    # 3.2 监听按键、鼠标事件并设置事件内容,修改方块的值
    # 检查事件
    for event in pygame.event.get():  # 遍历所有事件
        if event.type == pygame.QUIT:  # 如果单击关闭按钮
            pygame.quit()  # 卸载所有pygame模块
            sys.exit()  # 终止当前程序

    rect_pos = [rect_snake_body[0].x, rect_snake_body[0].y]
    # 检查按键,并对上下左右每一个按键,绑定事件
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        rect_pos[0] -= rect_speed[0]
    if keys[pygame.K_RIGHT]:
        rect_pos[0] += rect_speed[0]
    if keys[pygame.K_UP]:
        rect_pos[1] -= rect_speed[1]
    if keys[pygame.K_DOWN]:
        rect_pos[1] += rect_speed[1]

    # 3.3 根据上一步的数值变化进行,二次处理处理功能:1、不会移出屏幕。2、吃掉果子。3、蛇身移动
    # 1、确保蛇不会移出屏幕
    rect_pos[0] = max(0, min(width - rect_len, rect_pos[0]))
    rect_pos[1] = max(0, min(height - rect_len, rect_pos[1]))

    # 2、吃掉果子逻辑
    overRect = food_body.clip(rect_snake_body[0])
    if overRect.width * overRect.height >= food_body.width * food_body.height * 0.7:
        foodPosition = food_body.x, food_body.y = random.randint(0, width - 30), random.randint(0,height - 30)  # 果子的随机位置
        snake_len += 1  # 蛇的身长加1
    # 3、移动
    if rect_pos[0] != rect_snake_body[0].x or rect_pos[1] != rect_snake_body[0].y:
        newHead = pygame.Rect(*rect_pos, rect_len, rect_len)
        rect_snake_body.insert(0, newHead)
        if len(rect_snake_body) >= snake_len:
            rect_snake_body.pop()

    # 3.4 渲染
    # 填充背景色(每次用黑色的背景覆盖掉旧有的画面)
    screen.fill((0, 0, 0))
    # 画果子
    pygame.draw.rect(screen, food_color, food_body)
    # 倒序画蛇身
    for i in range(len(rect_snake_body)):
        # 画矩阵
        p=rect_snake_body[len(rect_snake_body)-i-1]
        pygame.draw.rect(screen, rect_color, p)
        pygame.draw.rect(screen, (200,200,200), p,1)
    # 更新屏幕
    pygame.display.update()

    # 控制游戏刷新速度
    clock.tick(60)
AI 代码解读

三、pygame初级

经过前面的学习,想必各位帅哥美女对怎么用pygame写交互逻辑有了一些初步的认知。现在让我们学习一下:1、图片导入。2、文本。3、游戏结束和重新开始。4、空格游戏暂停

初级的博客在个人博客里可以搜到,后续看反响,如果还行整理成视频在b站发布。

中级的话就是工程开发,将以上的全部,按照工程标准分多个文件开发。后面也会提到

目录
打赏
0
1
1
0
52
分享
相关文章
探索Python编程:从基础到高级
在这篇文章中,我们将一起深入探索Python编程的世界。无论你是初学者还是有经验的程序员,都可以从中获得新的知识和技能。我们将从Python的基础语法开始,然后逐步过渡到更复杂的主题,如面向对象编程、异常处理和模块使用。最后,我们将通过一些实际的代码示例,来展示如何应用这些知识解决实际问题。让我们一起开启Python编程的旅程吧!
Python编程入门:从零基础到实战应用
本文是一篇面向初学者的Python编程教程,旨在帮助读者从零开始学习Python编程语言。文章首先介绍了Python的基本概念和特点,然后通过一个简单的例子展示了如何编写Python代码。接下来,文章详细介绍了Python的数据类型、变量、运算符、控制结构、函数等基本语法知识。最后,文章通过一个实战项目——制作一个简单的计算器程序,帮助读者巩固所学知识并提高编程技能。
[oeasy]python053_学编程为什么从hello_world_开始
视频介绍了“Hello World”程序的由来及其在编程中的重要性。从贝尔实验室诞生的Unix系统和C语言说起,讲述了“Hello World”作为经典示例的起源和流传过程。文章还探讨了C语言对其他编程语言的影响,以及它在系统编程中的地位。最后总结了“Hello World”、print、小括号和双引号等编程概念的来源。
107 80
Python编程数据结构的深入理解
深入理解 Python 中的数据结构是提高编程能力的重要途径。通过合理选择和使用数据结构,可以提高程序的效率和质量
158 59
|
12天前
|
[oeasy]python055_python编程_容易出现的问题_函数名的重新赋值_print_int
本文介绍了Python编程中容易出现的问题,特别是函数名、类名和模块名的重新赋值。通过具体示例展示了将内建函数(如`print`、`int`、`max`)或模块名(如`os`)重新赋值为其他类型后,会导致原有功能失效。例如,将`print`赋值为整数后,无法再用其输出内容;将`int`赋值为整数后,无法再进行类型转换。重新赋值后,这些名称失去了原有的功能,可能导致程序错误。总结指出,已有的函数名、类名和模块名不适合覆盖赋新值,否则会失去原有功能。如果需要使用类似的变量名,建议采用其他命名方式以避免冲突。
34 14
基于agentscope的多智能体游戏场景-骗子酒馆
骗子酒馆是一款基于多智能体系统的在线社交推理游戏,玩家通过掷骰子和扑克牌进行智力和心理博弈,结合大语言模型技术,每个游戏角色由AI扮演,具备独特的性格和决策逻辑,提供高度沉浸式的体验。游戏采用黑板通信模式,确保信息高效交换,支持多种角色如胆小鬼、占卜师等,每个角色拥有特定的技能和行为模式,增强游戏的策略深度和互动性。游戏界面简洁,操作流畅,适合喜欢心理战和策略游戏的玩家。文章末尾有源码和体验地址。
139 13
技术评测:MaxCompute MaxFrame——阿里云自研分布式计算框架的Python编程接口
随着大数据和人工智能技术的发展,数据处理的需求日益增长。阿里云推出的MaxCompute MaxFrame(简称“MaxFrame”)是一个专为Python开发者设计的分布式计算框架,它不仅支持Python编程接口,还能直接利用MaxCompute的云原生大数据计算资源和服务。本文将通过一系列最佳实践测评,探讨MaxFrame在分布式Pandas处理以及大语言模型数据处理场景中的表现,并分析其在实际工作中的应用潜力。
58 2
探索Python编程:从基础到实战
本文将引导你走进Python编程的世界,从基础语法开始,逐步深入到实战项目。我们将一起探讨如何在编程中发挥创意,解决问题,并分享一些实用的技巧和心得。无论你是编程新手还是有一定经验的开发者,这篇文章都将为你提供有价值的参考。让我们一起开启Python编程的探索之旅吧!
50 10
Python 语言:强大、灵活与高效的编程之选
本文全面介绍了 Python 编程语言,涵盖其历史、特点、应用领域及核心概念。从 1989 年由 Guido van Rossum 创立至今,Python 凭借简洁的语法和强大的功能,成为数据科学、AI、Web 开发等领域的首选语言。文章还详细探讨了 Python 的语法基础、数据结构、面向对象编程等内容,旨在帮助读者深入了解并有效利用 Python 进行编程。
探索Python编程的奥秘
在数字世界的海洋中,Python如同一艘灵活的帆船,引领着无数探险者穿梭于数据的波涛之中。本文将带你领略Python编程的魅力,从基础语法到实际应用,一步步揭开Python的神秘面纱。
45 12