基于JavaScript动态规划编写一个益智小游戏

 更新时间:2023年06月19日 11:23:42   作者:小九九的爸爸  
最近在学习动态规划相关的知识,所以本文将利用动态规划编写一个简单的益智小游戏,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下

首先要在这里感谢宫水三叶大佬,最近在学习动态规划相关的知识,这位大佬的很多题解给都了我不错的灵感。其实动态规划大家真的应该重视起来,如果将动态规划这类问题可视化出来,你会发现,原来我们每天生活中都在跟动态规划打交道。这不,今天我就将一道lc变种成一个益智小游戏了,那么我们抓紧开始吧。

游戏规则

给你一张n*n的地图,让你给出从S点到E点的路径上的最大得分数以及路径上的最大得分数对应的方案数。

规则限制如下:

  • 1、起始点始终为S,终点始终为E
  • 2、X为障碍物,你不能移动到障碍物上,也就是说遇到障碍物需要绕道而行。
  • 3、移动的方向只有三个:向左、向左上、向上。

对于S点来说,X点就是它的左上方,因为X点为障碍物,所以对于S点来说,可移动的方向只有2个,分别是向左和向上。

这里再对返回值做如下说明

路径上的最大得分数:对于上图来说,从S点到E点的路径上的最大得分数是7(路径如下图)。

路径上的最大得分数对应的方案数:对于上图来说,方案数是1。因为此时符合题意的到达E点的方案数是2个,但是这2个方案对应的得分数却不一样,且最大值为7,所以此时的方案数是1。

游戏交互

  • 起始点S与终点E,程序会用蓝色框来标注。
  • 障碍物X,程序会用红色标注。
  • 输入框可以让用户来填写答案,最后点击提交进行答案校验。

游戏效果

“好的食材往往需要最简单的烹饪方式” 这句话很适用我们今天做的这个小游戏,最开始给用户一个 2*2 规模的图,然后点击提交按钮,我们会校验你给出的答案,如果答案错误,我们会给出失败的提示语;如果成功,程序会给出“敢继续挑战嘛”的提示框,如果此时你点击“继续”,那么我们的规模将逐步的提升,由 2*2 会 提升到 3*3,后面可能会逐步提升到8*8。所以勇士们,证明你们的时刻到了,come on!

游戏算法讲解

地图的数据结构展示

首先对于地图的数据结构,我们可以使用二维数组来表示。我们以下图来举例:

对于这个地图,我们就可以这样表示:

let arr = [
    ['E', '2', '3'],
    ['2', 'X', '2'],
    ['1', '2', 'S']
];
let showContent = (item, x, y) => {
    let { arr } = this.state;
    if (x === y && x === 0){
        return 'E'
    }
    if (x === y && x === arr.length - 1){
        return 'S'
    }
    return item;
}
// 循环上图
<div>
    arr.map((item, itemIndex) => {
        return item.map((child, childIndex) => {
            return <div>
                {showContent(child, itemIndex, childIndex)}
            </div> 
        })
    })
</div>

如何统计最大路径得分数以及相应方案?

根据游戏规则我们知道,的移动的大方向一定是向上的(因为终点永远在第一层,起点永远在最后一层),

在大方向上,对于每个单元格的移动方向又细分了3个方向(向左、向上、向左上)。

对于移动方向的规则这个是基本点,所以一定不能忘了。有了这个基本点,我们继续往下分析。

对于E点来说,他的答案一定可以通过它右边的绿色框下面的绿色框来得出答案。比如现在来求一下S到E点的最大路径得分数,下面这个等式是一定成立的:

S点 到 E点的最大得分数 = Math.max(S点到E点右侧的绿色框的得分数, S点到E点下侧的绿色框的得分数)

根据上面的等式成立,所以我们思路就可以转变到S点到任意一点(除障碍物外)的最大得分数以及相应的方案数

我们可以使用二维数组的方式来存储每个单元格对应的答案。

let arr = [
    ['E', '2', '3'],
    ['2', 'X', '2'],
    ['1', '2', 'S']
];
let pathsWithMaxScore = () => {
    let n = arr.length;
    // 声明二维数组dp来存储每个单元格对应的答案
    // 其中dp[i][j] 代表 单元格
    // dp[i][j][0] 代表 s点到当前单元格的最大路径得分
    // dp[i][j][1] 代表 s点到当前单元格的最大路径得分对应的方案数
    let dp = new Array(n).fill(0).map(() => new Array(n).fill(0).map(() => [-1, 0]));
}

接着上面的思路,我们来求任意点对应的最大路径以及方案数。我们这里以下图的b点为例。

let pathsWithMaxScore = () => {
    let n = arr.length;
    // 声明二维数组dp来存储每个单元格对应的答案
    // 其中dp[i][j] 代表 单元格
    // dp[i][j][0] 代表 s点到当前单元格的最大路径得分
    // dp[i][j][1] 代表 s点到当前单元格的最大路径得分对应的方案数
    let dp = new Array(n).fill(0).map(() => new Array(n).fill(0).map(() => [-1, 0]));
    for (let i = n - 1; i >= 0; i--){
        for (let j = n - 1; j >= 0; j--){
            if (arr[i][j] === 'X'){
                // 根据题意,遇到障碍物就要绕行
                continue;
            }
            // 当 i === n-1 && j === n - 2时
            // 此时位置正好是b点
            // 此时需要做出2件事...
        }
    }
}

当我们到达b点时,我们需要考虑2件事:

  • 1、什么时候应该更新b点的最大得分数?
  • 2、b点的最大得分数对应的方案数应该如何更新?

对于第一个问题,当前循环到b点时,b点一定知道能够到达这个点的来源路径(fromX, fromY)是哪些,当dp[fromX][fromY][0] + arr[i][j] > dp[i][j][0] 成立时,就可以b点原先存储的最大数了。

对于第二个问题,又分为了2种情况。如果dp[fromX][fromY][0] + arr[i][j] === dp[i][j][0],说明这个新来的路径也算是一条有效方案,此时应该+1(dp[i][j][1] + 1)。还有一种就是 dp[fromX][fromY] + arr[i][j] > dp[i][j][0] 的时候,此时应该将 dp[i][j][1] 更新为dp[fromX][fromY][1]。

let pathsWithMaxScore = () => {
    let n = arr.length;
    // 声明二维数组dp来存储每个单元格对应的答案
    // 其中dp[i][j] 代表 单元格
    // dp[i][j][0] 代表 s点到当前单元格的最大路径得分
    // dp[i][j][1] 代表 s点到当前单元格的最大路径得分对应的方案数
    let dp = new Array(n).fill(0).map(() => new Array(n).fill(0).map(() => [-1, 0]));
    arr[0][0] = arr[n-1][n-1] = 0;
    dp[n - 1] [n - 1] = [0, 1];
    let fromPath = [ // 任意点的来源路径集合
        [0, 1],
        [1, 1],
        [1, 0]
    ];
    for (let i = n - 1; i >= 0; i--){
        for (let j = n - 1; j >= 0; j--){
            if (arr[i][j] === 'X'){
                // 根据题意,遇到障碍物就要绕行
                continue;
            }
            // 当 i === n-1 && j === n - 2时
            // 此时位置正好是b点,我们需要先遍历能到达b点的路径有哪些
            for (let [sx, sy] of fromPath){
                    let fromX = i + sx;
                    let fromY = j + sy;
                    if (lx < 0 || lx >= n || ly < 0 || ly >= n || arr[lx][ly] === 'X' || f[lx][ly][1] === 0) {
                        // 超出地图范围的,都过滤掉
                        continue;
                    }
                    if (dp[i][j][0] === dp[fromX][fromY][0] + Number(arr[i][j])){
                        dp[i][j][1] += dp[fromX][fromY][1];
                    } else if (dp[i][j][0] < dp[fromX][fromY][0] + Number(arr[i][j])){
                        dp[i][j][0] = dp[fromX][fromY][0] + Number(arr[i][j]);
                        // 此时为什么要更新路径?因为之前的几条路对应的得分不是最大的呀
                        dp[i][j][1] = dp[fromX][fromY][1];
                    }
            }
        }
    }
    return dp[0][0]; // dp[0][0]的值是一个长度为2的数组,数组第0项时得分,第一项是方案数
}

到此,我们的算法也就讲完了。感兴趣的小伙伴可以把这个算法吸收一下,或者自己喂个数据跑一下。

到此这篇关于基于JavaScript动态规划编写一个益智小游戏的文章就介绍到这了,更多相关JavaScript益智游戏内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 浅析springcloud 整合 zipkin-server 内存日志监控

    浅析springcloud 整合 zipkin-server 内存日志监控

    Zipkin是一款开源的分布式实时数据追踪系统(Distributed Tracking System),其主要功能是聚集来自各个异构系统的实时监控数据。这篇文章主要介绍了springcloud 整合 zipkin-server 内存日志监控,需要的朋友可以参考下
    2019-11-11
  • Hibernate延迟加载技术详解

    Hibernate延迟加载技术详解

    这篇文章主要介绍了Hibernate延迟加载技术,结合实例形式详细分析了Hibernate延迟加载所涉及的各种常用技巧,需要的朋友可以参考下
    2016-03-03
  • Java8处理集合的优雅姿势之Stream

    Java8处理集合的优雅姿势之Stream

    这篇文章主要给大家介绍了关于Java8优雅处理集合之Stream的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者使用java8具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-03-03
  • Java中的线程中断机制和LockSupport详解

    Java中的线程中断机制和LockSupport详解

    这篇文章主要介绍了Java中的线程中断机制和LockSupport详解,在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作,因此,Java提供了一种用于停止线程的协商机制中断,也即中断标识协商机制,需要的朋友可以参考下
    2023-09-09
  • 关于Java中代码块的执行顺序

    关于Java中代码块的执行顺序

    这篇文章主要介绍了关于Java中代码块的执行顺序,构造代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块,需要的朋友可以参考下
    2023-08-08
  • SpringMVC中的handlerMappings对象用法

    SpringMVC中的handlerMappings对象用法

    这篇文章主要介绍了SpringMVC中的handlerMappings对象用法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • 在eclipse中中文汉字乱码的解决方案

    在eclipse中中文汉字乱码的解决方案

    在本篇文章里小编给大家分享的是关于在eclipse中中文汉字乱码的解决方案,有需要的朋友们可以学习下。
    2019-12-12
  • springboot + JPA 配置双数据源实战

    springboot + JPA 配置双数据源实战

    这篇文章主要介绍了springboot + JPA 配置双数据源实战,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • 使用Jenkins Pipeline自动化构建发布Java项目的方法

    使用Jenkins Pipeline自动化构建发布Java项目的方法

    这篇文章主要介绍了使用Jenkins Pipeline自动化构建发布Java项目的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-04-04
  • 使用Spring注入Hibernate验证框架

    使用Spring注入Hibernate验证框架

    这篇文章主要介绍了使用Spring注入Hibernate验证框架方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12

最新评论