基于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益智游戏内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring Boot中集成各种日志框架Logback、Log4j2和Java Util Logging的步骤和示例代码

    Spring Boot中集成各种日志框架Logback、Log4j2和Java Util 

    这篇文章主要介绍了Spring Boot中集成各种日志框架Logback、Log4j2和Java Util Logging,通过实例代码介绍了集成Logback、Log4j2和Java Util Logging的基本步骤,你可以根据自己的需求进行配置和扩展,以满足更复杂的日志需求,需要的朋友可以参考下
    2023-11-11
  • MyBatis如何使用(二)

    MyBatis如何使用(二)

    这篇文章主要介绍了MyBatis如何使用(二)的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-07-07
  • redis分布式锁的原理及代码实例

    redis分布式锁的原理及代码实例

    这篇文章主要介绍了redis分布式锁的原理及代码实例,Redis作为一款高性能内存数据库,其提供了一种非常实用的分布式锁解决方案,可以帮助开发人员轻松地实现分布式锁功能,对于分布式系统的开发和维护,具有非常大的实用价值,需要的朋友可以参考下
    2024-01-01
  • springboot项目父子多模块打包方式

    springboot项目父子多模块打包方式

    这篇文章主要介绍了springboot项目父子多模块打包方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • 关于单例模式懒汉式和饿汉式的区别及说明

    关于单例模式懒汉式和饿汉式的区别及说明

    这篇文章主要介绍了关于单例模式懒汉式和饿汉式的区别及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • Java Web十条开发实用小知识

    Java Web十条开发实用小知识

    这篇文章主要介绍了Java Web十条开发实用小知识的相关资料,需要的朋友可以参考下
    2016-05-05
  • CorsFilter 过滤器解决跨域的处理

    CorsFilter 过滤器解决跨域的处理

    这篇文章主要介绍了CorsFilter 过滤器解决跨域的处理操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • java 多线程-线程通信实例讲解

    java 多线程-线程通信实例讲解

    本文主要介绍java 多线程-线程通信 这里整理了相关资料及示例代码,有兴趣的小伙伴可以参考下
    2016-09-09
  • 使用@RequestParam设置默认可以传空值

    使用@RequestParam设置默认可以传空值

    这篇文章主要介绍了使用@RequestParam设置默认可以传空值的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • SpringBoot进行Web开发的实现

    SpringBoot进行Web开发的实现

    Spring Boot让我们可以快速构建项目并运行web应用,大大简化了Spring的复杂配置,本文主要介绍了SpringBoot进行Web开发的实现,感兴趣的可以了解一下
    2023-10-10

最新评论