基于穷举搜索的AI自动实现俄罗斯方块游戏(核心代码详解)

news2024/9/22 5:42:31

一、算法思想核心

这个俄罗斯方块AI的核心思想是通过穷举搜索当前形状和下一个形状的所有可能的放置组合,并为每个组合计算一个得分。得分是根据游戏板面的状态来评估每个组合的优劣,最终选择得分最高的放置策略。

具体的实现步骤和核心思想如下:

  1. 穷举搜索:通过嵌套的循环,穷举当前形状和下一个形状的所有可能的放置组合。首先确定当前形状的方向d0和在x轴上的起始位置x0,然后确定下一个形状的方向d1和在x轴上的起始位置x1。这样就得到一个可能的放置组合,然后再依次尝试所有的组合。

  2. 计算下落距离:对于每个放置组合,通过calcNextDropDist()方法计算下一个形状在当前位置下落的距离。这个距离将会在后续的计算中用到,因为在俄罗斯方块游戏中,形状在下落过程中会被堆积的方块所阻挡,不能无限下落。

  3. 计算得分:对于每个放置组合,通过calculateScore()方法计算其得分。在计算得分时,考虑了多个因素,包括消除的行数、洞的数量、方块的堆积高度等。具体得分计算方式在calculateScore()方法中进行。

  4. 选择最优策略:在计算得分过程中,将每个放置组合的得分与当前最优得分进行比较,保留得分更高的组合作为当前的最优策略。最终,整个穷举搜索完成后,得到得分最高的放置策略。

这个算法的核心思想是通过搜索和评估不同的放置组合,寻找到最优的形状放置策略。

二、核心代码实现

class TetrisAI(object):

    # 下一个形状的计算
    def nextMove(self):
        t1 = datetime.now()  # 记录开始计算时间
        if BOARD_DATA.currentShape == Shape.shapeNone:  # 若当前没有正在下落的形状,则返回None
            return None

        currentDirection = BOARD_DATA.currentDirection  # 获取当前形状的方向
        currentY = BOARD_DATA.currentY  # 获取当前形状的纵向位置
        _, _, minY, _ = BOARD_DATA.nextShape.getBoundingOffsets(0)  # 获取下一个形状的边界偏移量
        nextY = -minY  # 计算下一个形状的纵向位置

        # print("=======")
        strategy = None  # 定义策略变量,用于保存最优的放置策略
        if BOARD_DATA.currentShape.shape in (Shape.shapeI, Shape.shapeZ, Shape.shapeS):
            d0Range = (0, 1)  # 若当前形状为I、Z或S型,则有两个可能的方向
        elif BOARD_DATA.currentShape.shape == Shape.shapeO:
            d0Range = (0,)  # 若当前形状为O型,则只有一个可能的方向
        else:
            d0Range = (0, 1, 2, 3)  # 其他形状有四个可能的方向

        if BOARD_DATA.nextShape.shape in (Shape.shapeI, Shape.shapeZ, Shape.shapeS):
            d1Range = (0, 1)  # 若下一个形状为I、Z或S型,则有两个可能的方向
        elif BOARD_DATA.nextShape.shape == Shape.shapeO:
            d1Range = (0,)  # 若下一个形状为O型,则只有一个可能的方向
        else:
            d1Range = (0, 1, 2, 3)  # 其他形状有四个可能的方向

        # 遍历所有可能的放置组合
        for d0 in d0Range:
            minX, maxX, _, _ = BOARD_DATA.currentShape.getBoundingOffsets(d0)  # 获取当前形状在x轴上的最小和最大边界
            for x0 in range(-minX, BOARD_DATA.width - maxX):  # 遍历所有可能的x轴位置
                board = self.calcStep1Board(d0, x0)  # 计算当前形状放置后的游戏板面状态
                for d1 in d1Range:
                    minX, maxX, _, _ = BOARD_DATA.nextShape.getBoundingOffsets(d1)  # 获取下一个形状在x轴上的最小和最大边界
                    dropDist = self.calcNextDropDist(board, d1, range(-minX, BOARD_DATA.width - maxX))  # 计算下一个形状的下落距离
                    for x1 in range(-minX, BOARD_DATA.width - maxX):  # 遍历所有可能的x轴位置
                        score = self.calculateScore(np.copy(board), d1, x1, dropDist)  # 计算该放置组合的得分
                        if not strategy or strategy[2] < score:  # 如果当前得分大于之前记录的最优得分,则更新最优策略
                            strategy = (d0, x0, score)
        print("===", datetime.now() - t1)  # 输出计算时间
        return strategy  # 返回得分最高的放置策略

    # 计算下一个形状在当前位置的下落距离
    def calcNextDropDist(self, data, d0, xRange):
        res = {}  # 用于存储不同x轴位置对应的下落距离
        for x0 in xRange:
            if x0 not in res:
                res[x0] = BOARD_DATA.height - 1  # 初始化下落距离为游戏板面的高度减1
            for x, y in BOARD_DATA.nextShape.getCoords(d0, x0, 0):
                yy = 0
                while yy + y < BOARD_DATA.height and (yy + y < 0 or data[(y + yy), x] == Shape.shapeNone):
                    yy += 1  # 逐步向下探测下落距离,直到遇到障碍物或达到游戏板面底部
                yy -= 1
                if yy < res[x0]:
                    res[x0] = yy  # 记录每个x轴位置对应的最小下落距离
        return res

    # 计算当前形状在给定方向和x轴位置下,下落一步后的游戏板面状态
    def calcStep1Board(self, d0, x0):
        board = np.array(BOARD_DATA.getData()).reshape((BOARD_DATA.height, BOARD_DATA.width))  # 复制游戏板面数据
        self.dropDown(board, BOARD_DATA.currentShape, d0, x0)  # 将当前形状放置到指定位置
        return board  # 返回放置后的游戏板面状态

    # 计算形状在给定位置的下落距离,并更新游戏板面状态
    def dropDown(self, data, shape, direction, x0):
        dy = BOARD_DATA.height - 1  # 初始化下落距离为游戏板面的高度减1
        for x, y in shape.getCoords(direction, x0, 0):  # 遍历形状的每个方块
            yy = 0
            while yy + y < BOARD_DATA.height and (yy + y < 0 or data[(y + yy), x] == Shape.shapeNone):
                yy += 1  # 逐步向下探测下落距离,直到遇到障碍物或达到游戏板面底部
            yy -= 1
            if yy < dy:
                dy = yy  # 记录形状在该位置的最小下落距离
        self.dropDownByDist(data, shape, direction, x0, dy)  # 将形状按最小下落距离放置到游戏板面上

    # 根据下落距离将形状放置到游戏板面上
    def dropDownByDist(self, data, shape, direction, x0, dist):
        for x, y in shape.getCoords(direction, x0, 0):  # 遍历形状的每个方块
            data[y + dist, x] = shape.shape  # 在对应位置放置形状方块

    # 计算放置组合的得分
    def calculateScore(self, step1Board, d1, x1, dropDist):
        t1 = datetime.now()  # 记录开始计算时间
        width = BOARD_DATA.width  # 获取游戏板面的宽度
        height = BOARD_DATA.height  # 获取游戏板面的高度

        self.dropDownByDist(step1Board, BOARD_DATA.nextShape, d1, x1, dropDist[x1])  # 将下一个形状按下落距离放置到游戏板面上
        # print(datetime.now() - t1)

        # Term 1: lines to be removed
        fullLines, nearFullLines = 0, 0
        roofY = [0] * width
        holeCandidates = [0] * width
        holeConfirm = [0] * width
        vHoles, vBlocks = 0, 0
        for y in range(height - 1, -1, -1):
            hasHole = False
            hasBlock = False
            for x in range(width):
                if step1Board[y, x] == Shape.shapeNone:  # 若游戏板面上存在洞
                    hasHole = True
                    holeCandidates[x] += 1  # 记录每个x轴位置可能的洞数
                else:
                    hasBlock = True
                    roofY[x] = height - y  # 记录每个x轴位置的方块堆叠高度
                    if holeCandidates[x] > 0:
                        holeConfirm[x] += holeCandidates[x]
                        holeCandidates[x] = 0
                    if holeConfirm[x] > 0:
                        vBlocks += 1  # 统计垂直方向上的方块堆叠
            if not hasBlock:
                break
            if not hasHole and hasBlock:
                fullLines += 1  # 统计填满的行数
        vHoles = sum([x ** .7 for x in holeConfirm])  # 根据洞的位置分布计算垂直方向的洞数
        maxHeight = max(roofY) - fullLines  # 计算最大堆叠高度
        # print(datetime.now() - t1)

        roofDy = [roofY[i] - roofY[i+1] for i in range(len(roofY) - 1)]  # 计算相邻列之间的高度差

        if len(roofY) <= 0:
            stdY = 0
        else:
            stdY = math.sqrt(sum([y ** 2 for y in roofY]) / len(roofY) - (sum(roofY) / len(roofY)) ** 2)  # 计算堆叠高度的标准差
        if len(roofDy) <= 0:
            stdDY = 0
        else:
            stdDY = math.sqrt(sum([y ** 2 for y in roofDy]) / len(roofDy) - (sum(roofDy) / len(roofDy)) ** 2)  # 计算堆叠高度差的标准差

        absDy = sum([abs(x) for x in roofDy])  # 计算相邻列高度差的绝对值之和
        maxDy = max(roofY) - min(roofY)  # 计算最大和最小堆叠高度之间的差
        # print(datetime.now() - t1)

        score = fullLines * 1.8 - vHoles * 1.0 - vBlocks * 0.5 - maxHeight ** 1.5 * 0.02 \
            - stdY * 0.0 - stdDY * 0.01 - absDy * 0.2 - maxDy * 0.3  # 综合考虑多个因素计算得分
        # print(score, fullLines, vHoles, vBlocks, maxHeight, stdY, stdDY, absDy, roofY, d0, x0, d1, x1)
        return score  # 返回放置组合的得分

该代码用于计算俄罗斯方块游戏中当前形状和下一个形状的最优放置策略。

  1. TetrisAI类:定义了一个名为TetrisAI的类,用于处理俄罗斯方块AI相关的逻辑和计算。

  2. nextMove()方法:该方法用于计算当前形状和下一个形状的最优放置策略。它首先检查当前是否有正在下落的形状,若没有则返回None,表示不需要进行任何操作。接下来,它依次尝试不同的放置组合,并计算每个组合的得分,最后返回得分最高的放置策略。

  3. calcNextDropDist()方法:该方法用于计算下一个形状在当前位置下落的距离。它将下落距离存储在一个字典中,字典的键为下落位置x0,值为对应的下落距离。

  4. calcStep1Board()方法:该方法用于计算在当前形状和给定方向、x0(x轴上的起始位置)下,下落一步后的游戏板面状态。

  5. dropDown()方法和dropDownByDist()方法:这两个方法用于计算形状在给定位置下落的距离,并将形状的状态更新到游戏板面上。

  6. calculateScore()方法:该方法用于计算给定放置组合的得分,根据板面状态的不同特征来评估每个组合的优劣。得分是根据消除的行数、出现的洞(空白位置)、方块堆叠的高度等因素进行计算。

总体来说,这段代码实现了一个简单的俄罗斯方块AI,用于预测并选择最优的形状放置位置,以尽可能消除更多的方块行。

三、游戏实现与测试

 四、完整代码功能解释及游戏玩法

完整的代码下:

  • tetris_game.py是主函数,主要为应用界面的主函数。
  • tetris_model.py是游戏的数据模型。
  • tetris_ai.py是AI实现算法部分部分。

tetris_game.py代码中,可以通过修改如下:

speed的速度修改方块的下落的速度。 

通过注释掉# TETRIS_AI = None可以实现认为控制玩俄罗斯方块。

 自己玩的规则:游戏板面是一个矩形网格,您需要使用方向键和空格键来控制方块的移动和旋转。

  • 使用向上键:旋转当前形状,使其适应不同的空间布局。
  • 使用向左键:将当前形状向左移动一个格子,改变其在横向上的位置。
  • 使用向右键:将当前形状向右移动一个格子,改变其在横向上的位置。
  • 使用空格键:立即将当前形状下落到最底部,以便快速堆叠方块。

如果您想暂停游戏,只需按下键盘上的“P”键即可。游戏会暂停在当前状态,您可以随时继续游戏。右侧面板会显示下一个即将出现的形状,这样您可以提前做好相应的准备。

完整代码链接:

https://download.csdn.net/download/weixin_40651515/88111818

环境配置:

moviepy==1.0.3
numpy==1.19.5
PyQt5==5.15.4

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

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

相关文章

怎么恢复回收站清空的文件,教你三种恢复的方法

不论是笔记本电脑还是台式机电脑&#xff0c;都有回收站这样的“后悔药”&#xff0c;我们许多误删除的文件夹、图片、视频、文档等都能从它里面找到&#xff0c;回收站在灵活运用下能更好地为我们服务&#xff0c;但是使用过程中难免会出现失误&#xff0c;比如说不小心清空了…

pdf合并大小不一样怎么办?有这几个方法就够了

pdf合并大小不一样怎么办&#xff1f;在日常工作和生活中&#xff0c;我们经常需要处理PDF文件。在将多个PDF文件合并成一个时&#xff0c;由于这些文件的大小和格式可能不同&#xff0c;可能会遇到一些问题。但不用担心&#xff0c;接下来将介绍几种方法来解决这个问题。 方法…

JAVA缓存技术介绍

OSCache OSCache是个一个广泛采用的高性能的J2EE缓存框架&#xff0c;OSCache能用于任何Java应用程序的普通的缓存解决方案。 OSCache有以下特点&#xff1a; 我创建了一个群&#xff0c;群里不定期分享技术干货&#xff0c;行业秘闻&#xff0c;汇集各类奇妙好玩的话题和流行动…

Individual household electric power consumption个人家庭用电量数据挖掘与时序预测建模

今天接到一个任务就是需要基于给定的数据集来进行数据挖掘分析相关的计算&#xff0c;并完成对未来时段内数据的预测建模&#xff0c;话不多少直接看内容。 官方数据详情介绍在这里&#xff0c;如下所示&#xff1a; 数据集中一共包含9个不同的字段&#xff0c;详情如下&#…

MinIO对象存储

MinIO 是一个高性能的分布式对象存储系统。 它是软件定义的&#xff0c;在行业标准硬件上运行&#xff0c;并且 100% 开源&#xff0c;主要许可证是 GNU AGPL v3。 MinIO 的不同之处在于它从一开始就被设计为私有/混合云对象存储的标准。 因为 MinIO 是专门为对象而构建的&…

day14 | 100.二叉树的最大深度 111.二叉树的最小深度 222.完全二叉树的节点个数

文章目录 一、二叉树的最大深度二、二叉树的最小深度三、完全二叉树的节点数 一、二叉树的最大深度 100.二叉树的最大深度 因为题给出的高度和深度是一样的&#xff0c;所以求得结果都能过。 class Solution { public:int getHeight(TreeNode *node){if (node nullptr)retu…

Qt 第三讲 手动拖动ui界面组件,事件处理机制,实现简单闹钟

手动拖动ui界面组件&#xff0c;实现闹钟 源文件 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);ui->btn_end->setEnabled(false);speecher new QTex…

Linux基础内容(29)—— 额外锁

Linux基础内容&#xff08;28&#xff09;—— POSIX信号量与循环队列_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/131844590?spm1001.2014.3001.5501 目录 1.其他常见的各种锁 自旋锁 库语言的实现 2.读者写者问题 1.读者写者线程 2.…

SpringMVC启动时非常缓慢,显示一直在部署中,网页也无法访问,,,Artifact is being deployed, please wait...

写了一个基本的SpringMVC程序进行测试&#xff0c;结果启动时一直显示在等待部署完毕&#xff0c;&#xff0c;&#xff0c; but这个地方一直显示转圈圈。。 后来通过url访问时网页一直转圈圈。。也就是等待响应。。 看了一会儿&#xff0c;也不知道哪儿错了&#xff0c;&…

什么是多运行时架构?

服务化演进中的问题 自从数年前微服务的概念被提出&#xff0c;到现在基本成了技术架构的标配。微服务的场景下衍生出了对分布式能力的大量需求&#xff1a;各服务之间需要相互协作和通信&#xff0c;以及共享状态等等&#xff0c;因此就有了各种中间件来为业务服务提供这种分…

紫光FPGA试用--软件篇

目录 一 软件安装启动 二 如何打开IP核&#xff1f;查看/修改现有IP核参数&#xff1f; 三 如何定义引脚&#xff1f; 四 如何下载code进入FPGA? 1. 下载到FPGA芯片内&#xff1a; 2.下载到外部FLASH中 五 如何进入在线调试模式&#xff0c;调试步骤 操作步骤&#xff…

微分流形之魂

找到一个非常棒的教程&#xff1a;本科生自学微分流形有哪些资料推荐&#xff1f; - 知乎 应该是目前微积分的终极答案了&#xff08;非数学系&#xff09; 首先&#xff0c;这个函数具有线性结构。所以他是属于V*的。 之前我倒没想过这个问题&#xff0c;以为所有的泛函都是V…

4个公式8个案例学会与AI GPT对话

一、什么是Prompt? (1)Prompt的概念 我们在使用GPT的时候,给GPT发送的消息就是Prompt. 例如,当我们问ChatGPT"WPS是什么软件?"时: 其中,"WPS是什么软件?"这句话就是Prompt. chatgpt体验&#xff1a;http://www.chat136.com chatgpt学习&#xff1a…

【OJ比赛日历】快周末了,不来一场比赛吗? #07.29-08.04 #13场

CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 以下信息仅供参考&#xff0c;以比赛官网为准 目录 2023-07-29&#xff08;周六&#xff09; #8场比赛2023-07-30…

一文带你迅速入门SprIngMVC,看这一篇就足够了!

0. 什么是SpringMVC 要知道什么是SpringMVC&#xff0c;我们首先得知道什么 MVC&#xff0c;MVC是软件工程中的一种架构模式&#xff0c;分为 Model、View、Control。它把软件系统分为模型、视图和控制器三个基本部分。 Model&#xff1a;模型&#xff0c;应用程序负责数据逻…

SVN - 记录一下无法提交代码 提示:被锁定(locked)

今天遇到一个问题&#xff0c;svn 在提交代码的时候出现了svn is already locked&#xff0c;导致代码无法提交&#xff08;commit&#xff09;和更新&#xff08;update&#xff09; 主要报错如下&#xff1a; 解决方法&#xff1a; 然后点击 Clean up 选中一下选项&#xff…

人工智能巨头碰撞——埃隆·马斯克推出xAI挑战OpenAI的统治地位

目录 前言XAI 的推出什么是XAI&#xff1f;它将聚焦于什么&#xff1f; 一&#xff1a;“反AI斗士”马斯克进军AI&#xff0c;你怎么看&#xff1f;二&#xff1a;回顾上半年的“百模大战”&#xff0c;中国的AI产业怎么样了&#xff1f;三&#xff1a;AI大模型这把火&#xff…

算法通过村第二关-链表白银笔记

文章目录 再战链表|反转链表剑指 Offer II 024. 反转链表熟练掌握这两种解法建立头节点的解决思路不采用建立头节点的方法采用循环/递归的方式解决 总结 再战链表|反转链表 提示&#xff1a;多拿些酒来&#xff0c;因为生命只有乌有。 剑指 Offer II 024. 反转链表 如果不使用…

9个可用于图片转文本的最佳免费 OCR 软件

光学字符识别 (OCR) 软件可帮助将不可编辑的文档格式&#xff08;例如 PDF、图像或纸质文档&#xff09;转换为可编辑和可搜索的机器可读格式。 OCR 应用程序通常用于从 PDF 和图像中捕获文本&#xff0c;并将文本转换为可编辑格式&#xff0c;例如 Word、Excel 或纯文本文件。…

SpringBoot复习:(3)应用打包成jar包,清单文件里的主类是我们用@SpringBootApplication注解标记的类吗?

不是. MANIFEST.MF类似如下&#xff1a; Manifest-Version: 1.0 Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx Spring-Boot-Layers-Index: BOOT-INF/layers.idx Start-Class: com.example.demo.DemoApplication Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-…