一筆畫完破解(上)
一筆畫完是最近朋友給我推薦的一個小遊戲(微信小程式),遊戲規則是一筆畫完全部格子,不能重複,不能有空缺,休閒類遊戲,感覺挺有意思的,於是我就是想用java來幫我實現通關這個遊戲,遊戲介面如下。(第一篇部落格講怎麼破解,第二篇實現完全自動化)不管哪一關都可以用這個程式碼0.1s內自動破解找到答案!
附第一篇部落格結果動圖:
熟悉程式設計的朋友肯定會說用棧來儲存走過的位置,不難啊,難度確實不大,本篇主要是個人興趣,記錄分享一下,且難度不在演算法其實,因為時間複雜度最大的暴力破解也可以在0.1秒內破解完畢,更主要的是實現影象識別和自動化闖關。
問題分析:先把問題簡化,思考下面的問題怎麼解
很容易想到的是直接一筆畫完了,現在我們回過頭捋一下思維,我們是怎麼找到這個解的,首先有一個起點,先看他能往哪裡走,然後走一步試試,如果沒通關的話,繼續看看能往哪裡走。
於是首先得出暴力破解方式:使用遞迴,傳入當前位置,計算能往哪裡走,能走則呼叫自身,否則嘗試下一個方向,直到通關。大概思路如下如下
function step( 當前位置 ){
if 通關?
return
if 能向上走?
step( 當前位置.上面一格 )
if 能向下走?
step( 當前位置.下面一格 )
//同理左右一樣...
}
雖然這種演算法非常笨,但是一種解決方案了,演算法的優化我們在需要的時候在進行(對於計算機來說這點方格根本不是事),下面把問題轉換為計算機能理解的結構:
我用int 型別二維陣列陣列 來表示這個問題(如果想優化演算法,可以採用其他資料結構或自定義),我把不可以走的地方設定為-1,把可以走但沒走的地方可以設定為0,把走過的路線按照走的順序計數,比如起點為1,第二步為2,第三步為3...
畫了個醜醜的圖,勉強看一下
好了,思路有了,該怎麼解呢,程式碼如下
public void solve(int nowX, int nowY) { tryNum++; if(hasWin()) return; if(!hasWin && nowY > 0 && array[nowX][nowY - 1] == 0) { //System.out.println("←"); array[nowX][nowY - 1] = ++step; solve(nowX, nowY - 1); }//left if(!hasWin && nowY < column-1 && array[nowX][nowY + 1] == 0) { //System.out.println("→"); array[nowX][nowY +1] = ++step; solve(nowX, nowY +1); }//right if(!hasWin && nowX > 0 && array[nowX - 1][nowY] == 0) { //System.out.println("↑"); array[nowX - 1][nowY] = ++step; solve(nowX - 1, nowY); }//up if(!hasWin && nowX < row -1 && array[nowX + 1][nowY] == 0) { //System.out.println("↓"); array[nowX + 1][nowY] = ++step; solve(nowX + 1, nowY); }//down if(!hasWin && array[nowX][nowY] != 1) { array[nowX][nowY] = 0; step--; }//如果走到這發現上下左右都不能走,並且還沒有勝利,那就是走的不對,本格子置0,退回上一步 }
大概的程式碼,hasWin是是否勝利,判斷方式是當前的步數==總的可以走的格子數?有為0的格子?未過關:過關:未過關,
雖然還有很多地方可以改進,但是可以求解了已經,遇到不會的關卡也可以自己破解了,程式碼放下面,可以執行,不過還有很多地方可以改進,但這個計算量不大,沒有改進的必要暫時,附垃圾程式碼:
public class Checkpoint {
//public Grid[][] grids;
public int row;
public int column;
public int startX;
public int startY;
/*public int nowX;
public int nowY;*/
public int testCount = 0;
int step = 1;
int[][] array;
boolean hasWin = false;
public int[][] getArray(){
return array;
}
/**
* array 0代表起點,1-n代表行走順序,-1代表不可達
* @param array
*/
public Checkpoint(int[][] array, int startX, int startY) {
this.array = array;
row = array.length;
column = array[0].length;
this.array[startX][startY] = 1;
this.startX = startX;
this.startY = startY;
}
public boolean hasWin() {
for(int i = 0; i < row; i++) {
for(int j = 0; j < column; j++) {
if(array[i][j] == 0) {
return false;
}
}
}
hasWin = true;
return true;
}
public void solve(int nowX, int nowY) {
testCount++;
//MyPoint point = stack.peek();
if(hasWin())
return;
if(!hasWin && nowY > 0 && array[nowX][nowY - 1] == 0) {
//System.out.println("←");
array[nowX][nowY - 1] = ++step;
solve(nowX, nowY - 1);
}//left
if(!hasWin && nowY < column-1 && array[nowX][nowY + 1] == 0) {
//System.out.println("→");
array[nowX][nowY +1] = ++step;
solve(nowX, nowY +1);
}//right
if(!hasWin && nowX > 0 && array[nowX - 1][nowY] == 0) {
//System.out.println("↑");
array[nowX - 1][nowY] = ++step;
solve(nowX - 1, nowY);
}//up
if(!hasWin && nowX < row -1 && array[nowX + 1][nowY] == 0) {
//System.out.println("↓");
array[nowX + 1][nowY] = ++step;
solve(nowX + 1, nowY);
}//down
if(!hasWin && array[nowX][nowY] != 1) {
array[nowX][nowY] = 0;
step--;
}
}
public void print() {
System.out.println("try:" + testCount + " result:" + ( hasWin ?"Ok":"No Answer") + " step:" + step);
for(int i = 0; i < row; i++) {
for(int j = 0; j < column; j++) {
System.out.print(array[i][j] == 0 ? "□" : array[i][j] == -1 ? "■" : array[i][j]);
System.out.print("\t");
}
System.out.println("\n\n");
}
}
public void print_plus() {
//System.out.println("try:" + testCount + " result:" + ( hasWin ?"Ok":"No Answer"));
for(int i = 0; i < row; i++) {
for(int j = 0; j < column; j++) {
if(array[i][j] == -1)
System.out.print("■");
if(array[i][j] == step) {
System.out.print("End");
}else {
if(array[i][j] == 1) {
System.out.print("Start");
}
if(i < row - 1 && array[i][j] == array[i + 1][j] - 1)
System.out.print("↓");
if(i > 0 && array[i][j] == array[i - 1][j] - 1)
System.out.print("↑");
if(j < column - 1 && array[i][j] == array[i][j + 1] - 1)
System.out.print("→");
if(j> 0 && array[i][j] == array[i][j - 1] - 1)
System.out.print("←");
}
System.out.print("\t");
}
System.out.println("\n\n");
}
}
public void print_plus2() throws Exception {
TreeMap<Integer, MyPosition> map = new TreeMap<Integer,MyPosition>();
for(int i = 0; i < row; i++) {
for(int j = 0; j < column; j++) {
map.put(array[i][j], new MyPosition(i,j));
}
}
Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()) {
int key = (int) iterator.next();
for(int i = 0; i < row; i++) {
for(int j = 0; j < column; j++) {
if(array[i][j] < key) {
if(array[i][j] == -1)
System.out.print("■");
if(array[i][j] == step) {
System.out.print("End");
}else {
if(array[i][j] == 1) {
System.out.print("Start");
}
if(i < row - 1 && array[i][j] == array[i + 1][j] - 1)
System.out.print("↓");
if(i > 0 && array[i][j] == array[i - 1][j] - 1)
System.out.print("↑");
if(j < column - 1 && array[i][j] == array[i][j + 1] - 1)
System.out.print("→");
if(j> 0 && array[i][j] == array[i][j - 1] - 1)
System.out.print("←");
}
}else {
System.out.print(" ");
}
System.out.print("\t");
}
System.out.println("\n\n");
}
Thread.sleep(200);
for(int i = 0;i++ < 40;) {
System.out.println();
}
}
//System.out.println("try:" + testCount + " result:" + ( hasWin ?"Ok":"No Answer"));
for(int i = 0; i < row; i++) {
for(int j = 0; j < column; j++) {
if(array[i][j] == -1)
System.out.print("■");
if(array[i][j] == step) {
System.out.print("End");
}else {
if(array[i][j] == 1) {
System.out.print("Start");
}
if(i < row - 1 && array[i][j] == array[i + 1][j] - 1)
System.out.print("↓");
if(i > 0 && array[i][j] == array[i - 1][j] - 1)
System.out.print("↑");
if(j < column - 1 && array[i][j] == array[i][j + 1] - 1)
System.out.print("→");
if(j> 0 && array[i][j] == array[i][j - 1] - 1)
System.out.print("←");
}
System.out.print("\t");
}
System.out.println("\n\n");
}
}
public void caculate() {
solve(startX,startY);
}
}
輸出部分為了按照步數輸出,又想直接o(1)的時間尋找那個位置(實際上是自己懶得在寫一個順序查找了)所以使用了帶排序的雜湊:TreeMap,放進去之後,順序遍歷即可為順序輸出,空間置換時間嘛。
主函式:
public static void main(String[] args) throws Exception {
int[][] array = {
{0, -1, 0, 0, 0, -1},
{0, 0, 0, 0, 0, 0 },
{0, 0, 0, 0, 0, 0 },
{0, 0, 0, -1, 0, 1,},
{-1, 0, 0, 0, 0, 0},
{0, 0, 0, 0, -1, 0},
{0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0}};
array[0][1] = -1;
array[0][5] = -1;
array[3][3] = -1;
array[4][0] = -1;
array[5][4] = -1;
array[0][1] = -1;
array[1][1] = 1;
Checkpoint question = new Checkpoint(array,1,1);
question.caculate();
question.print_plus2();
}
垃圾程式碼風格,湊合著看,上面為237關的例子,我們執行發現,程式一執行就能算出一個解法了,因此暫不進行尋路演算法的優化(有興趣的同學可以找找規律,僅舉一個例子:終點的度為1,只可能存在一個終點,如果走了一步之後,發現有兩個或以上度為1的格子,那麼這一步一定走錯了,繼續探索也沒有意義了,提前返回上一個格子)。
雖然能夠1秒內解出來,但是顯然不滿足我們的需求,還要自己輸入行列個數,哪裡不能走,起點在哪裡,太麻煩了。
這一篇主要講破解方式,下一篇下一篇點這裡部落格我會分享一下怎麼用adb+java實現完全自動化闖關。