俄罗斯方块游戏代码

news2024/11/29 6:35:00

♥️作者:小刘在C站

♥️个人主页:小刘主页

♥️每天分享云计算网络运维课堂笔记,努力不一定有收获,但一定会有收获加油!一起努力,共赴美好人生!

♥️夕阳下,是最美的,绽放,愿所有的美好,再疫情结束后如约而至。

目录

一.什么是python

二.游戏代码效果呈现

三.游戏主代码二

1.shapes

2.misc

3.gameboard

4.__init__

5.README


一.什么是python

Python由荷兰数学和计算机科学研究学会的吉多·范罗苏姆于1990年代初设计,作为一门叫做ABC语言的替代品。   Python提供了高效的高级数据结构,还能简单有效地面向对象编程。Python语法和动态类型,以及解释型语言的本质,使它成为多数平台上写脚本和快速开发应用的编程语言, [ 随着版本的不断更新和语言新功能的添加,逐渐被用于独立的、大型项目的开发。

Python解释器易于扩展,可以使用C语言或C++(或者其他可以通过C调用的语言)扩展新的功能和数据类型。Python也可用于可定制化软件中的扩展程序语言。Python丰富的标准库,提供了适用于各个主要系统平台的源码或机器码

二.游戏代码效果呈现

 三.游戏主代码

'''

'''
import os
import sys
import random
from modules import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *


'''定义俄罗斯方块游戏类'''
class TetrisGame(QMainWindow):
    def __init__(self, parent=None):
        super(TetrisGame, self).__init__(parent)
        # 是否暂停ing
        self.is_paused = False
        # 是否开始ing
        self.is_started = False
        self.initUI()
    '''界面初始化'''
    def initUI(self):
        # icon
        self.setWindowIcon(QIcon(os.path.join(os.getcwd(), 'resources/icon.jpg')))
        # 块大小
        self.grid_size = 22
        # 游戏帧率
        self.fps = 200
        self.timer = QBasicTimer()
        # 焦点
        self.setFocusPolicy(Qt.StrongFocus)
        # 水平布局
        layout_horizontal = QHBoxLayout()
        self.inner_board = InnerBoard()
        self.external_board = ExternalBoard(self, self.grid_size, self.inner_board)
        layout_horizontal.addWidget(self.external_board)
        self.side_panel = SidePanel(self, self.grid_size, self.inner_board)
        layout_horizontal.addWidget(self.side_panel)
        self.status_bar = self.statusBar()
        self.external_board.score_signal[str].connect(self.status_bar.showMessage)
        self.start()
        self.center()
        self.setWindowTitle('俄罗斯方块儿 —— 源码基地:959755565')
        self.show()
        self.setFixedSize(self.external_board.width() + self.side_panel.width(), self.side_panel.height() + self.status_bar.height())
    '''游戏界面移动到屏幕中间'''
    def center(self):
        screen = QDesktopWidget().screenGeometry()
        size = self.geometry()
        self.move((screen.width() - size.width()) // 2, (screen.height() - size.height()) // 2)
    '''更新界面'''
    def updateWindow(self):
        self.external_board.updateData()
        self.side_panel.updateData()
        self.update()
    '''开始'''
    def start(self):
        if self.is_started:
            return
        self.is_started = True
        self.inner_board.createNewTetris()
        self.timer.start(self.fps, self)
    '''暂停/不暂停'''
    def pause(self):
        if not self.is_started:
            return
        self.is_paused = not self.is_paused
        if self.is_paused:
            self.timer.stop()
            self.external_board.score_signal.emit('Paused')
        else:
            self.timer.start(self.fps, self)
        self.updateWindow()
    '''计时器事件'''
    def timerEvent(self, event):
        if event.timerId() == self.timer.timerId():
            removed_lines = self.inner_board.moveDown()
            self.external_board.score += removed_lines
            self.updateWindow()
        else:
            super(TetrisGame, self).timerEvent(event)
    '''按键事件'''
    def keyPressEvent(self, event):
        if not self.is_started or self.inner_board.current_tetris == tetrisShape().shape_empty:
            super(TetrisGame, self).keyPressEvent(event)
            return
        key = event.key()
        # P键暂停
        if key == Qt.Key_P:
            self.pause()
            return
        if self.is_paused:
            return
        # 向左
        elif key == Qt.Key_Left:
            self.inner_board.moveLeft()
        # 向右
        elif key == Qt.Key_Right:
            self.inner_board.moveRight()
        # 旋转
        elif key == Qt.Key_Up:
            self.inner_board.rotateAnticlockwise()
        # 快速坠落
        elif key == Qt.Key_Space:
            self.external_board.score += self.inner_board.dropDown()
        else:
            super(TetrisGame, self).keyPressEvent(event)
        self.updateWindow()


'''run'''
if __name__ == '__main__':
    app = QApplication([])
    tetris = TetrisGame()
    sys.exit(app.exec_())

三.游戏主代码二

1.shapes

'''
Function:
    定义俄罗斯方块的形状

'''
'''定义一个俄罗斯方块的形状'''
class tetrisShape():
    def __init__(self, shape=0):
        # 空块
        self.shape_empty = 0
        # 一字型块
        self.shape_I = 1
        # L型块
        self.shape_L = 2
        # 向左的L型块
        self.shape_J = 3
        # T型块
        self.shape_T = 4
        # 田字型块
        self.shape_O = 5
        # 反向Z型块
        self.shape_S = 6
        # Z型块
        self.shape_Z = 7
        # 每种块包含的四个小方块相对坐标分布
        self.shapes_relative_coords = [
            [[0, 0], [0, 0], [0, 0], [0, 0]],
            [[0, -1], [0, 0], [0, 1], [0, 2]],
            [[0, -1], [0, 0], [0, 1], [1, 1]],
            [[0, -1], [0, 0], [0, 1], [-1, 1]],
            [[0, -1], [0, 0], [0, 1], [1, 0]],
            [[0, 0], [0, -1], [1, 0], [1, -1]],
            [[0, 0], [0, -1], [-1, 0], [1, -1]],
            [[0, 0], [0, -1], [1, 0], [-1, -1]]
        ]
        self.shape = shape
        self.relative_coords = self.shapes_relative_coords[self.shape]
    '''获得该形状当前旋转状态的四个小方块的相对坐标分布'''
    def getRotatedRelativeCoords(self, direction):
        # 初始分布
        if direction == 0 or self.shape == self.shape_O:
            return self.relative_coords
        # 逆时针旋转90度
        if direction == 1:
            return [[-y, x] for x, y in self.relative_coords]
        # 逆时针旋转180度
        if direction == 2:
            if self.shape in [self.shape_I, self.shape_Z, self.shape_S]:
                return self.relative_coords
            else:
                return [[-x, -y] for x, y in self.relative_coords]
        # 逆时针旋转270度
        if direction == 3:
            if self.shape in [self.shape_I, self.shape_Z, self.shape_S]:
                return [[-y, x] for x, y in self.relative_coords]
            else:
                return [[y, -x] for x, y in self.relative_coords]
    '''获得该俄罗斯方块的各个小块绝对坐标'''
    def getAbsoluteCoords(self, direction, x, y):
        return [[x + i, y + j] for i, j in self.getRotatedRelativeCoords(direction)]
    '''获得相对坐标的边界'''
    def getRelativeBoundary(self, direction):
        relative_coords_now = self.getRotatedRelativeCoords(direction)
        xs = [i[0] for i in relative_coords_now]
        ys = [i[1] for i in relative_coords_now]
        return min(xs), max(xs), min(ys), max(ys)

2.misc

'''
Function:
    其他工具函数

'''
from PyQt5.QtGui import *


'''给板块的一个Cell填色'''
def drawCell(painter, x, y, shape, grid_size):
    colors = [0x000000, 0xCC6666, 0x66CC66, 0x6666CC, 0xCCCC66, 0xCC66CC, 0x66CCCC, 0xDAAA00]
    if shape == 0:
        return
    color = QColor(colors[shape])
    painter.fillRect(x + 1, y + 1, grid_size - 2, grid_size - 2, color)
    painter.setPen(color.lighter())
    painter.drawLine(x, y + grid_size - 1, x, y)
    painter.drawLine(x, y, x + grid_size - 1, y)
    painter.setPen(color.darker())
    painter.drawLine(x + 1, y + grid_size - 1, x + grid_size - 1, y + grid_size - 1)
    painter.drawLine(x + grid_size - 1, y + grid_size - 1, x + grid_size - 1, y + 1)

3.gameboard

'''
Function:

'''
import random
from .misc import *
from .shapes import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import QFrame


'''内部板块'''
class InnerBoard():
    def __init__(self, width=10, height=22):
        # 宽和长, 单位长度为小方块边长
        self.width = width
        self.height = height
        self.reset()
    '''判断当前俄罗斯方块是否可以移动到某位置'''
    def ableMove(self, coord, direction=None):
        assert len(coord) == 2
        if direction is None:
            direction = self.current_direction
        for x, y in self.current_tetris.getAbsoluteCoords(direction, coord[0], coord[1]):
            # 超出边界
            if x >= self.width or x < 0 or y >= self.height or y < 0:
                return False
            # 该位置有俄罗斯方块了
            if self.getCoordValue([x, y]) > 0:
                return False
        return True
    '''向右移动'''
    def moveRight(self):
        if self.ableMove([self.current_coord[0] + 1, self.current_coord[1]]):
            self.current_coord[0] += 1
    '''向左移动'''
    def moveLeft(self):
        if self.ableMove([self.current_coord[0] - 1, self.current_coord[1]]):
            self.current_coord[0] -= 1
    '''顺时针转'''
    def rotateClockwise(self):
        if self.ableMove(self.current_coord, (self.current_direction - 1) % 4):
            self.current_direction = (self.current_direction-1) % 4
    '''逆时针转'''
    def rotateAnticlockwise(self):
        if self.ableMove(self.current_coord, (self.current_direction + 1) % 4):
            self.current_direction = (self.current_direction+1) % 4
    '''向下移动'''
    def moveDown(self):
        removed_lines = 0
        if self.ableMove([self.current_coord[0], self.current_coord[1] + 1]):
            self.current_coord[1] += 1
        else:
            x_min, x_max, y_min, y_max = self.current_tetris.getRelativeBoundary(self.current_direction)
            # 简单起见, 有超出屏幕就判定游戏结束
            if self.current_coord[1] + y_min < 0:
                self.is_gameover = True
                return removed_lines
            self.mergeTetris()
            removed_lines = self.removeFullLines()
            self.createNewTetris()
        return removed_lines
    '''坠落'''
    def dropDown(self):
        removed_lines = 0
        while self.ableMove([self.current_coord[0], self.current_coord[1] + 1]):
            self.current_coord[1] += 1
        x_min, x_max, y_min, y_max = self.current_tetris.getRelativeBoundary(self.current_direction)
        # 简单起见, 有超出屏幕就判定游戏结束
        if self.current_coord[1] + y_min < 0:
            self.is_gameover = True
            return removed_lines
        self.mergeTetris()
        removed_lines = self.removeFullLines()
        self.createNewTetris()
        return removed_lines
    '''合并俄罗斯方块(最下面定型不能再动的那些)'''
    def mergeTetris(self):
        for x, y in self.current_tetris.getAbsoluteCoords(self.current_direction, self.current_coord[0], self.current_coord[1]):
            self.board_data[x + y * self.width] = self.current_tetris.shape
        self.current_coord = [-1, -1]
        self.current_direction = 0
        self.current_tetris = tetrisShape()
    '''移出整行都有小方块的'''
    def removeFullLines(self):
        new_board_data = [0] * self.width * self.height
        new_y = self.height - 1
        removed_lines = 0
        for y in range(self.height - 1, -1, -1):
            cell_count = sum([1 if self.board_data[x + y * self.width] > 0 else 0 for x in range(self.width)])
            if cell_count < self.width:
                for x in range(self.width):
                    new_board_data[x + new_y * self.width] = self.board_data[x + y * self.width]
                new_y -= 1
            else:
                removed_lines += 1
        self.board_data = new_board_data
        return removed_lines
    '''创建新的俄罗斯方块(即将next_tetris变为current_tetris)'''
    def createNewTetris(self):
        x_min, x_max, y_min, y_max = self.next_tetris.getRelativeBoundary(0)
        # y_min肯定是-1
        if self.ableMove([self.init_x, -y_min]):
            self.current_coord = [self.init_x, -y_min]
            self.current_tetris = self.next_tetris
            self.next_tetris = self.getNextTetris()
        else:
            self.is_gameover = True
        self.shape_statistics[self.current_tetris.shape] += 1
    '''获得下个俄罗斯方块'''
    def getNextTetris(self):
        return tetrisShape(random.randint(1, 7))
    '''获得板块数据'''
    def getBoardData(self):
        return self.board_data
    '''获得板块数据上某坐标的值'''
    def getCoordValue(self, coord):
        return self.board_data[coord[0] + coord[1] * self.width]
    '''获得俄罗斯方块各个小块的绝对坐标'''
    def getCurrentTetrisCoords(self):
        return self.current_tetris.getAbsoluteCoords(self.current_direction, self.current_coord[0], self.current_coord[1])
    '''重置'''
    def reset(self):
        # 记录板块数据
        self.board_data = [0] * self.width * self.height
        # 当前俄罗斯方块的旋转状态
        self.current_direction = 0
        # 当前俄罗斯方块的坐标, 单位长度为小方块边长
        self.current_coord = [-1, -1]
        # 下一个俄罗斯方块
        self.next_tetris = self.getNextTetris()
        # 当前俄罗斯方块
        self.current_tetris = tetrisShape()
        # 游戏是否结束
        self.is_gameover = False
        # 俄罗斯方块的初始x位置
        self.init_x = self.width // 2
        # 形状数量统计
        self.shape_statistics = [0] * 8


'''外部板块'''
class ExternalBoard(QFrame):
    score_signal = pyqtSignal(str)
    def __init__(self, parent, grid_size, inner_board):
        super(ExternalBoard, self).__init__(parent)
        self.grid_size = grid_size
        self.inner_board = inner_board
        self.setFixedSize(grid_size * inner_board.width, grid_size * inner_board.height)
        self.initExternalBoard()
    '''外部板块初始化'''
    def initExternalBoard(self):
        self.score = 0
    '''把内部板块结构画出来'''
    def paintEvent(self, event):
        painter = QPainter(self)
        for x in range(self.inner_board.width):
            for y in range(self.inner_board.height):
                shape = self.inner_board.getCoordValue([x, y])
                drawCell(painter, x * self.grid_size, y * self.grid_size, shape, self.grid_size)
        for x, y in self.inner_board.getCurrentTetrisCoords():
            shape = self.inner_board.current_tetris.shape
            drawCell(painter, x * self.grid_size, y * self.grid_size, shape, self.grid_size)
        painter.setPen(QColor(0x777777))
        painter.drawLine(0, self.height() - 1, self.width(), self.height() - 1)
        painter.drawLine(self.width() - 1, 0, self.width() - 1, self.height())
        painter.setPen(QColor(0xCCCCCC))
        painter.drawLine(self.width(), 0, self.width(), self.height())
        painter.drawLine(0, self.height(), self.width(), self.height())
    '''数据更新'''
    def updateData(self):
        self.score_signal.emit(str(self.score))
        self.update()


'''侧面板, 右边显示下一个俄罗斯方块的形状'''
class SidePanel(QFrame):
    def __init__(self, parent, grid_size, inner_board):
        super(SidePanel, self).__init__(parent)
        self.grid_size = grid_size
        self.inner_board = inner_board
        self.setFixedSize(grid_size * 5, grid_size * inner_board.height)
        self.move(grid_size * inner_board.width, 0)
    '''画侧面板'''
    def paintEvent(self, event):
        painter = QPainter(self)
        x_min, x_max, y_min, y_max = self.inner_board.next_tetris.getRelativeBoundary(0)
        dy = 3 * self.grid_size
        dx = (self.width() - (x_max - x_min) * self.grid_size) / 2
        shape = self.inner_board.next_tetris.shape
        for x, y in self.inner_board.next_tetris.getAbsoluteCoords(0, 0, -y_min):
            drawCell(painter, x * self.grid_size + dx, y * self.grid_size + dy, shape, self.grid_size)
    '''更新数据'''
    def updateData(self):
        self.update()

4.__init__

'''初始化'''
from .shapes import tetrisShape
from .gameboard import InnerBoard, ExternalBoard, SidePanel

 

5.README

requirements

♥️关注,就是我创作的动力

♥️点赞,就是对我最大的认可

♥️这里是小刘,励志用心做好每一篇文章,谢谢大家

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/335450.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

图表控件LightningChart .NET再破世界纪录,支持实时可视化 1 万亿个数据点

LightningChart.NET SDK 是一款高性能数据可视化插件工具&#xff0c;由数据可视化软件组件和工具类组成&#xff0c;可支持基于 Windows 的用户界面框架&#xff08;Windows Presentation Foundation&#xff09;、Windows 通用应用平台&#xff08;Universal Windows Platfor…

数据可视化大屏百度地图绘制行政区域标注实战案例解析(个性化地图、标注、视频、控件、定位、检索)

百度地图开发系列目录 数据可视化大屏应急管理综合指挥调度系统完整案例详解&#xff08;PHP-API、Echarts、百度地图&#xff09;数据可视化大屏百度地图API开发&#xff1a;停车场分布标注和检索静态版百度地图高级开发&#xff1a;map.getDistance计算多点之间的距离并输入…

元宵晚会节目预告没有岳云鹏,是不敢透露还是另有隐情

在刚刚结束的元宵节晚会上&#xff0c;德云社的岳云鹏&#xff0c;再一次参加并引起轰动&#xff0c;并获得了观众朋友们的一致好评。 不过有细心的网友发现&#xff0c;早前央视元宵晚会节目预告&#xff0c;并没有看到小岳岳&#xff0c;难道是不敢提前透露&#xff0c;怕公布…

TCP 三次握手和四次挥手

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录TCP 建立连接(三次握手)为啥不能是 4 次&#xff1f;为啥不能是 2 次&#xff1f;三次握手的意义&#xff1a;TCP 断开连接(四…

前端报表如何实现无预览打印解决方案或静默打印

在前端开发中&#xff0c;除了将数据呈现后&#xff0c;我们往往需要为用户提供&#xff0c;打印&#xff0c;导出等能力&#xff0c;导出是为了存档或是二次分析&#xff0c;而打印则因为很多单据需要打印出来作为主要的单据来进行下一环节的票据支撑&#xff0c; 而前端打印可…

Android Binder机制之一(简介)

目录 前言 一、Android 进程间通信方式 二、Binder架构图 三、Binder涉及角色 3.1 Binder驱动 3.2 Binder实体 3.3 Binder引用 3.4 远程服务 3.5 ServiceManager守护进程 四、涉及源码 前言 这是本人第N次看Binder 相关知识了&#xff0c;其实每次看都有新的收获&…

Docker搭建本地私有仓库

目录 一、本地私有仓库的优点 二、Docker搭建本地私有仓库 2.1、首先下载 registry 镜像 2.2、在 daemon.json 文件中添加私有镜像仓库地址 2.3、运行 registry 容器 2.4、Docker容器的重启策略 2.5、为镜像打标签 2.6、上传到私有仓库 2.7、私有仓库的 centos 镜像有…

Cubox是什么应用?如何将Cubox同步至Notion、语雀、在线文档中

Cubox是什么应用&#xff1f; Cubox 是一款跨平台的网络收藏工具&#xff0c;通过浏览器扩展、客户端、手机应用、微信转发等方式&#xff0c;将网页、文字、图片、语音、视频、文件等内容保存起来&#xff0c;再经过自动整理、标签、分类之后&#xff0c;就可以随时阅读、搜索…

02- pandas 数据库 (数据库)

pandas 数据库重点: pandas 的主要数据结构: Series (一维数据)与 DataFrame (二维数据)。 pd.DataFrame(data np.random.randint(0,151,size (5,3)), # 生成pandas数据 index [Danial,Brandon,softpo,Ella,Cindy], # 行索引 …

windeployqt实现一键打包

每次发布QT程序前,都必须要在命令行环境下运行windeployqt 工具进行打包,加载相关的lib文件,才能正常运行。但是在命令行模式下,每次都要手动输入windeployqt的目录,和应用程序的位置目录,效率非常低,见下图: 那QT有没有什么好用的工具可以避免这个问题呢,认真找了一下…

前端如何实现将多页数据合并导出到Excel单Sheet页解决方案|内附代码

前端与数据展示 前后端分离是当前比较盛行的开发模式&#xff0c;它使项目的分工更加明确,后端负责处理、存储数据;前端负责显示数据.前端和后端开发人员通过接口进行数据的交换。因此前端最重要的能力是需要将数据呈现给用户后&#xff0c;与终端用户进行交互。 在前端拿到数…

【Linux】宝塔面板 SSL 证书安装部署

宝塔面板 SSL 证书安装部署前言证书下载宝塔配置SSL注意事项前言 前期有讲过Tomcat和Nginx分别部署SSL证书&#xff0c;但也有好多小伙伴们私信我说&#xff0c;帮忙出一期宝塔面板部署SSL证书的教程&#xff0c;毕竟宝塔的用户体量也是蛮大的&#xff0c;于是宠粉的博主&…

基于地基激光雷达数据和深度学习的Faster R-CNN的橡胶树个体分割

Paper题目&#xff1a;Individual Rubber T ree Segmentation Based on Ground-Based LiDAR Data and Faster R-CNN of Deep Learning Abstract 中国南方的橡胶树经常受到可能导致树体倾斜的自然干扰的影响。从扫描点云中对单个橡胶树进行准确的树冠分割是准确检索树参数的必…

前端 ES6 之 Promise 实践应用与控制反转

Promise 主要是为解决程序异步处理而生的&#xff0c;在现在的前端应用中无处不在&#xff0c;已然成为前端开发中最重要的技能点之一。它不仅解决了以前回调函数地狱嵌套的痛点&#xff0c;更重要的是它提供了更完整、更强大的异步解决方案。 同时 Promise 也是前端面试中必不…

玩转系统|初遇ChatGPT,我和TA的第一次约会

最近互联网圈子有一个非常火爆的话题ChatGPT&#xff0c;短短一周的时间就有上百万的用户&#xff0c;如果你不是程序员&#xff0c;也许会问这到底是个什么玩意&#xff1f;ChatGPT是什么&#xff1f;ChatGPT&#xff0c;美国“开放人工智能研究中心”研发的聊天机器人程序 [1…

CAPL(vTESTStudio) - DoIP - TCP接收_04

TCP接收 函数介绍 TcpOpen函数

LeetCode刷题系列 -- 59. 螺旋矩阵 II

给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。示例 1&#xff1a;输入&#xff1a;n 3输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]]示例 2&#xff1a;输入&#xff1a;n 1输出&#xff1…

以后更新功能,再也不用App发版了!智能小程序将为开发者最大化减负

在 IoT 时代&#xff0c;越来越多的企业意识到打造自有 App 对于品牌的重要性。作为智能设备不可或缺的控制终端&#xff0c;App 具备连接用户、完善服务、精细化运营用户的独特优势&#xff0c;可帮助企业大大提升品牌竞争力。 为了帮助品牌企业打造更具个性化、差异化的智能…

MoveIT Rviz和Gazebo联合仿真

文章目录环境安装概述ros_control框架ros_control数据流文件配置附加工具故障问题解决参考接前两篇&#xff1a;ROS MoveIT1&#xff08;Noetic&#xff09;安装总结 Solidworks导出为URDF用于MoveIT总结&#xff08;带prismatic&#xff09; MoveIT1 Assistant 总结 环境 Ubu…

网络安全协议(3)

作者简介&#xff1a;一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.当前流行操作系统的安全等级 1.Windows的安全等级 什么是EAL…