基于JavaScript动态规划编写一个益智小游戏
作者:小九九的爸爸
首先要在这里感谢宫水三叶大佬,最近在学习动态规划相关的知识,这位大佬的很多题解给都了我不错的灵感。其实动态规划大家真的应该重视起来,如果将动态规划这类问题可视化出来,你会发现,原来我们每天生活中都在跟动态规划打交道。这不,今天我就将一道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益智游戏内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!