基于JavaScript動態(tài)規(guī)劃編寫一個益智小游戲
首先要在這里感謝宮水三葉大佬,最近在學(xué)習(xí)動態(tài)規(guī)劃相關(guān)的知識,這位大佬的很多題解給都了我不錯的靈感。其實(shí)動態(tài)規(guī)劃大家真的應(yīng)該重視起來,如果將動態(tài)規(guī)劃這類問題可視化出來,你會發(fā)現(xiàn),原來我們每天生活中都在跟動態(tài)規(guī)劃打交道。這不,今天我就將一道lc變種成一個益智小游戲了,那么我們抓緊開始吧。
游戲規(guī)則
給你一張n*n
的地圖,讓你給出從S
點(diǎn)到E
點(diǎn)的路徑上的最大得分?jǐn)?shù)以及路徑上的最大得分?jǐn)?shù)對應(yīng)的方案數(shù)。
規(guī)則限制如下:
- 1、起始點(diǎn)始終為
S
,終點(diǎn)始終為E
。 - 2、
X
為障礙物,你不能移動到障礙物上,也就是說遇到障礙物需要繞道而行。 - 3、移動的方向只有三個:向左、向左上、向上。
對于S點(diǎn)來說,X點(diǎn)就是它的左上方,因?yàn)閄點(diǎn)為障礙物,所以對于S點(diǎn)來說,可移動的方向只有2個,分別是向左和向上。
這里再對返回值做如下說明
:
路徑上的最大得分?jǐn)?shù)
:對于上圖來說,從S點(diǎn)到E點(diǎn)的路徑上的最大得分?jǐn)?shù)是7(路徑如下圖)。
路徑上的最大得分?jǐn)?shù)對應(yīng)的方案數(shù)
:對于上圖來說,方案數(shù)是1。因?yàn)榇藭r(shí)符合題意的到達(dá)E點(diǎn)的方案數(shù)是2個,但是這2個方案對應(yīng)的得分?jǐn)?shù)卻不一樣,且最大值為7,所以此時(shí)的方案數(shù)是1。
游戲交互
- 起始點(diǎn)S與終點(diǎn)E,程序會用藍(lán)色框來標(biāo)注。
- 障礙物X,程序會用紅色標(biāo)注。
- 輸入框可以讓用戶來填寫答案,最后點(diǎn)擊
提交
進(jìn)行答案校驗(yàn)。
游戲效果
“好的食材往往需要最簡單的烹飪方式” 這句話很適用我們今天做的這個小游戲,最開始給用戶一個 2*2
規(guī)模的圖,然后點(diǎn)擊提交按鈕
,我們會校驗(yàn)?zāi)憬o出的答案,如果答案錯誤,我們會給出失敗的提示語;如果成功,程序會給出“敢繼續(xù)挑戰(zhàn)嘛”的提示框,如果此時(shí)你點(diǎn)擊“繼續(xù)”,那么我們的規(guī)模將逐步的提升,由 2*2
會 提升到 3*3
,后面可能會逐步提升到8*8
。所以勇士們,證明你們的時(shí)刻到了,come on!
游戲算法講解
地圖的數(shù)據(jù)結(jié)構(gòu)展示
首先對于地圖的數(shù)據(jù)結(jié)構(gòu),我們可以使用二維數(shù)組來表示。我們以下圖來舉例:
對于這個地圖,我們就可以這樣表示:
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; } // 循環(huán)上圖 <div> arr.map((item, itemIndex) => { return item.map((child, childIndex) => { return <div> {showContent(child, itemIndex, childIndex)} </div> }) }) </div>
如何統(tǒng)計(jì)最大路徑得分?jǐn)?shù)以及相應(yīng)方案?
根據(jù)游戲規(guī)則我們知道,的移動的大方向
一定是向上的(因?yàn)榻K點(diǎn)永遠(yuǎn)在第一層,起點(diǎn)永遠(yuǎn)在最后一層),
在大方向上,對于每個單元格的移動方向又細(xì)分了3個方向(向左、向上、向左上)。
對于移動方向的規(guī)則這個是基本點(diǎn),所以一定不能忘了。有了這個基本點(diǎn),我們繼續(xù)往下分析。
對于E點(diǎn)來說,他的答案一定可以通過它右邊的綠色框
與下面的綠色框
來得出答案。比如現(xiàn)在來求一下S到E點(diǎn)的最大路徑得分?jǐn)?shù),下面這個等式是一定成立的:
S點(diǎn) 到 E點(diǎn)的最大得分?jǐn)?shù) = Math.max(S點(diǎn)到E點(diǎn)右側(cè)的綠色框的得分?jǐn)?shù), S點(diǎn)到E點(diǎn)下側(cè)的綠色框的得分?jǐn)?shù))
根據(jù)上面的等式成立,所以我們思路就可以轉(zhuǎn)變到S點(diǎn)到任意一點(diǎn)(除障礙物外)的最大得分?jǐn)?shù)以及相應(yīng)的方案數(shù)
。
我們可以使用二維數(shù)組
的方式來存儲每個單元格對應(yīng)的答案。
let arr = [ ['E', '2', '3'], ['2', 'X', '2'], ['1', '2', 'S'] ]; let pathsWithMaxScore = () => { let n = arr.length; // 聲明二維數(shù)組dp來存儲每個單元格對應(yīng)的答案 // 其中dp[i][j] 代表 單元格 // dp[i][j][0] 代表 s點(diǎn)到當(dāng)前單元格的最大路徑得分 // dp[i][j][1] 代表 s點(diǎn)到當(dāng)前單元格的最大路徑得分對應(yīng)的方案數(shù) let dp = new Array(n).fill(0).map(() => new Array(n).fill(0).map(() => [-1, 0])); }
接著上面的思路,我們來求任意點(diǎn)對應(yīng)的最大路徑以及方案數(shù)。我們這里以下圖的b點(diǎn)為例。
let pathsWithMaxScore = () => { let n = arr.length; // 聲明二維數(shù)組dp來存儲每個單元格對應(yīng)的答案 // 其中dp[i][j] 代表 單元格 // dp[i][j][0] 代表 s點(diǎn)到當(dāng)前單元格的最大路徑得分 // dp[i][j][1] 代表 s點(diǎn)到當(dāng)前單元格的最大路徑得分對應(yīng)的方案數(shù) 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'){ // 根據(jù)題意,遇到障礙物就要繞行 continue; } // 當(dāng) i === n-1 && j === n - 2時(shí) // 此時(shí)位置正好是b點(diǎn) // 此時(shí)需要做出2件事... } } }
當(dāng)我們到達(dá)b點(diǎn)時(shí),我們需要考慮2件事:
- 1、什么時(shí)候應(yīng)該更新b點(diǎn)的最大得分?jǐn)?shù)?
- 2、b點(diǎn)的最大得分?jǐn)?shù)對應(yīng)的方案數(shù)應(yīng)該如何更新?
對于第一個問題,當(dāng)前循環(huán)到b點(diǎn)時(shí),b點(diǎn)一定知道能夠到達(dá)這個點(diǎn)的來源路徑(fromX, fromY)是哪些,當(dāng)dp[fromX][fromY][0] + arr[i][j] > dp[i][j][0]
成立時(shí),就可以b點(diǎn)原先存儲的最大數(shù)了。
對于第二個問題,又分為了2種情況。如果dp[fromX][fromY][0] + arr[i][j] === dp[i][j][0]
,說明這個新來的路徑也算是一條有效方案,此時(shí)應(yīng)該+1(dp[i][j][1] + 1)。還有一種就是 dp[fromX][fromY] + arr[i][j] > dp[i][j][0]
的時(shí)候,此時(shí)應(yīng)該將 dp[i][j][1] 更新為dp[fromX][fromY][1]。
let pathsWithMaxScore = () => { let n = arr.length; // 聲明二維數(shù)組dp來存儲每個單元格對應(yīng)的答案 // 其中dp[i][j] 代表 單元格 // dp[i][j][0] 代表 s點(diǎn)到當(dāng)前單元格的最大路徑得分 // dp[i][j][1] 代表 s點(diǎn)到當(dāng)前單元格的最大路徑得分對應(yīng)的方案數(shù) 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 = [ // 任意點(diǎn)的來源路徑集合 [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'){ // 根據(jù)題意,遇到障礙物就要繞行 continue; } // 當(dāng) i === n-1 && j === n - 2時(shí) // 此時(shí)位置正好是b點(diǎn),我們需要先遍歷能到達(dá)b點(diǎn)的路徑有哪些 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]); // 此時(shí)為什么要更新路徑?因?yàn)橹暗膸讞l路對應(yīng)的得分不是最大的呀 dp[i][j][1] = dp[fromX][fromY][1]; } } } } return dp[0][0]; // dp[0][0]的值是一個長度為2的數(shù)組,數(shù)組第0項(xiàng)時(shí)得分,第一項(xiàng)是方案數(shù) }
到此,我們的算法也就講完了。感興趣的小伙伴可以把這個算法吸收一下,或者自己喂個數(shù)據(jù)跑一下。
到此這篇關(guān)于基于JavaScript動態(tài)規(guī)劃編寫一個益智小游戲的文章就介紹到這了,更多相關(guān)JavaScript益智游戲內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
spring 定時(shí)任務(wù)@Scheduled詳解
這篇文章主要介紹了spring 定時(shí)任務(wù)@Scheduled的相關(guān)資料,文中通過示例代碼介紹的很詳細(xì),相信對大家的理解和學(xué)習(xí)具有一定的參考借鑒價(jià)值,有需要的朋友們下面來一起看看吧。2017-01-01Spring Boot 使用WebAsyncTask異步返回結(jié)果
這篇文章主要介紹了Spring Boot 使用WebAsyncTask異步返回結(jié)果的相關(guān)資料,需要的朋友可以參考下2018-02-02Java實(shí)現(xiàn)百度AOI數(shù)據(jù)的解析與轉(zhuǎn)換
Java作為一種成熟且廣泛應(yīng)用的編程語言,具有跨平臺、面向?qū)ο蟆踩愿叩忍攸c(diǎn),非常適合用于開發(fā)各種類型的應(yīng)用程序,本文為大家整理了基于Java的AOI數(shù)據(jù)解析與轉(zhuǎn)換的實(shí)現(xiàn)方法,需要的可以參考下2025-02-02Springboot+Shiro+Jwt實(shí)現(xiàn)權(quán)限控制的項(xiàng)目實(shí)踐
如今的互聯(lián)網(wǎng)已經(jīng)成為前后端分離的時(shí)代,所以本文在使用SpringBoot整合Shiro框架的時(shí)候會聯(lián)合JWT一起搭配使用,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09java system類使用方法示例 獲取系統(tǒng)信息
這篇文章主要介紹了java system類使用方法,該類中的方法都是靜態(tài)的。不能被實(shí)例化,沒有對外提供構(gòu)造函數(shù),該類可以獲取系統(tǒng)信息2014-01-01Mybatis-Plus中update()和updateById()將字段更新為null
本文主要介紹了Mybatis-Plus中update()和updateById()將字段更新為null,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08