最初的準(zhǔn)備
首先得完成數(shù)據(jù)的錄入,及從掃雷的程序讀取界面數(shù)據(jù)成為我的算法可識別的數(shù)據(jù)
其次是設(shè)計掃雷的算法,及如何才能判斷格子是雷或者可以點擊鼠標(biāo)左鍵和中鍵。
然后將步驟2的到的結(jié)果通過我的程序?qū)崿F(xiàn)鼠標(biāo)的點擊動作
下面是一個成功的gif圖片,放在前面容易吸引人啊,哈哈。
首先實現(xiàn)第一步
將掃雷程序界面數(shù)據(jù)讀取并保存為我的代碼可識別的數(shù)據(jù)。我們知道程序界面的各個數(shù)字都有不同的顏色,那么我們可以通過這些顏色得到每個數(shù)字的特征碼,及我的程序可以通過這些特征碼識別這些數(shù)據(jù)。
什么是圖片的特征碼,及圖片和特征碼為一對一的關(guān)系,知道特征碼就能確定這個圖片是啥,因此我的程序內(nèi)置了特征碼,然后獲取雷區(qū)截圖,通過分析rgb值得到特征碼和已有的特征碼比較,相同則確定了該位置是個啥。我借鑒了該文章圖像識別技術(shù)的圖像灰化,及將一張彩色圖片灰化為兩個不同的值,及圖片二值化。使用如下算法
經(jīng)過我的處理可以得到下面的值,我只拿其中一個數(shù)字說明,下面是數(shù)字2做圖像灰化后得到的二值圖像碼
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,1,
1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,
1,1,0,0,0,1,1,1,1,0,0,0,1,1,1,1,
1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,
1,1,1,1,1,1,1,0,0,0,0,1,1,1,1,1,
1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,
1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,
1,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,
1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,
1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
得到所有圖像的特征碼如下,我用go語言做了處理
GridNumbers = map[GridDefine][]int{ // 每個標(biāo)識的特殊位置的顏色值,用于區(qū)分這些位置的值DefOne: {0, 1, 0, 1, 1, 1, 1, 1, 1, 1}, // 1DefTwo: {0, 0, 0, 0, 0, 0, 0, 1, 0, 1}, // 2DefThree: {0, 1, 0, 0, 0, 0, 0, 1, 0, 1}, // 3DefFour: {0, 1, 0, 1, 0, 1, 0, 1, 1, 1}, // 4DefFive: {0, 1, 0, 0, 1, 0, 1, 1, 0, 1}, // 5DefSix: {0, 0, 0, 0, 1, 1, 1, 1, 1, 1}, // 6DefSeven: {1, 1, 0, 1, 0, 1, 0, 1, 0, 1}, // 7DefEight: {0, 0, 0, 0, 0, 1, 0, 1, 0, 1}, // 8DefFlag: {0, 1, 1, 1, 1, 1, 1, 1, 1, 0}, // 紅旗DefMine: {0, 1, 0, 1, 0, 1, 0, 1, 0, 1}, // 地雷DefRedMine: {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 標(biāo)紅地雷,表示輸了DefClick: {1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, // 可點擊白板DefNotNeed: {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, // 不可點擊}
找到圖像識別方案,下面就說說算法吧
第一步,找到必定為雷的位置,如下圖所示,之所以能得出標(biāo)旗位置,是因為周圍有數(shù)字可以確定這是一個雷。因此有下面的準(zhǔn)則
可點擊數(shù) + 已經(jīng)標(biāo)旗數(shù) = 本格子雷數(shù),右鍵把可點擊位置全部標(biāo)雷
此時這些點已經(jīng)沒有意義,可以不考慮了
第二步,找到有不確定的位置,但是標(biāo)旗數(shù)已經(jīng)等于本格雷數(shù),所以該格子周圍可點擊處一定不是雷。所以還有一下準(zhǔn)則
標(biāo)旗數(shù) = 本格子雷數(shù),鼠標(biāo)中鍵點擊格子,點開可點擊位置
此時對于左邊的3右邊的2來說雷的數(shù)量已經(jīng)夠了,可以中鍵點擊這兩個格子。此時這些點已經(jīng)沒有意義,可以不考慮了
第三步,如果遍歷所有數(shù)字點,沒有滿足第一和第二步的情況,那么將除了第一步和第二步排除后的數(shù)字點列為不確定的點。此時需要相鄰2個點同時做判斷。下面介紹一種不確定狀態(tài)下標(biāo)雷技巧
對于中間的2上面3個點有二個雷,2右邊的1表示上面三個點中右邊二個點只能有一個雷,因此對于中間的2來說剩余的一個點一定是雷。下面是準(zhǔn)則。
兩個相鄰格子待標(biāo)雷數(shù)多的為A,待標(biāo)雷數(shù)少的為B
A待標(biāo)雷數(shù) - B待標(biāo)雷數(shù) = A可點擊數(shù) - AB相交位置數(shù)
表示A除了相交位置以外的可點擊位置一定全是雷
第四步,下面的情況我一定知道紅色位置不是雷,因為對于4而言下面二個點只能有一個雷,那么4左邊的2的已經(jīng)把雷確認(rèn)完了,所以紅色位置肯定不是雷,下面是準(zhǔn)則。
兩個相鄰格子A和B待標(biāo)雷數(shù)相同,A的可點擊數(shù)為a,格子B的可點擊數(shù)為b,兩個格子相交數(shù)為c
如果a = c,則格子B除開相交位置的可點擊位置一定不是雷
如果b = c,則格子A除開相交位置的可點擊位置一定不是雷
根據(jù)上面四個準(zhǔn)則,掃雷一定能進行到除了猜雷就沒轍的情況。意思是按照上面的準(zhǔn)則,全都沒法判定是雷不是雷的情況,那么只能乖乖的猜雷了。然而我多出查詢資料得到猜雷最多憑概率,沒有辦法做到一定成功。因此我的掃雷算法的猜雷只是簡單的在不確定點周圍隨機點一個左鍵。所以用我的程序自動猜雷是會出現(xiàn)猜錯的情況。下面公布我的代碼吧
package mainimport ("log""math/rand""reflect""time""unsafe""fmt""github.com/lxn/win" // 另外的源碼,github.com/CodyGuo/win
)type Pos struct {x int /* 表示一個點的橫坐標(biāo),縱坐標(biāo) */y int
}/* 定義格子內(nèi)容的類型,不直接用int避免使用時混亂 */
type GridDefine intconst ( /* 枚舉類型,標(biāo)記格子內(nèi)容 */DefClick GridDefine = iota // 可點擊白板DefOne // 1DefTwo // 2DefThree // 3DefFour // 4DefFive // 5DefSix // 6DefSeven // 7DefEight // 8DefFlag // 紅旗DefNotNeed // 不可點擊的空白,以及標(biāo)識無用的數(shù)字位置DefMine // 地雷DefRedMine // 標(biāo)紅地雷,表示輸了
)const (GameName = "掃雷" // 游戲窗體名稱GameHigh = 16 // 雷區(qū)高度GameWide = 30 // 雷區(qū)寬度GridLen = 16 // 雷區(qū)每個格子長寬//GameMine = 99 // 存在雷的個數(shù),貌似用不上了
)var (StartBtn win.POINT // 笑臉的位置,點擊可以開始游戲StartNum win.POINT // 標(biāo)記剩余雷數(shù)的位置StartMine win.POINT // 雷區(qū)起始位置GameHwnd win.HWND // 掃雷窗體對象GuessMineOk int // 1表示啟動啟動猜雷,0表示玩家自己猜雷TeachModel int // 1表示每一步都顯示操作,0表示不顯示每一步的操作CntNotSure int // 標(biāo)記不確定點個數(shù)flagStart int // 標(biāo)記是否已經(jīng)開局,如果開局則永遠(yuǎn)不會等于0NotSurePos [GameHigh * GameWide]Pos // 緩存一次掃描中所有不確定點位置GridSave [][]GridDefine // 保存數(shù)據(jù)的二維數(shù)組GridNumbers = map[GridDefine][]int{ // 每個標(biāo)識的特殊位置的顏色值,用于區(qū)分這些位置的值DefOne: {0, 1, 0, 1, 1, 1, 1, 1, 1, 1}, // 1DefTwo: {0, 0, 0, 0, 0, 0, 0, 1, 0, 1}, // 2DefThree: {0, 1, 0, 0, 0, 0, 0, 1, 0, 1}, // 3DefFour: {0, 1, 0, 1, 0, 1, 0, 1, 1, 1}, // 4DefFive: {0, 1, 0, 0, 1, 0, 1, 1, 0, 1}, // 5DefSix: {0, 0, 0, 0, 1, 1, 1, 1, 1, 1}, // 6DefSeven: {1, 1, 0, 1, 0, 1, 0, 1, 0, 1}, // 7DefEight: {0, 0, 0, 0, 0, 1, 0, 1, 0, 1}, // 8DefFlag: {0, 1, 1, 1, 1, 1, 1, 1, 1, 0}, // 紅旗DefMine: {0, 1, 0, 1, 0, 1, 0, 1, 0, 1}, // 地雷DefRedMine: {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 標(biāo)紅地雷,表示輸了DefClick: {1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, // 可點擊白板DefNotNeed: {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, // 不可點擊}GridSlice = make([]byte, GameWide*GridLen*GameHigh*GridLen*4) // 緩存截屏數(shù)據(jù),全局初始化,避免重復(fù)申請內(nèi)存
)/**
* 初始化數(shù)據(jù)
* 做準(zhǔn)備工作
**/
func init() {var RectPos, ClientPos win.RECT // 找出窗體左上,右下的坐標(biāo),以及窗體除標(biāo)題欄的寬高GameHwnd = win.FindWindow(win.StringToBSTR(GameName), win.StringToBSTR(GameName))if !win.ShowWindow(GameHwnd, win.SW_RESTORE) { // 激活窗口,如果當(dāng)前為最小化則還原窗體log.Fatal("請運行掃雷游戲...") /* 找不到游戲窗體,直接退出 */}win.UpdateWindow(GameHwnd) // 更新窗體win.GetWindowRect(GameHwnd, &RectPos) /* 得到窗體左上右下的坐標(biāo) */win.GetClientRect(GameHwnd, &ClientPos) /* 得到窗體處標(biāo)題欄以外的長和寬 */var leftX, topY = RectPos.Right - ClientPos.Right, RectPos.Bottom - ClientPos.BottomStartBtn.X, StartBtn.Y = (RectPos.Left+RectPos.Right)/2, topY+25 // 初始化開始游戲按鈕StartMine.X, StartMine.Y = leftX+10, topY+53 // 初始化雷區(qū)起始位置StartNum.X, StartNum.Y = leftX+15, topY+14 // 初始化數(shù)字區(qū)位置GridSave = make([][]GridDefine, GameHigh) // 產(chǎn)生二維數(shù)組保存雷區(qū)數(shù)據(jù)for i := 0; i < GameHigh; i++ { // 遍歷高GridSave[i] = make([]GridDefine, GameWide)}fmt.Print("1 [教學(xué)模式],0 [自動模式],請輸入:")fmt.Scanln(&TeachModel)if TeachModel == 0 { /* 如果是自動模式則需要選擇猜雷方法 */fmt.Print("1 [自動猜雷],0 [人工猜雷],請輸入:")fmt.Scanln(&GuessMineOk)} /* 如果是教學(xué)模式,則默認(rèn)人工猜雷 */
}/**
* 開始執(zhí)行
**/
func main() {var (i, j, flagSure, flagAuto int)win.SetCursorPos(StartBtn.X, StartBtn.Y) // 媽逼的只能鼠標(biāo)點擊把窗口激活了,試過win32 api不行啊win.MouseEvent(win.MOUSEEVENTF_LEFTDOWN|win.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)time.Sleep(time.Millisecond * 200) // 雙擊開始的笑臉win.MouseEvent(win.MOUSEEVENTF_LEFTDOWN|win.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)rand.Seed(time.Now().UnixNano()) //初始化隨機數(shù)種子log.Println("游戲開始!")for RefreshGrid() && GetRemainMineCnt() > 0 { /* 當(dāng)遇到紅色地雷 或 地雷 以及剩余雷數(shù)為0 */CntNotSure, flagSure = 0, 0for i = 0; i < GameHigh; i++ { // 遍歷高度for j = 0; j < GameWide; j++ { //遍歷寬度if GridSave[i][j] >= DefOne && GridSave[i][j] <= DefEight { /* 代表數(shù)字的位置 */switch flagPos, style := GetAroundCount(i, j, 0); style {case 1: /* 類型1表示周圍可點擊格子一定是雷 */for _, v := range flagPos {ClickPos(v, "right")}GridSave[i][j] = DefNotNeed /* 標(biāo)完雷,此位置已無效 */flagSure = 1 // 表示本次遍歷出現(xiàn)可點右鍵標(biāo)雷的點case 2: /* 類型2表示本格可以點鼠標(biāo)中鍵了 */ClickPos(Pos{x: j, y: i}, "center")GridSave[i][j] = DefNotNeed /* 點開一片區(qū)域,此位置已無效 */flagSure = 2 // 表示本次出現(xiàn)可點中鍵的點if RefreshGrid() == false { /* 點中鍵后需要刷新當(dāng)前界面避免重復(fù)點擊中鍵 */goto EndLoop // 當(dāng)然如果遇到地雷則退出循環(huán)}case 3: /* 周圍地雷已經(jīng)標(biāo)完,且不需要點擊中鍵 */GridSave[i][j] = DefNotNeed /* 點開一片區(qū)域,此位置已無效 */flagSure = 3 // 表示本次出現(xiàn)可點中鍵的點default:NotSurePos[CntNotSure].x = iNotSurePos[CntNotSure].y = jCntNotSure++ // 記錄本次遍歷所有無法確定雷的點}}} //GameWide} // GameHighif flagSure == 0 { /* 本次遍歷全是不確定的點 */if flagStart == 0 { /* 開局,如果沒有出現(xiàn)必定能判斷的點,則一直隨機點鼠標(biāo)左鍵 */ClickPos(Pos{x: rand.Intn(GameWide), y: rand.Intn(GameHigh)}, "left")continue // 開局點擊后如無法出現(xiàn)必定可以繼續(xù)的點,直接進入下一個循環(huán)}for i = 0; i < CntNotSure; i++ { // 遍歷不確定的點if GetAroundNotSureCount(NotSurePos[i].x, NotSurePos[i].y) {break // 如果對雷區(qū)有操作則需要重新整個界面掃雷}flagSure++ // 如果把不確定點全部遍歷則表示需要猜雷了}if flagSure == CntNotSure {/** 猜測某個點沒有雷,運氣成分,如需提高勝率可優(yōu)化下面代碼,下面是關(guān)于猜雷的思路* 1.(https://tieba.baidu.com/p/1761431400?red_tag=3267954760)* 上面是我看到比較靠譜的理論,由于計算機雖然笨但運算快,因此我打算實現(xiàn)這個方案* 2.死猜,及無論如何都不可能判定哪個是雷,那就只能隨機猜一個了* 3.這里還要注意一點,及剩余雷數(shù),有時候根據(jù)剩余雷數(shù)可以提高勝率* 按照上面3個步驟,還沒發(fā)確定是不是雷,媽逼只能靠運氣了* 到處找教程最終沒能找到一個好點的方案,還是隨機點擊一個位置**/if GuessMineOk == 1 { /* 自動猜雷是隨機點一個 */i = rand.Intn(CntNotSure) // 下面是最挫的猜雷方案,隨機找一個不確定點,在該點周圍隨機點一個點var tPos, _ = GetAroundCount(NotSurePos[i].x, NotSurePos[i].y, 1)i = rand.Intn(len(tPos)) // 在不確定列表中隨機找一個點,隨機點這個點周圍的一個可點擊點ClickPos(Pos{x: tPos[i].x, y: tPos[i].y}, "left")} else {if flagAuto == 0 {flagAuto = 1log.Println("請你猜雷吧,確認(rèn)后按空格鍵繼續(xù)...")WaitKeyboard(win.VK_SPACE) // 等待空格鍵按下并松開}time.Sleep(time.Millisecond * 500) // 玩家猜雷,可以允許延時}}} else { /* 將開局標(biāo)志賦值,此時flagStart代表已經(jīng)不是開局了 */flagStart = flagSureflagAuto = 0 // 經(jīng)歷了猜雷到不猜}}
EndLoop:if GetRemainMineCnt() == 0 {log.Println("你贏了比賽!")} else {log.Println("你輸了比賽!")}fmt.Println("請按回車退出...")fmt.Scanln() // 雙擊打開時避免最終一閃而逝!
}/**
* 找到不可確定點
* 與周圍的不可確定點一起
* 看能否確定一些雷和可點擊位置
* return,返回true表示一定點了數(shù)字或標(biāo)了雷
**/
func GetAroundNotSureCount(x, y int) bool {var (i, j, cntMix intclickPos, needMine = GetAroundCount(x, y, 1) /* 得到本點可點擊位置,以及剩余雷數(shù) */clickCnt = len(clickPos) // 可點擊數(shù)個數(shù)MorePos = [2]struct {//* 記錄在點a不在點b周圍可點擊的位置 */pos [8]Pos //最大也就8個點,數(shù)組完全夠用cnt int // 記錄點的個數(shù),用于遍歷時使用}{} // 多于點的位置,以及點的個數(shù)endClick = struct {k int // 最終需要點擊MorePos數(shù)組的哪一個key string // 鼠標(biāo)鍵值,因為有時候需要左鍵數(shù)字,有時候需要右鍵標(biāo)雷}{}posInNotSure = func(x1, y1 int) bool { //閉包,確定這個點在不確定列表中for i1 := 0; i1 < CntNotSure; i1++ {if NotSurePos[i1].x == x1 && NotSurePos[i1].y == y1 {return true}}return false})/* 只找x相同或y相同的點 */for i = x - 1; i <= x+1; i++ { // 以下雙層循環(huán)遍歷本點四周的點for j = y - 1; j <= y+1; j++ { /* 只會找上下左右4個點,并且本點是一個數(shù)字點,且必須在不確定列表中 */if i >= 0 && j >= 0 && i < GameHigh && j < GameWide && (i == x && j != y || i != x && j == y) && (GridSave[i][j] >= DefOne && GridSave[i][j] <= DefEight) && posInNotSure(i, j) {cntMix = 0 // 記錄兩個點重合可點擊位置個數(shù)/* 根據(jù)(x,y),(i,j)這兩個不確定點找還能標(biāo)雷或點擊的位置 */var nowPos, nowMine = GetAroundCount(i, j, 1) // 得到周圍的這個不確定點,周圍可點擊位置和待標(biāo)雷數(shù)MorePos[0].cnt = 0for _, v1 := range clickPos {endClick.k = 0 // 這里該變量作為標(biāo)記使用,避免定義太多變量了for _, v2 := range nowPos {if v1 == v2 {endClick.k = 1cntMix++ // 記錄相交的點個數(shù)break}}if endClick.k == 0 {MorePos[0].pos[MorePos[0].cnt] = v1MorePos[0].cnt++ // 記錄在clickPos中且不在nowPos中的點}} // range clickPosMorePos[1].cnt = 0for _, v1 := range nowPos {endClick.k = 0for _, v2 := range clickPos {if v1 == v2 {endClick.k = 1break}}if endClick.k == 0 {MorePos[1].pos[MorePos[1].cnt] = v1MorePos[1].cnt++ // 記錄在clickPos中且不在nowPos中的點}} // range nowPosendClick.k = -1 // 當(dāng)賦值其他數(shù)據(jù)時,表示一定需要點擊endClick.key = "right" // 因為只有nowMine == needMine才為left,設(shè)置默認(rèn)值if nowMine == needMine { // 兩個點待標(biāo)雷個數(shù)相同if cntMix == clickCnt { // 表示一個點全部在相交位置,此時另一個點附近不相交的點只能是數(shù)字endClick.k = 1} else if cntMix == len(nowPos) { // 同上,只是換了一個點而已endClick.k = 0}endClick.key = "left"} else if nowMine > needMine { // 待標(biāo)雷個數(shù)大的一方if nowMine-needMine == len(nowPos)-cntMix {endClick.k = 1 // 2個點待標(biāo)雷數(shù)相減 = 可點擊數(shù)大的點減去重合點的個數(shù),表示可點擊數(shù)多的點多出的位置一定全是雷}} else {if needMine-nowMine == clickCnt-cntMix {endClick.k = 0 // 同上,只是點不一樣而已}}if endClick.k >= 0 && MorePos[endClick.k].cnt > 0 {for i = 0; i < MorePos[endClick.k].cnt; i++ {ClickPos(MorePos[endClick.k].pos[i], endClick.key) // 需要操作的格子不是地雷則隨便搞}return true // 已經(jīng)點擊數(shù)字或標(biāo)雷,整個界面需要重新判定}} // end if} // end j} // end ireturn false // 當(dāng)前點沒有合適的判定點
}/**
* 找到x,y周圍8個點中
* 可點擊的點個數(shù),已經(jīng)標(biāo)為紅旗的個數(shù)
* 返回可點擊的坐標(biāo)位置,且返回當(dāng)前這個點的類型
* 類型有2=>需要點擊鼠標(biāo)中鍵,1=>需要把周邊的可點擊點標(biāo)小旗
* 剩下的類型需要更深層次的計算了
**/
func GetAroundCount(x, y, inTpye int) (flagPos []Pos, status int) {var (i, j intcntClick, cntFlag GridDefine /* 標(biāo)記可點擊,標(biāo)記紅旗 */)for i = x - 1; i <= x+1; i++ { // 以下雙層循環(huán)遍歷本點四周的點for j = y - 1; j <= y+1; j++ {if i >= 0 && j >= 0 && i < GameHigh && j < GameWide && (i != x || j != y) { // 剔除超過邊界點,以及x,y所在點if DefClick == GridSave[i][j] {flagPos = append(flagPos, Pos{x: j, y: i})cntClick++} else if DefFlag == GridSave[i][j] {cntFlag++}}} // y} // xif 1 == inTpye { /* 返回周圍可點擊點位置,并且返回當(dāng)前點剩余雷的個數(shù) */return flagPos, int(GridSave[x][y] - cntFlag)}if GridSave[x][y] == cntFlag {if cntClick == 0 { /* 如果可點擊數(shù)量為空,則當(dāng)前位置不需要點鼠標(biāo)中鍵 */return flagPos, 3}return flagPos, 2 /* 小旗個數(shù)等于本格子雷數(shù),點擊鼠標(biāo)中鍵 */}if cntClick+cntFlag == GridSave[x][y] {return flagPos, 1 /* 可點擊 + 小旗 = 本格子雷數(shù),表示可點擊一定全是雷 */}return flagPos, 0 /* 剩下的情況一定是可點擊格數(shù)大于本格剩余雷數(shù) */
}/**
* 傳入ClickTask對象
* 模擬鼠標(biāo)點擊某個位置
* 單擊左鍵中鍵右鍵
**/
func ClickPos(pos Pos, key string) {var NowPos = uintptr((pos.x*GridLen + 21) | (pos.y*GridLen+64)<<16)switch key {case "left": // 確定不是雷,則隨便點左鍵win.SendMessage(GameHwnd, win.WM_LBUTTONDOWN, 0, NowPos)win.SendMessage(GameHwnd, win.WM_LBUTTONUP, 0, NowPos)case "right":if GridSave[pos.y][pos.x] == DefClick { /* 右鍵位置為可點擊才點,否則不點,避免重復(fù)標(biāo)雷 */win.SendMessage(GameHwnd, win.WM_RBUTTONDOWN, 0, NowPos)win.SendMessage(GameHwnd, win.WM_RBUTTONUP, 0, NowPos)GridSave[pos.y][pos.x] = DefFlag // 并且此處標(biāo)記為地雷}case "center": // 中鍵點開一片區(qū)域win.SendMessage(GameHwnd, win.WM_MBUTTONDOWN, 0, NowPos)win.SendMessage(GameHwnd, win.WM_MBUTTONUP, 0, NowPos)}if TeachModel == 1 && flagStart != 0 { /* 開局以后的操作才顯示 */var tmpPos = win.POINT{X: int32(pos.x*GridLen + 21), Y: int32(pos.y*GridLen + 64)}win.ClientToScreen(GameHwnd, &tmpPos) /* 將相對窗體位置轉(zhuǎn)化為相對整個屏幕的位置 */win.SetCursorPos(tmpPos.X, tmpPos.Y)log.Printf("點擊鼠標(biāo)按鍵:%6s,請按空格鍵繼續(xù)...\n", key)WaitKeyboard(win.VK_SPACE) // 等待空格鍵按下并松開}
}/**
* 等待一個按鍵按下并松開
**/
func WaitKeyboard(key int32) {for win.GetKeyState(key) >= 0 { /* 有按鍵退出循環(huán) */time.Sleep(time.Millisecond * 100)}for win.GetKeyState(key) < 0 { /* 松開按鍵退出循環(huán) */time.Sleep(time.Millisecond * 100)}
}/**
* 用到獲取屏幕截圖代碼
* 已經(jīng)去掉robotgo,讓程序沒有dll依賴
* 截屏代碼摘自https://github.com/vova616/screenshot
* 這里需要學(xué)習(xí)指針轉(zhuǎn)換的操作,以及內(nèi)存拷貝的操作
**/
func RefreshGrid() bool {var (w, h int32 = GameWide * GridLen, GameHigh * GridLena, b, c, d, e int // 只是作為循環(huán)變量而已screen, screenMem win.HDCdib win.HBITMAPbi win.BITMAPINFOptr = unsafe.Pointer(uintptr(0))obj win.HGDIOBJtmpArr [10]int // 緩存那幾個點的值GrayControl = func(r, g, b byte) int { /* 灰化圖像,閾值為150 */if float32(r)*0.11+float32(g)*0.59+float32(b)*0.3 >= 150 {return 1}return 0})bi.BmiHeader.BiSize = uint32(reflect.TypeOf(bi.BmiHeader).Size())bi.BmiHeader.BiWidth = wbi.BmiHeader.BiHeight = -h /* Non-cartesian, please */bi.BmiHeader.BiPlanes = 1bi.BmiHeader.BiBitCount = 32bi.BmiHeader.BiCompression = win.BI_RGBbi.BmiHeader.BiSizeImage = uint32(4 * w * h)bi.BmiHeader.BiXPelsPerMeter = 0bi.BmiHeader.BiYPelsPerMeter = 0bi.BmiHeader.BiClrUsed = 0bi.BmiHeader.BiClrImportant = 0if screen = win.GetDC(0); screen == 0 {return false}defer win.ReleaseDC(0, screen)dib = win.CreateDIBSection(screen, &bi.BmiHeader, win.DIB_RGB_COLORS, &ptr, 0, 0)if dib == 0 || win.GpStatus(dib) == win.InvalidParameter {return false}defer win.DeleteObject(win.HGDIOBJ(dib))if screenMem = win.CreateCompatibleDC(screen); screenMem == 0 {return false}defer win.DeleteDC(screenMem)if obj = win.SelectObject(screenMem, win.HGDIOBJ(dib)); obj == 0 || obj == 0xffffffff {return false}defer win.DeleteObject(obj)if !win.BitBlt(screenMem, 0, 0, w, h, screen, StartMine.X, StartMine.Y, win.SRCCOPY) {return false // 截屏}hDrp := (*reflect.SliceHeader)(unsafe.Pointer(&GridSlice))hDrp.Data = uintptr(ptr) /* 將指針中的數(shù)據(jù)映射到[]byte中 *///hDrp.Len = int(w * h * 4)//hDrp.Cap = int(w * h * 4)for a = 0; a < GameHigh; a++ { // 遍歷高度for b = 0; b < GameWide; b++ { //遍歷寬度if DefNotNeed == GridSave[a][b] || DefFlag == GridSave[a][b] {continue /* 該點已沒意義 或 該點已經(jīng)標(biāo)記為地雷,所以不用計算 */}for c, e = 0, 0; c < 5; c++ { // 找到那幾個特殊的點,備注找到更少點確定值則可以越快得到數(shù)據(jù)d = (a*GridLen+7-c)*4*GameWide*GridLen + 4*(b*GridLen+c+7)tmpArr[e] = GrayControl(GridSlice[d+2], GridSlice[d+1], GridSlice[d])e++/* 注意本處的特征碼是根據(jù)灰化后的圖像得出,我也是花了九牛二虎之力才搞到的額 */d = (a*GridLen+c+9)*4*GameWide*GridLen + 4*(b*GridLen+2)tmpArr[e] = GrayControl(GridSlice[d+2], GridSlice[d+1], GridSlice[d])e++}for k, v := range GridNumbers { // 遍歷map,得到本格子的實際信息for c = 0; c < 10; c++ {if v[c] != tmpArr[c] {c = -1 // 標(biāo)識該位置與當(dāng)前v不匹配break}}if c != -1 { /* 如果z=-1表示沒有匹配到當(dāng)前v的特征值 */if k == DefMine || k == DefRedMine {return false /* 遇到標(biāo)紅的雷或者黑色的雷,游戲結(jié)束 */}GridSave[a][b] = kbreak}}} // 內(nèi)層for結(jié)束} // 外層for結(jié)束return true
}/**
* 從界面得到剩余雷的個數(shù)
**/
func GetRemainMineCnt() int {var (i, x, y intNum [3]intFlagCnt [7]int/* __0__* 5| |1* |__6__|* 4| |2* |__3__|* 按照上面的順序標(biāo)記一個數(shù)字* 相鄰顏色值相同則賦值為1,不同則賦值為0* 根據(jù)對應(yīng)的map匹配得到該位置具體數(shù)字**/FlagDot = map[int][]int{0: {1, 1, 1, 1, 1, 1, 0},1: {0, 1, 1, 0, 0, 0, 0},2: {1, 1, 0, 1, 1, 0, 1},3: {1, 1, 1, 1, 0, 0, 1},4: {0, 1, 1, 0, 0, 1, 1},5: {1, 0, 1, 1, 0, 1, 1},6: {1, 0, 1, 1, 1, 1, 1},7: {1, 1, 1, 0, 0, 0, 0},8: {1, 1, 1, 1, 1, 1, 1},9: {1, 1, 1, 1, 0, 1, 1},}NumPosX = []int32{StartNum.X, StartNum.X + 13, StartNum.X + 26} /* 三個數(shù)字左上角x坐標(biāo) */hdc = win.CreateDC(win.StringToBSTR("DISPLAY"), nil, nil, nil))defer win.DeleteDC(hdc) // 用完hdc對象要釋放for i = 0; i < 3; i++ {if win.GetPixel(hdc, NumPosX[i]+5, StartNum.Y) == win.GetPixel(hdc, NumPosX[i]+5, StartNum.Y+1) {FlagCnt[0] = 1} else {FlagCnt[0] = 0}if win.GetPixel(hdc, NumPosX[i]+9, StartNum.Y+4) == win.GetPixel(hdc, NumPosX[i]+9, StartNum.Y+5) {FlagCnt[1] = 1} else {FlagCnt[1] = 0}if win.GetPixel(hdc, NumPosX[i]+9, StartNum.Y+14) == win.GetPixel(hdc, NumPosX[i]+9, StartNum.Y+15) {FlagCnt[2] = 1} else {FlagCnt[2] = 0}if win.GetPixel(hdc, NumPosX[i]+5, StartNum.Y+18) == win.GetPixel(hdc, NumPosX[i]+5, StartNum.Y+19) {FlagCnt[3] = 1} else {FlagCnt[3] = 0}if win.GetPixel(hdc, NumPosX[i]+1, StartNum.Y+14) == win.GetPixel(hdc, NumPosX[i]+1, StartNum.Y+15) {FlagCnt[4] = 1} else {FlagCnt[4] = 0}if win.GetPixel(hdc, NumPosX[i]+1, StartNum.Y+4) == win.GetPixel(hdc, NumPosX[i]+1, StartNum.Y+5) {FlagCnt[5] = 1} else {FlagCnt[5] = 0}if win.GetPixel(hdc, NumPosX[i]+5, StartNum.Y+9) == win.GetPixel(hdc, NumPosX[i]+5, StartNum.Y+10) {FlagCnt[6] = 1} else {FlagCnt[6] = 0}for x = 0; x < 10; x++ {for y = 0; y < 7; y++ {if FlagCnt[y] != FlagDot[x][y] {y = -1 // 不匹配當(dāng)前數(shù)字,不必遍歷所有值break}}if -1 != y { // 如果全部匹配則就是這個數(shù)字了Num[i] = xbreak /* 找到數(shù)字值,不必再循環(huán) */}}}return Num[0]*100 + Num[1]*10 + Num[2] /* 轉(zhuǎn)換為剩余雷數(shù) */
}
這里我要說一下,我的程序可以教你掃雷。如下圖輸入1或0回車即可
如果選擇了教學(xué)模式,那么只能用人工猜雷。此時教學(xué)模式會把每一步鼠標(biāo)操作都展現(xiàn)出來,方便大家思考和學(xué)習(xí)。這時候按一下空格鍵才會自動進行下一步,而且會顯示鼠標(biāo)位置到底是右鍵標(biāo)雷還是左鍵點擊還是中鍵操作。你可以根據(jù)當(dāng)前操作分析周圍的點,看看我的程序是如何做到判斷一個位置是否有雷的。
剩下的自動模式無非就程序自動掃雷,此時會選擇自動猜雷還是人工猜雷,如果人工猜雷則需要等程序無法判定能否操作時由你自己去猜雷。自動猜雷就是隨便點了一個點而已。綜上所述,我的掃雷代碼算是完成了,除了猜雷沒有好的方案完成以外,其他都做的很不錯。希望給大家一些啟發(fā)。
這里是程序哈掃雷游戲下載程序
當(dāng)然如果你沒有csdn積分可以用百度云,密碼49nt
總結(jié)
以上是生活随笔為你收集整理的用算法去扫雷(go语言)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。