Java 实现扫雷与高胜率低耗时自动扫雷 AI (下)
上一篇博客介紹了本項(xiàng)目總體情況, 這一篇來(lái)介紹一下我實(shí)現(xiàn)的自動(dòng)掃雷 AI 算法. 本 AI 勝率比網(wǎng)上最高勝率的 AI 差 0.5% 左右. 不過(guò)本 AI 也不是沒(méi)有優(yōu)勢(shì), 它運(yùn)算速度很快 (強(qiáng)行有優(yōu)勢(shì) (ˉ▽ ̄~)), 平均 42 毫秒可以?huà)咄暌痪?Win XP 規(guī)則下的專(zhuān)家難度.
這篇博客會(huì)介紹一下我的思路和踩過(guò)的坑, 也會(huì)列出一些關(guān)于勝率的數(shù)據(jù). 希望能夠幫助其他萌新入個(gè)門(mén). 項(xiàng)目已經(jīng)開(kāi)源, 代碼也寫(xiě)了注釋, 鏈接放在文章最后.
先再次把最終成品的 AI 勝率等指標(biāo)羅列一下:
| 測(cè)試版本 | Win XP 規(guī)則, 專(zhuān)家難度 | Win 7 規(guī)則, 專(zhuān)家難度 |
| 測(cè)試局?jǐn)?shù) | 50,0000 局 | 50,0000 局 |
| 勝率 | 39.68% | 52.45% |
| 運(yùn)行總耗時(shí) | 21275 秒 | 33136 秒 |
| 每局平均耗時(shí) | 42 毫秒 | 66 毫秒 |
| 勝局的每局平均耗時(shí) | 57 毫秒 | 68 毫秒 |
注1: 測(cè)試 Win XP 規(guī)則是在半夜運(yùn)行的, 電腦除了掃雷沒(méi)有在運(yùn)行其他進(jìn)程; 而測(cè)試 Win 7 規(guī)則時(shí)我同時(shí)還在拿電腦辦公, 導(dǎo)致 Win 7 規(guī)則下耗時(shí)比 XP 多. 而實(shí)際上 Win XP 與 Win 7 應(yīng)該運(yùn)行時(shí)間差不多.
注2: 測(cè)試使用的掃雷程序是自己復(fù)現(xiàn)的 Win XP 與 Win 7 規(guī)則, 而非模擬鼠標(biāo)點(diǎn)擊原版掃雷窗口. 因?yàn)槟菢犹? 網(wǎng)上有大佬已經(jīng)測(cè)試過(guò), Win XP, Win 7 原版掃雷并無(wú)影響地雷分布的隱藏規(guī)則. 所以若真要在原版上測(cè)試, 結(jié)果應(yīng)該不會(huì)有較大偏差.
我在網(wǎng)上找到的勝率最高的掃雷 AI 是 ztxz16 大佬寫(xiě)的, Win XP 版本勝率 40.07%, Win 7 版本勝率 52.98%. 我的做法也是參考了 ztxz16 大佬的算法 (最強(qiáng)掃雷 AI 算法詳解 + 源碼分享 - ztxz16). B 站的 _黃歪歪 應(yīng)該也是他, 也發(fā)了些關(guān)于掃雷 AI 的視頻. 膜一波.
不過(guò) ztxz16 大佬的這個(gè)項(xiàng)目偏試驗(yàn)性, 我頂著沒(méi)有注釋的壓力把大佬開(kāi)源的源碼看了一遍, 看得出來(lái)有些地方他應(yīng)該是懶得優(yōu)化, 最終 AI 速度不是很快. 我本來(lái)想自己測(cè)試一遍他的 AI, 但跑了一晚上也只跑出來(lái)了幾千局還是幾萬(wàn)局 (捂臉).
該交代的差不多都交代完了, 下面開(kāi)始講我的做法.
高勝率低耗時(shí)自動(dòng)掃雷 AI 算法
術(shù)語(yǔ)與定義
統(tǒng)一一下一些操作的術(shù)語(yǔ)方便后續(xù)描述. 我不是掃雷圈的, 很多術(shù)語(yǔ)可能不知道, 所以如果以下操作本來(lái)就有自己的中文名稱(chēng), 提醒我我會(huì)改過(guò)來(lái)的!
| 鼠標(biāo)左鍵, 那個(gè)點(diǎn)開(kāi)格子的操作 | 挖掘 |
| 鼠標(biāo)右鍵, 那個(gè)放置小紅旗的操作 | 插旗 |
| 鼠標(biāo)雙鍵或中鍵, 那個(gè)自動(dòng)檢測(cè)周?chē)烁袷欠窨赏诰虻牟僮?/td> | 檢查周?chē)?/td> |
| 空白的, 沒(méi)點(diǎn)過(guò)的格子 | 未知格 |
| 已知的有數(shù)字 (包括 0) 的格子 (已挖開(kāi)且不是雷的格子) | 數(shù)字格 |
另外,
第一步: 開(kāi)局挖哪
開(kāi)局第一步點(diǎn)擊不同的地方也是會(huì)影響勝率的. 根據(jù) ztxz16 大佬的測(cè)試 (地表最強(qiáng)掃雷AI ? 編程探索掃雷極限勝率 - _黃歪歪), 開(kāi)局 Win XP 挖掘 ( 0 , 0 ) (0, 0) (0,0), Win 7 挖掘 ( 2 , 2 ) (2, 2) (2,2) (或?qū)ΨQ(chēng)的另外三個(gè)角落) 勝率最高.
我自己也對(duì) Win XP 與 Win7 大致測(cè)了一下 ( 0 , 1 ) (0, 1) (0,1), ( 1 , 0 ) (1, 0) (1,0), ( 1 , 1 ) (1, 1) (1,1) 等幾個(gè)位置, 確實(shí)如此, 勝率會(huì)下降 1% ~ 2% 左右.
| Win XP 開(kāi)局 ( 0 , 0 ) (0, 0) (0,0) | Win 7 開(kāi)局 ( 2 , 2 ) (2, 2) (2,2) |
不過(guò)至于為什么是角落勝率比中心高, 我猜可能是角落有更多可套用減法公式的場(chǎng)景 (減法公式在下一節(jié)有講). 減法公式本質(zhì)就是根據(jù)相鄰兩個(gè)數(shù)字格的一側(cè)去推斷另一側(cè). 而邊緣的格子天然已知靠邊的一側(cè)無(wú)雷, 因而可以更容易地推斷出另一側(cè)雷的個(gè)數(shù).
第二步: 基于定義與定式
用到了兩個(gè)基本公式或定式:
第一, 僅基于一格判斷: 即根據(jù)目標(biāo)數(shù)字格的數(shù)字與其周?chē)烁?(角落的話(huà)不滿(mǎn)八格) 的狀態(tài), 判斷該數(shù)字格周?chē)烁竦奈粗袷遣皇侨珵槔谆蛉粸槔? 這是掃雷最基本的公式. 其邏輯其實(shí)就是鼠標(biāo)左右雙鍵檢查周?chē)倪壿?
第二, 基于相鄰兩格: 即使用減法公式.
如圖, M M M, N N N 為兩個(gè)相鄰數(shù)字格, 兩者上下各有一塊互相影響的公共區(qū)域 P P P, Q Q Q, 左右則分別有互不影響的兩翼 A A A, B B B. 記地雷數(shù)量為 C C C, 顯然地雷數(shù)量 C C C 符合如下等式:
{ C A r o u n d M = C A + C P + C Q C A r o u n d N = C B + C P + C Q \begin{cases} C_{AroundM} = C_A + C_P + C_Q \\ C_{AroundN} = C_B + C_P + C_Q \end{cases} {CAroundM?=CA?+CP?+CQ?CAroundN?=CB?+CP?+CQ??
兩式相減即得減法公式:
C A r o u n d M ? C A r o u n d N = C A ? C B C_{AroundM} - C_{AroundN} = C_A - C_B CAroundM??CAroundN?=CA??CB?
即 M M M 與 N N N 的數(shù)字之差就是 A A A 與 B B B 雷數(shù)量之差.
舉個(gè)簡(jiǎn)單的例子: 如下方案例圖 1 兩個(gè)數(shù)字格 2 ? 1 = 1 2 - 1 = 1 2?1=1, 即藍(lán)色區(qū)域比綠色區(qū)域多一個(gè)雷, 而藍(lán)色區(qū)域三個(gè)格子有兩個(gè)已知不是雷, 故而可推得藍(lán)色區(qū)域有且只有一個(gè)雷, 這個(gè)雷只能在 * 處. 既然藍(lán)區(qū)有一個(gè)雷, 綠區(qū)就有 1 ? 1 = 0 1 - 1 = 0 1?1=0 個(gè)雷, 故而 N 處必為一個(gè)數(shù)字格.
| 案例圖 1 | 案例圖 2 |
而如果相鄰兩格的一側(cè)在棋盤(pán)邊緣, 如案例圖 2 所示, 藍(lán)區(qū)在棋盤(pán)外, 即藍(lán)區(qū)無(wú)雷, 而 M ? N = 0 M - N = 0 M?N=0, 故綠區(qū)均不為雷.
可見(jiàn)減法公式在邊緣有更多的使用場(chǎng)景, 這可能也是開(kāi)局要挖角落的原因之一.
關(guān)于減法公式更詳細(xì)的內(nèi)容可以參考這篇專(zhuān)欄: 掃雷新手判雷上路(一)——初步認(rèn)識(shí)減法公式及其衍生的最簡(jiǎn)單定式 - MsPVZ.ZSW.
截至這一步的勝率與耗時(shí)
如果僅僅使用基本定式, 遇到定式無(wú)法解決的就投降判負(fù), 勝率在 5% 左右 (畢竟如果開(kāi)局就點(diǎn)了個(gè) ‘1’, 那就直接無(wú)了).
而如果僅使用基本定式, 遇到定式無(wú)法解決的就瞎猜, 勝率就已經(jīng)有 23% 了.
平均每局耗時(shí)在 4ms 以下, 沒(méi)精確測(cè)過(guò). 基于定式的算法雖然使用場(chǎng)景較為受限, 但效率非常高, 掃完一整局的時(shí)間復(fù)雜度也就 O ( S ) O(S) O(S).
第三步: 基于每個(gè)格子可能有雷的概率
針對(duì)某一棋局局面, 每個(gè)未知格有雷的概率當(dāng)然是可以計(jì)算出來(lái)的, 即
目 標(biāo) 未 知 格 含 雷 概 率 = 目 標(biāo) 未 知 格 含 雷 的 方 案 數(shù) 量 局 面 所 有 可 行 方 案 數(shù) 量 目標(biāo)未知格含雷概率 = \frac{目標(biāo)未知格含雷的方案數(shù)量}{局面所有可行方案數(shù)量} 目標(biāo)未知格含雷概率=局面所有可行方案數(shù)量目標(biāo)未知格含雷的方案數(shù)量?
掃的時(shí)候誰(shuí)有雷的概率最低就掃誰(shuí).
局面所有可行方案的計(jì)算方法用回溯法即可. 當(dāng)然直接就對(duì)所有未知格進(jìn)行回溯的話(huà)復(fù)雜度有 O ( 2 S ) O(2^S) O(2S) 自然受不了, 所以這一步的算法主要難點(diǎn)是剪枝.
如何剪枝, 可以用下面這張圖很直觀地展現(xiàn)出來(lái):
在上圖中, 每個(gè)未知格上都被打上了該格子的非雷概率 (注意, 這張圖上展示的是 “非雷概率”, 也就是 1 ? P 有 雷 1 - P_{有雷} 1?P有雷?), 且與數(shù)字格相鄰的未知格均被用彩色高亮了出來(lái); 而不與數(shù)字格相鄰的未知格則均用灰色標(biāo)識(shí).
不知道怎么取名, 這里我姑且將所有相近的 (不一定緊挨)、相同顏色的未知格集合稱(chēng)作一個(gè) “連通分量”, 而剩下所有灰色格子姑且稱(chēng)之為 “孤立格”. 而本小節(jié)最重要的兩個(gè)步驟就是①計(jì)算出所有連通分量, ②基于連通分量計(jì)算出每個(gè)未知格的有雷概率.
計(jì)算連通分量
回溯法剪枝其實(shí)就是要分隔出盡可能短的連通分量, 然后在每個(gè)連通分量上回溯出所有可能方案, 最后合并所有連通分量的方案.
首先不太嚴(yán)謹(jǐn)?shù)囟x一下 “連通分量”. 在不考慮剩余雷數(shù)制約的前提下, 如果一個(gè)未知格 A 最終挖開(kāi)的不同狀態(tài) (數(shù)字或雷) 會(huì)影響另一個(gè)未知格 B 不同狀態(tài)的概率分布 (反過(guò)來(lái) B 的狀態(tài)也會(huì)影響 A), 則認(rèn)為 A、B 同屬一個(gè)連通分量. 且若 A、B 同屬一個(gè)連通分量, B、C 同屬一個(gè)連通分量, 則 A、B、C 同屬一個(gè)連通分量.
放到實(shí)際計(jì)算中, 其實(shí)就很簡(jiǎn)單:
用上述方法計(jì)算得的連通分量?jī)?nèi)部其實(shí)依然可能存在互不影響的部分, 可以進(jìn)一步優(yōu)化, 將一個(gè)連通分量拆成多個(gè)更短的連通分量.
優(yōu)化方法也很簡(jiǎn)單, 如果已經(jīng)能確定某個(gè)未知格是雷, 請(qǐng)把他插上小紅旗 (網(wǎng)上看了很多 AI 不喜歡插旗). 如下兩張圖所示.
| 不插旗時(shí)找出的連通分量 (深紅) | 插了旗后找出的連通分量 (青色、深紅) |
計(jì)算有雷概率
(公式敲的我頭昏腦脹, 如有錯(cuò)誤請(qǐng)指正)
回溯枚舉所有 K K K 個(gè)連通分量, 可以得到如下結(jié)果:
對(duì)于 ? c ≥ 0 \forall c \ge 0 ?c≥0, ? i ∈ [ 1 , K ] \forall i \in [1, K] ?i∈[1,K], ? j ∈ [ 1 , L e n ( C C i ) ] \forall j \in [1, Len(CC_i)] ?j∈[1,Len(CCi?)],
- T o t a l C n t ( i , c ) TotalCnt_{(i, c)} TotalCnt(i,c)? - 當(dāng)連通分量 C C i CC_i CCi? 中一共有 c c c 個(gè)雷時(shí)的所有可行方案?jìng)€(gè)數(shù).
- M i n e C n t ( i , j , c ) MineCnt_{(i, j, c)} MineCnt(i,j,c)? - 當(dāng)連通分量 C C i CC_i CCi? 中一共有 c c c 個(gè)雷時(shí), 屬于 C C i CC_i CCi? 的格子 C e l l ( i , j ) Cell_{(i, j)} Cell(i,j)? 在所有 T o t a l C n t ( i , c ) TotalCnt_{(i, c)} TotalCnt(i,c)? 個(gè)可行方案中有雷的次數(shù).
由上述信息可以計(jì)算, 前 k k k 個(gè)連通分量 C C 1 ~ C C k CC_1 \sim CC_k CC1?~CCk? 作為一個(gè)整體一共有 c c c 個(gè)雷時(shí)的方案數(shù):
M u l t i C n t ( 1 ~ k , c ) = ∑ s = 0 c M u l t i C n t ( 1 ~ k ? 1 , s ) × T o t a l C n t ( k , c ? s ) ( 1 ) = ∑ s = 0 c M u l t i C n t ( 1 ~ i ? 1 , s ) × M u l t i C n t ( i ~ k , c ? s ) , i ∈ [ 2 , k ] ( 2 ) \begin{aligned} MultiCnt_{(1 \sim k, c)} & = \sum_{s = 0}^c MultiCnt_{(1 \sim k - 1, s)} \times TotalCnt_{(k, c - s)} & (1) \\ & = \sum_{s = 0}^c MultiCnt_{(1 \sim i - 1, s)} \times MultiCnt_{(i \sim k, c - s)}, i \in [2, k] & (2) \end{aligned} MultiCnt(1~k,c)??=s=0∑c?MultiCnt(1~k?1,s)?×TotalCnt(k,c?s)?=s=0∑c?MultiCnt(1~i?1,s)?×MultiCnt(i~k,c?s)?,i∈[2,k]?(1)(2)?
在實(shí)際編程中, 會(huì)涉及大量計(jì)算 M u l t i C n t ( except? i , c ) MultiCnt_{(\text{except }i, c)} MultiCnt(except?i,c)?. 使用上面 ( 1 ) (1) (1) 動(dòng)態(tài)規(guī)劃, 使用 ( 2 ) (2) (2) 從左右雙向記憶化, 能節(jié)約點(diǎn)計(jì)算.
然后對(duì)于所有不屬于任何連通分量的孤立格, 我們也要計(jì)算它們的可行方案數(shù). 若 b b b 個(gè)孤立格中有 c c c 個(gè)雷, 則這些孤立格的可行方案數(shù)為:
I s o l C n t ( b , c ) = I s o l C n t ( b ? 1 , c ) + I s o l C n t ( b ? 1 , c ? 1 ) IsolCnt_{(b, c)} = IsolCnt_{(b - 1, c)} + IsolCnt_{(b - 1, c - 1)} IsolCnt(b,c)?=IsolCnt(b?1,c)?+IsolCnt(b?1,c?1)?
實(shí)際實(shí)現(xiàn)中我直接打了個(gè) 16 × 30 × 99 16 \times 30 \times 99 16×30×99 的表.
于是可以計(jì)算, 所有 b b b 個(gè)孤立格與指定連通分量集合 T T T 在有 c c c 個(gè)雷的情況下的可行方案數(shù)為:
C n t ( T , c ) = ∑ s = 0 c M u l t i C n t ( T , s ) × I s o l C n t ( b , c ? s ) Cnt_{(T, c)} = \sum_{s = 0}^c MultiCnt_{(T, s)} \times IsolCnt_{(b, c - s)} Cnt(T,c)?=s=0∑c?MultiCnt(T,s)?×IsolCnt(b,c?s)?
于是, 對(duì)于任意連通分量的任意格子 C e l l ( i , j ) Cell_{(i, j)} Cell(i,j)?, 一共 c c c 個(gè)雷, K K K 個(gè)連通分量, 其有雷概率為:
M i n e P r o b ( i , j ) = ∑ s = 0 c C n t ( except? i , s ) × M i n e C n t ( i , j , c ? s ) C n t ( 1 ~ K , c ) MineProb_{(i, j)} = \frac{\sum_{s = 0}^c Cnt_{(\text{except }i, s)} \times MineCnt_{(i, j, c - s)}}{Cnt_{(1 \sim K, c)}} MineProb(i,j)?=Cnt(1~K,c)?∑s=0c?Cnt(except?i,s)?×MineCnt(i,j,c?s)??
有了每個(gè)連通分量的格子的概率, 孤立格子的概率就很好算了:
M i n e P r o b isol = c ? ∑ i ∑ j M i n e P r o b ( i , j ) b MineProb_{\text{isol}} = \frac{c - \sum_{i}\sum_{j}MineProb_{(i, j)}}{b} MineProbisol?=bc?∑i?∑j?MineProb(i,j)??
由此我們就計(jì)算出了所有未知格的有雷概率.
附加小策略小技巧
當(dāng)有雷概率最低的未知格有不止一個(gè)時(shí), 也有些小策略可以增加勝率. 經(jīng)過(guò)實(shí)際測(cè)試, 目前我采用了以下策略, 能提升 2% 左右:
截至這一步的勝率與耗時(shí)
這一步的算法的使用場(chǎng)景是覆蓋了基本定式那一步的, 即定式能做的概率也能做. 但定式速度快, 所以仍有存在意義. 測(cè)了十來(lái)萬(wàn)盤(pán), “定式 + 概率” 策略的勝率在 36% ~ 37% 左右, 平均每局耗時(shí) 6ms.
(一開(kāi)始我沒(méi)有將連通分量的不同雷數(shù)分開(kāi)考慮, 而是根據(jù)連通分量的平均雷數(shù)來(lái)計(jì)算, 導(dǎo)致勝率只有 32%, 可見(jiàn)近似算法差距還是很大的.)
而加上了那些小策略后, 勝率提升到了 38.5%.
極端情況:
到這里你可能有疑問(wèn), 剪枝確實(shí)很高效, 但如果遇到極端情況不是依然得爆炸?
答案是肯定的. 但是經(jīng)過(guò)我這么多次的測(cè)試, 基本上 1,0000 局里也很難遇上極端情況.
但如果您真的很迫切地想看我的 AI 出丑也沒(méi)關(guān)系, 我親自在項(xiàng)目根目錄放了一個(gè)難以剪枝的殘局案例, 保證讓您風(fēng)扇起飛.
第四步: 基于對(duì)下一局面的有限預(yù)測(cè)
這一步是我瞎想的, 沒(méi)想到真的有用. 想到這一步的主要原因是, 無(wú)雷概率高不完全等于獲勝概率高; 而計(jì)算勝率又絕大部分情況下不可行. 于是就想著整個(gè)折中方案.
這一步是針對(duì)上一步有雷概率計(jì)算出現(xiàn)多個(gè)格子有最低有雷概率的情況. 本策略會(huì)覆蓋上面說(shuō)的小策略小技巧, 但又由于本策略復(fù)雜度不低, 所以當(dāng)判斷發(fā)現(xiàn)計(jì)算量過(guò)大時(shí)還是采用之前的小策略小技巧.
原理是, 對(duì)于每個(gè)有雷概率最低的格子, 計(jì)算: 選擇該格子后, 可被百分百確定的其他未知格的期望數(shù)量. 方法是, 枚舉格子的所有可能性 (0 ~ 8, 雷), 使用上一步的概率計(jì)算這一假設(shè)的新局面中所有格子的非雷概率. 最后統(tǒng)計(jì)每個(gè)新局面必然非雷的格子個(gè)數(shù), 計(jì)算一下它們的平均值.
最后我們選擇期望值最高的格子.
截至這一步的勝率與耗時(shí)
測(cè)了萬(wàn)把盤(pán), 勝率達(dá)到 39.3%, 平均每局耗 8ms 多.
這一步我做的比較保守, 因?yàn)槲也恢烙袥](méi)有可能出現(xiàn) “非雷概率 A > B” 但 “勝率 A < B” 的情況.
第五步: 基于勝率 (最終殺招)
最后一招, 直接把每個(gè)未知格的勝率算出來(lái).
這里區(qū)分一下 “獲勝概率” 與第三步的 “有雷概率”. 勝率是指挖掘某個(gè)未知格的最終獲勝概率; 有雷概率是指當(dāng)前局面下未知格是雷的概率. 顯然, 當(dāng)前局面所有未知格中最高的勝率代表了這個(gè)殘局的整體勝率.
這世上沒(méi)有什么策略比直接算勝率更準(zhǔn)了, 但可惜它的復(fù)雜度過(guò)于恐怖, 所以我只在當(dāng)殘余未知格少于等于 12 時(shí)才會(huì)啟用這個(gè)策略.
算法原理還是動(dòng)態(tài)規(guī)劃. 設(shè) b b b 為當(dāng)前局面, b ( x , y ) b_{(x, y)} b(x,y)? 為將未知格 x x x 設(shè)為數(shù)字 y y y 的下一局面. 另設(shè) C e l l W i n R a t e ( ) CellWinRate() CellWinRate() 為某格子的勝率, B o a r d W i n R a t e ( ) BoardWinRate() BoardWinRate() 為某局面的勝率, C n t ( ) Cnt() Cnt() 為某局面的所有可行方案?jìng)€(gè)數(shù).
B o a r d W i n R a t e ( b ) = Max x ( C e l l W i n R a t e ( b , x ) ) C e l l W i n R a t e ( b , x ) = ∑ y = 0 8 B o a r d W i n R a t e ( b ( x , y ) ) × C n t ( b ( x , y ) ) C n t ( b ) \begin{aligned} BoardWinRate(b) & = \underset{x}{\text{Max}}(CellWinRate(b, x)) \\ CellWinRate(b, x) & = \sum_{y=0}^{8}BoardWinRate(b_{(x, y)}) \times \frac{Cnt(b_{(x, y)})}{Cnt(b)} \end{aligned} BoardWinRate(b)CellWinRate(b,x)?=xMax?(CellWinRate(b,x))=y=0∑8?BoardWinRate(b(x,y)?)×Cnt(b)Cnt(b(x,y)?)??
每個(gè)局面 b b b 可以狀態(tài)壓縮, 用一個(gè) String 表示, 方便記憶化搜索.
勝率算法計(jì)算完后就很簡(jiǎn)單了, 勝率最高的格子隨便挑一個(gè)挖. 而且所有后續(xù)局面的勝率也在之前的計(jì)算中緩存過(guò)了, 從緩存里掏出來(lái)一路掃到結(jié)束即可.
我自己整理了幾個(gè) “無(wú)雷概率” 與 “獲勝概率” 區(qū)別的例子 (這里是無(wú)雷概率, 為了方便與勝率對(duì)比), 可以幫助理解一下:
更多對(duì)比案例可以在我的 Github 項(xiàng)目根目錄找到.
衍生
最后我稍微衍生了一下使用場(chǎng)景. 如果某個(gè)區(qū)域只可能有 c c c 個(gè)雷 (雷數(shù)可確定, 比如幸福三選一), 那這片區(qū)域也能套用勝率算法直接掃掉, 姑且叫之局部勝率. 這個(gè)操作不會(huì)影響總體勝率, 但能稍微提一丟丟速 (指爆雷重開(kāi)的速度… 早發(fā)現(xiàn)早治療, 早暴斃早投胎).
最終勝率與概率
已經(jīng)寫(xiě)在博文最上面了. 50 萬(wàn)局測(cè)試, 勝率 39.68%, 平均每局 42ms.
附: 探索百分比
測(cè)試 50 萬(wàn)盤(pán)的時(shí)候順便記錄了一下每一局不論輸贏都探索到了什么程度 (游戲結(jié)束前被掃開(kāi)的格子占所有格子的百分比). 雖然不知道有什么用但也列一下 (柱狀圖是我在命令行里畫(huà)的, 有點(diǎn)寫(xiě)意):
Win XP 規(guī)則專(zhuān)家難度 50,0000 局: A 占比 | | | | | | M M | M M M | M M M | M M M M M M M M M M M +---------------------------------------------> 探索程度0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100% Win 7 規(guī)則專(zhuān)家難度 50,0000 局: A 占比 | | | | M | M | M | M M | M M | M M M M M M M M M M M +---------------------------------------------> 探索程度0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100%這么看確實(shí) Win XP 下開(kāi)局勸退得有點(diǎn)離譜.
源碼
本博客僅發(fā)布于 Github IO: https://xienaoban.github.io/posts/62679.html
和 CSDN: https://blog.csdn.net/XieNaoban/article/details/112424633
其他都是盜的.
項(xiàng)目源碼 Github: https://github.com/XieNaoban/Minesweeper
(喜歡的話(huà) Star 一下呀 (づ ̄3 ̄)づ╭?~)
不會(huì)用 Github 的萌新也可以在這里下載: https://download.csdn.net/download/XieNaoban/14090898
主要有以下幾個(gè)文件:
AutoSweeper.java // 自動(dòng)掃雷 AI Gui.java // 掃雷 GUI 界面 Main.java // 執(zhí)行入口 MineSweeper.java // 掃雷游戲規(guī)則的實(shí)現(xiàn) WinXpSweeper.java // 通過(guò)監(jiān)視 Win XP 原版掃雷完成的規(guī)則實(shí)現(xiàn)其中 AutoSweeper.java 和 MineSweeper.java 寫(xiě)的比較上心, 有較為詳細(xì)的注釋, 代碼結(jié)構(gòu)也相對(duì)清晰. 別的幾個(gè)文件就寫(xiě)的比較寫(xiě)意, 酌情觀看.
總結(jié)
以上是生活随笔為你收集整理的Java 实现扫雷与高胜率低耗时自动扫雷 AI (下)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: ubuntu16.04下运行Drcom客
- 下一篇: -webkit-touch-callou