吴昊品游戏核心算法(新年特别篇)—— (转载自Github)(后篇)扫雷AI(概率预测+坦克算法+JAVA实现)

 

   吴昊这里接前篇,继续娓娓道来。

 为什么有时候连计算机也不知道该挖哪个雷?

 

 如 图所示,这两个没有标注的方块,我们知道肯定存在一个雷,而且两个位置的概率都是50%(我的意思是说考虑局部,也就是说事件只是这两个方块哪个方块有 雷),这样的话,就是连机器也木有办法了!觉得我大二的时候成功混入过点团队一个星期,Dian团队有一个组叫做量子组,在量子组里面我知道了有一种技术 叫做“量子通信”,尼玛NB啊!根据量子自旋的两种不同状态来标注密码,传递信息,这样,即使是敌方截获了密码,也只能得到——“我连续抛掷了N次硬币, 出现了a次正面(N>a)和(N-a)次方面”这样的几乎等同于无效的信息。这样,敌人就真的是一点办法都没有了。

 既然只能靠猜,我们怎样猜测呢?

 

   如图所示,从中间的3我们可以知道:上面3个都是地雷,然后我们可以标记。但是标记这些地雷不会给我们带来任何一些新信息:为了获取新的信息,我们必须去打开上些方块。在这13个可能的方块中去打开它,这里我们并不清楚哪一个是最好的。

用坦克算法可以解决发现11个可能的组合。如下:

 

 在这11个组合中的每一个组合都是等可能性的---所以我们可以为每一个方块中分配一个可能存在地雷的概率,通过计算有多少组合(在这11个组合中)包含地雷:

 

 我们最好的猜测是点击那些标记了“2”的方块:在这些可能性中,我们有82%的机率是正确的!

 扫雷与NP难

 扫雷是NP完全问题Minesweeper is NP-complete[32]

广义扫雷问题(general minesweeper problem)表述如下:给定一块被部分标定为数字或地雷的矩形区域,剩下一些格子还未打开,试确定在未打开的格子中是否存在某种形式的地雷分布,使已经出现的数字得到满足。换句话说,需要确定给出的数据是否相容。

 

由扫雷规则构逻辑 

电路元件(5张)

只要解决了这一问题,现实中的扫雷游戏便同样得到完美解决。例如要确定某个格子是否非雷,只需测试这一位置是雷时数据是否相容。如果不相容,就可以判定该位置安全。同理,若能验证某一格子为数字(0>8)时数据均不相容,也可判断出这一位置是雷。

广义扫雷问题具有NP完全性。首先,它肯定是一个NP问题,因为验证一个解(由雷分布推断出数字)可以用多项式时间完成。其次,利用扫雷规则可以构建出所有的逻辑电路元件,从而证明布林可满足性问题boolean satisfiability problem)可以约化为扫雷问题。因为布林可满足性问题已经被证明是NP完全问题,所以广义扫雷问题,或者说扫雷,也是NP完全问题。于是研究扫雷也成了研究NP完全问题的一个切入点。

 

 还有我们没有利用的条件吗?

 

   地雷数量。通常,这个信息对我们没有多大的用途,但是在一些残局中,可以保证我们猜测的安全性。比如:

 

 这里,我们有一个50-50的猜测,两个可能性都相等。 但如果地雷数量是1呢?此时双雷组合被淘汰了,只剩下一个可能性。我们可以安全地打开那周边的3个方块。意思就是说,我们可以确定左上角必然是有雷的(至于为什么,大家可以想想)

 

 残局的终极攻略:

 

 

  地雷数量是2.每个圆圈区域里面都给我们50-50的机会---且坦克算法不能在这里用了。

 当然,中间那个方块是安全的!

   为了修改算法去实现这些场景,当这里没有剩下任何方块的时候,在所有保留的方块中做一个递归,而不仅仅只是边上的方块

 这两个技巧共享了一个属性:他们都依赖于地雷计数器。然而读取地雷计数器不是一个直接的任务,我不会去尝试;相反地,这个程序在编写的时候记录了总的地雷数,然后在内部记录了剩余的地雷数

 优化后的坦克算法的优势:

   此时此刻,我确信这里已经没有可以再提升胜率的做法了。该算法利用每一块信息,然后只有当它证明地确信猜测是必要的时候才会失效。

这运行起来怎么样呢?我们会用高级关卡的胜率来作为一个基准。

  • 简单算法不可以解决它,除非我们非常幸运。
  • 坦克算法概率性地猜测用大概20%的胜率解决他。
  • 附加的两个残局技巧把胜率提升到了50%

 

  //源代码中除了AI算法以外还包括一些对于图形识别(OCR)的一些智能算法,所以,这里就不解析了,有时间的话,我还会将其拿过来品味一下。

   源代码:

import java.awt.*;
import java.awt.image.*;
import java.util.*;

/*
Initial setup to get working:
Change ScreenWidth, ScreenHeight
Make sure TOT_MINES is correct

Hopefully it works then
*/

public class MSolver{

  static BufferedImage screenShotImage(){
    try {
      Rectangle captureSize = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
      ScreenWidth = captureSize.width;
      ScreenHeight = captureSize.height;
      BufferedImage bufferedImage = robot.createScreenCapture(captureSize);
      return bufferedImage;
    }
    catch(Exception e) { e.printStackTrace(); }
    return null;
  }

  static int ScreenWidth = 1600;
  static int ScreenHeight = 900;


  static boolean isDark(int rgb){
    int red = (rgb >> 16) & 0xFF;
    int green = (rgb >> 8) & 0xFF;
    int blue = rgb & 0xFF;
    return red + green + blue < 120;
  }

  static int colorDifference(int r1, int g1, int b1, int r2, int g2, int b2){
    return Math.abs(r1 - r2) + Math.abs(b1 - b2) + Math.abs(g1 - g2);
  }


  static int BoardWidth = 0;
  static int BoardHeight = 1;
  static double BoardPix = 0;
  static int BoardTopW = 0;
  static int BoardTopH = 0;



  // Take a screenshot and try to figure out the board dimensions and stuff like that
  static void calibrate(){

    // Display this message
    System.out.println("Calibrating Screen...");

    BufferedImage bi = screenShotImage();
    bi.createGraphics();
    Graphics2D g = (Graphics2D)bi.getGraphics();


    int hh = 0; // boardheight of previous column
    int firh = 0; // position of first found
    int firw = 0;
    int lash = 0; // position of last found
    int lasw = 0;
    int tot = 0; // total number of crosses found


    for(int w = 0; w<ScreenWidth; w++){

      for(int h=0; h < ScreenHeight; h++){
        int rgb = bi.getRGB(w,h);

        if(isDark(rgb)){

          if(w<10 || h<10 || w>ScreenWidth-10 || h> ScreenHeight-10)
            continue;

          // look for the cross shape to indicate position on board
          // we consider it a cross if:
          // - the square is dark
          // - four selected pixels to the N,S,E,W are dark
          // - four selected pixels to the NE, SE, NW, SW are not dark
          if(isDark(bi.getRGB(w+7,h)))
          if(isDark(bi.getRGB(w-7,h)))
          if(isDark(bi.getRGB(w,h+7)))
          if(isDark(bi.getRGB(w,h-7)))
          if(isDark(bi.getRGB(w+3,h)))
          if(isDark(bi.getRGB(w-3,h)))
          if(isDark(bi.getRGB(w,h+3)))
          if(isDark(bi.getRGB(w,h-3)))
          if(!isDark(bi.getRGB(w-7,h-7)))
          if(!isDark(bi.getRGB(w+7,h-7)))
          if(!isDark(bi.getRGB(w-7,h+7)))
          if(!isDark(bi.getRGB(w+7,h+7)))
          if(!isDark(bi.getRGB(w-3,h-3)))
          if(!isDark(bi.getRGB(w+3,h-3)))
          if(!isDark(bi.getRGB(w-3,h+3)))
          if(!isDark(bi.getRGB(w+3,h+3))){
            
            g.setColor(Color.YELLOW); // for _calibrate.png
            g.fillRect(w-3,h-3,7,7);
            tot++;
            BoardHeight++;

            // Find the position of the first cross
            if(firh == 0){
              firh = h;
              firw = w;
            }

            // Note position of the last cross
            lash = h;
            lasw = w;
          }
        }


      }

      if(BoardHeight > 1){
        hh = BoardHeight;
        BoardHeight = 1;
      }
    }

    // Determine boardwidth from total and boardheight
    BoardHeight = hh;
    if(tot % (BoardHeight-1) == 0)
      BoardWidth = tot / (BoardHeight-1) + 1;
    else BoardWidth = 0;

    // Determine BoardPix by taking an average
    BoardPix = 0.5*((double)(lasw - firw) / (double)(BoardWidth-2))
             + 0.5*((double)(lash - firh) / (double)(BoardHeight-2));
    
    // Determine first cell position (where to click)
    int halfsiz = (int)BoardPix / 2;
    BoardTopW = firw - halfsiz + 3;
    BoardTopH = firh - halfsiz + 3;


    System.out.printf("BoardWidth=%d, BoardHeight=%d, BoardPix=%f\n", BoardWidth, BoardHeight, BoardPix);
    System.out.printf("BoardTopW=%d, BoardTopH=%d\n",BoardTopW, BoardTopH);


  }


  static int mouseLocX = ScreenWidth / 2;
  static int mouseLocY = ScreenHeight / 2;
  static void moveMouse(int mouseX, int mouseY) throws Throwable{

    int distance = Math.max(Math.abs(mouseX-mouseLocX), Math.abs(mouseY-mouseLocY));
    int DELAY = distance/4;
    int numSteps = DELAY / 5;

    double stepx = (double)(mouseX - mouseLocX) / (double)numSteps;
    double stepy = (double)(mouseY - mouseLocY) / (double)numSteps;

    for(int i=0; i<numSteps; i++){
      robot.mouseMove(mouseLocX + (int)(i*stepx), mouseLocY + (int)(i*stepy));
      Thread.sleep(5);
    }
    robot.mouseMove(mouseX, mouseY);
    mouseLocX = mouseX;
    mouseLocY = mouseY;
  }


  // Click on a given square, given i and j
  static void clickOn(int i, int j) throws Throwable{
    int mouseX = BoardTopW + (int)(j*BoardPix);
    int mouseY = BoardTopH + (int)(i*BoardPix);
    moveMouse(mouseX, mouseY);

    robot.mousePress(16);
    Thread.sleep(5);
    robot.mouseRelease(16);
    Thread.sleep(10);
  }


  // Manually flag some mine
  static void flagOn(int i, int j) throws Throwable{
    int mouseX = BoardTopW + (int)(j*BoardPix);
    int mouseY = BoardTopH + (int)(i*BoardPix);
    moveMouse(mouseX, mouseY);

    robot.mousePress(4);
    Thread.sleep(5);
    robot.mouseRelease(4);
    Thread.sleep(10);
  }


  // Click on it with both mouse buttons in order to "chord"
  static void chordOn(int i, int j) throws Throwable{
    int mouseX = BoardTopW + (int)(j*BoardPix);
    int mouseY = BoardTopH + (int)(i*BoardPix);
    moveMouse(mouseX, mouseY);

    robot.mousePress(4);
    robot.mousePress(16);
    Thread.sleep(5);
    robot.mouseRelease(4);
    robot.mouseRelease(16);
    Thread.sleep(10);
  }


  // Special method to try to separate 3 from 7
  // which conveniently are the same color
  static int detect_3_7(int[] areapix){

    // Assume it's length 225 and dimensions 15x15.
    // Classify each pixel as red or not.
    // Since we don't have to deal with 5, we can take a greater liberty
    // in deciding on red pixels.

    boolean redx[][] = new boolean[15][15];
    for(int k=0; k<225; k++){
      int i = k % 15;
      int j = k / 15;
      int rgb = areapix[k];
      int red = (rgb >> 16) & 0xFF;
      int green = (rgb >> 8) & 0xFF;
      int blue = rgb & 0xFF;

      if(colorDifference(red, green, blue, 170, 0, 0) < 100)
        redx[i][j] = true;
    }

    /*
for(int i=0; i<15; i++){
for(int j=0; j<15; j++){
if(redx[i][j]) System.out.print("x");
else System.out.print(" ");
}
System.out.println();
}
System.out.println();
*/


    // Look for this pattern in the 3 but not 7:
    // x x
    // x .
    /*
for(int i=0; i<14; i++){
for(int j=0; j<14; j++){
if(redx[i][j] && redx[i+1][j]
&& redx[i][j+1] && !redx[i+1][j+1])
return 3;
}
}
*/

    // . . .
    // x
    for(int i=0; i<13; i++){
      for(int j=0; j<13; j++){
        if(!redx[i][j] && !redx[i][j+1] && !redx[i][j+2] && redx[i+1][j+1])
          return 3;
      }
    }

    return 7;

  }


  // Try to read what number's in this position
  static int detect(BufferedImage bi, int i, int j){
    int mouseX = BoardTopW + (int)(j*BoardPix);
    int mouseY = BoardTopH + (int)(i*BoardPix);

    // Don't take one pixel, take a 15x15 area of pixels
    int areapix[] = new int[225];
    int count = 0;
    for(int ii = mouseX-7; ii <= mouseX+7; ii++)
      for(int jj = mouseY-7; jj <= mouseY+7; jj++){
        areapix[count] = bi.getRGB(ii,jj);
        count++;
      }


    boolean hasColorOfOneSquare = false;
    boolean hasColorOfBlank = false;
    boolean isRelativelyHomogenous = true;

    for(int rgb : areapix){
      int red = (rgb >> 16) & 0xFF;
      int green = (rgb >> 8) & 0xFF;
      int blue = rgb & 0xFF;

      // Detect death
      if(colorDifference(red, green, blue, 110,110,110) < 20)
        return -10;

      // Detect flagging of any sort
      if(colorDifference(red,green,blue,255,0,0) < 30)
        return -3;

      if(colorDifference(red, green, blue, 65,79,188) < 10){
        hasColorOfOneSquare = true;
      }
      if(blue > red && blue > green &&
          colorDifference(red, green, blue, 220,220,255) < 200){
        hasColorOfBlank = true;
      }
      if(colorDifference(red, green, blue, 167,3,5) < 20)
        return detect_3_7(areapix);
      if(colorDifference(red, green, blue, 29,103,4) < 20) return 2;
      if(colorDifference(red, green, blue, 0,0,138) < 20) return 4;
      if(colorDifference(red, green, blue, 124,1,3) < 20) return 5;
      if(colorDifference(red, green, blue, 7,122,131) < 20) return 6;
    }

    // Determine how 'same' the area is.
    // This is to separate the empty areas which are relatively the same from
    // the unexplored areas which have a gradient of some sort.
    {
      int rgb00 = areapix[0];
      int red00 = (rgb00 >> 16) & 0xFF;
      int green00 = (rgb00 >> 8) & 0xFF;
      int blue00 = rgb00 & 0xFF;
      for(int rgb : areapix){
        int red = (rgb >> 16) & 0xFF;
        int green = (rgb >> 8) & 0xFF;
        int blue = rgb & 0xFF;
        if(colorDifference(red, green, blue, red00, green00, blue00) > 60){
          isRelativelyHomogenous = false;
          break;
        }
      }
    }



    if(hasColorOfOneSquare && hasColorOfBlank)
      return 1;

    if(hasColorOfBlank && isRelativelyHomogenous)
      return 0;

    return -1;
  }


  static Scanner scanner = new Scanner(System.in);
  static Robot robot;
  static Random rand = new Random();


  // Internal representation of the board state as displayed on the screen.
  // 1-8 means that the square there is that number
  // 0 means that it's actually empty
  // -1 means it's not opened yet
  // -2 means it's outside the boundries of the board
  // -3 means a mine
  // -10 means that something went wrong and we should exit the program
  static int[][] onScreen = null;

  // List of squares in which we know there are mines
  static boolean[][] flags = null;

  static int numMines = 0;
  static int TOT_MINES = 99;


  // Remove the need for edge detection every fricking time
  static int onScreen(int i, int j){
    if(i < 0 || j < 0 || i > BoardHeight-1 || j > BoardWidth-1)
      return -10;
    return onScreen[i][j];
  }


  // take a screenshot and update the onScreen array
  static int updateOnScreen(){
    BufferedImage bi = screenShotImage();

    int numMines_t = 0;
    for(int i = 0; i < BoardHeight; i++){
      for(int j=0; j < BoardWidth; j++){

        int d = detect(bi,i,j);
        if(d == -10) return d; // death
        onScreen[i][j] = d;

        // Special case for flags
        if(d == -3 || flags[i][j]){
          onScreen[i][j] = -1;
          flags[i][j] = true;
        }
        if(d == -1){
          flags[i][j] = false;
        }
        

        // Update mines count
        if(flags[i][j]){
          numMines_t++;
        }

      }
    }

    //if(numMines_t < numMines - 2) exit();
    numMines = numMines_t;
    return 0;
  }


  // tries to detect problems with screenshotting
  static boolean checkConsistency(){
    for(int i=0; i<BoardHeight; i++){
      for(int j=0; j<BoardWidth; j++){
        
        int freeSquares = countFreeSquaresAround(onScreen, i, j);
        int numFlags = countFlagsAround(flags, i, j);

        if(onScreen(i,j) == 0 && freeSquares > 0){
          return false;
        }
        if((onScreen(i,j) - numFlags) > 0 && freeSquares == 0){
          return false;
        }

      }
    }

    return true;
  }


  // Handle clicking the first square
  static void firstSquare() throws Throwable{

    // Check that it's indeed the first square
    robot.mouseMove(0,0);
    Thread.sleep(20);
    updateOnScreen();
    robot.mouseMove(mouseLocX,mouseLocY);
    boolean isUntouched = true;
    for(int i=0; i<BoardHeight; i++){
      for(int j=0; j<BoardWidth; j++){
        if(onScreen(i,j) != -1)
          isUntouched = false;
      }
    }
    if(!isUntouched){
      return;
    }

    // Click the middle
    clickOn(BoardHeight/2-1, BoardWidth/2-1);
    clickOn(BoardHeight/2-1, BoardWidth/2-1);
    Thread.sleep(200);

  }


  // How many flags exist around this square?
  static int countFlagsAround(boolean[][] array, int i, int j){
    int mines = 0;

    // See if we're on the edge of the board
    boolean oU = false, oD = false, oL = false, oR = false;
    if(i == 0) oU = true;
    if(j == 0) oL = true;
    if(i == BoardHeight-1) oD = true;
    if(j == BoardWidth-1) oR = true;

    if(!oU && array[i-1][j]) mines++;
    if(!oL && array[i][j-1]) mines++;
    if(!oD && array[i+1][j]) mines++;
    if(!oR && array[i][j+1]) mines++;
    if(!oU && !oL && array[i-1][j-1]) mines++;
    if(!oU && !oR && array[i-1][j+1]) mines++;
    if(!oD && !oL && array[i+1][j-1]) mines++;
    if(!oD && !oR && array[i+1][j+1]) mines++;

    return mines;
  }

  // How many unopened squares around this square?
  static int countFreeSquaresAround(int[][] board, int i, int j){
    int freeSquares = 0;

    if(onScreen(i-1,j)==-1) freeSquares++;
    if(onScreen(i+1,j)==-1) freeSquares++;
    if(onScreen(i,j-1)==-1) freeSquares++;
    if(onScreen(i,j+1)==-1) freeSquares++;
    if(onScreen(i-1,j-1)==-1) freeSquares++;
    if(onScreen(i-1,j+1)==-1) freeSquares++;
    if(onScreen(i+1,j-1)==-1) freeSquares++;
    if(onScreen(i+1,j+1)==-1) freeSquares++;

    return freeSquares;
  }

  // A boundry square is an unopened square with opened squares near it.
  static boolean isBoundry(int[][] board, int i, int j){
    if(board[i][j] != -1) return false;

    boolean oU = false, oD = false, oL = false, oR = false;
    if(i == 0) oU = true;
    if(j == 0) oL = true;
    if(i == BoardHeight-1) oD = true;
    if(j == BoardWidth-1) oR = true;
    boolean isBoundry = false;

    if(!oU && board[i-1][j]>=0) isBoundry = true;
    if(!oL && board[i][j-1]>=0) isBoundry = true;
    if(!oD && board[i+1][j]>=0) isBoundry = true;
    if(!oR && board[i][j+1]>=0) isBoundry = true;
    if(!oU && !oL && board[i-1][j-1]>=0) isBoundry = true;
    if(!oU && !oR && board[i-1][j+1]>=0) isBoundry = true;
    if(!oD && !oL && board[i+1][j-1]>=0) isBoundry = true;
    if(!oD && !oR && board[i+1][j+1]>=0) isBoundry = true;

    return isBoundry;
  }


  // Attempt to deduce squares that we know have mines
  // More specifically if number of squares around it = its number
  static void attemptFlagMine() throws Throwable{

    for(int i=0; i<BoardHeight; i++){
      for(int j=0; j<BoardWidth; j++){
        
        if(onScreen(i,j) >= 1){
          int curNum = onScreen(i,j);

          // Flag necessary squares
          if(curNum == countFreeSquaresAround(onScreen,i,j)){
            for(int ii=0; ii<BoardHeight; ii++){
              for(int jj=0; jj<BoardWidth; jj++){
                if(Math.abs(ii-i)<=1 && Math.abs(jj-j)<=1){
                  if(onScreen(ii,jj) == -1 && !flags[ii][jj]){
                    flags[ii][jj] = true;
                    flagOn(ii,jj);
                  }
                }
              }
            }
          }


        }
      }
    }

  }


  // Attempt to deduce a spot that should be free and click it
  // More specifically:
  // Find a square where the number of flags around it is the same as it
  // Then click every empty square around it
  static void attemptMove() throws Throwable{

    boolean success = false;
    for(int i=0; i<BoardHeight; i++){
      for(int j=0; j<BoardWidth; j++){
        
        if(onScreen(i,j) >= 1){

          // Count how many mines around it
          int curNum = onScreen[i][j];
          int mines = countFlagsAround(flags,i,j);
          int freeSquares = countFreeSquaresAround(onScreen,i,j);


          // Click on the deduced non-mine squares
          if(curNum == mines && freeSquares > mines){
            success = true;

            // Use the chord or the classical algorithm
            if(freeSquares - mines > 1){
              chordOn(i,j);
              onScreen[i][j] = 0; // hack to make it not overclick a square
              continue;
            }

            // Old algorithm: don't chord
            for(int ii=0; ii<BoardHeight; ii++){
              for(int jj=0; jj<BoardWidth; jj++){
                if(Math.abs(ii-i)<=1 && Math.abs(jj-j)<=1){
                  if(onScreen(ii,jj) == -1 && !flags[ii][jj]){
                    clickOn(ii,jj);
                    onScreen[ii][jj] = 0;
                  }
                }
              }
            }

          }
        }
      }
    }

    if(success) return;

    // Bring in the big guns
    tankSolver();
  }


  // Exactly what it says on the tin
  static void guessRandomly() throws Throwable{
    System.out.println("Attempting to guess randomly");
    while(true){
      int k = rand.nextInt(BoardHeight*BoardWidth);
      int i = k / BoardWidth;
      int j = k % BoardWidth;

      if(onScreen(i,j) == -1 && !flags[i][j]){
        clickOn(i,j);
        return;
      }
    }
  }

 
  // TANK solver: slow and heavyweight backtrack solver designed to
  // solve any conceivable position! (in development)
  static void tankSolver() throws Throwable{

    // Be extra sure it's consistent
    Thread.sleep(100);
    robot.mouseMove(0,0);
    Thread.sleep(20);
    updateOnScreen();
    robot.mouseMove(mouseLocX,mouseLocY);
    //dumpPosition();
    if(!checkConsistency()) return;

    // Timing
    long tankTime = System.currentTimeMillis();

    ArrayList<Pair> borderTiles = new ArrayList<Pair>();
    ArrayList<Pair> allEmptyTiles = new ArrayList<Pair>();

    // Endgame case: if there are few enough tiles, don't bother with border tiles.
    borderOptimization = false;
    for(int i=0; i<BoardHeight; i++)
      for(int j=0; j<BoardWidth; j++)
        if(onScreen(i,j) == -1 && !flags[i][j])
          allEmptyTiles.add(new Pair(i,j));

    // Determine all border tiles
    for(int i=0; i<BoardHeight; i++)
      for(int j=0; j<BoardWidth; j++)
        if(isBoundry(onScreen,i,j) && !flags[i][j])
          borderTiles.add(new Pair(i,j));

    // Count how many squares outside the knowable range
    int numOutSquares = allEmptyTiles.size() - borderTiles.size();
    if(numOutSquares > BF_LIMIT){
      borderOptimization = true;
    }
    else borderTiles = allEmptyTiles;


    // Something went wrong
    if(borderTiles.size() == 0)
      return;


    // Run the segregation routine before recursing one by one
    // Don't bother if it's endgame as doing so might make it miss some cases
    ArrayList<ArrayList<Pair>> segregated;
    if(!borderOptimization){
      segregated = new ArrayList<ArrayList<Pair>>();
      segregated.add(borderTiles);
    }
    else segregated = tankSegregate(borderTiles);

    int totalMultCases = 1;
    boolean success = false;
    double prob_best = 0; // Store information about the best probability
    int prob_besttile = -1;
    int prob_best_s = -1;
    for(int s = 0; s < segregated.size(); s++){

      // Copy everything into temporary constructs
      tank_solutions = new ArrayList<boolean[]>();
      tank_board = onScreen.clone();
      knownMine = flags.clone();

      knownEmpty = new boolean[BoardHeight][BoardWidth];
      for(int i=0; i<BoardHeight; i++)
        for(int j=0; j<BoardWidth; j++)
          if(tank_board[i][j] >= 0)
            knownEmpty[i][j] = true;
          else knownEmpty[i][j] = false;


      // Compute solutions -- here's the time consuming step
      tankRecurse(segregated.get(s),0);

      // Something screwed up
      if(tank_solutions.size() == 0) return;


      // Check for solved squares
      for(int i=0; i<segregated.get(s).size(); i++){
        boolean allMine = true;
        boolean allEmpty = true;
        for(boolean[] sln : tank_solutions){
          if(!sln[i]) allMine = false;
          if(sln[i]) allEmpty = false;
        }

        
        Pair<Integer,Integer> q = segregated.get(s).get(i);
        int qi = q.getFirst();
        int qj = q.getSecond();

        // Muahaha
        if(allMine){
          flags[qi][qj] = true;
          flagOn(qi,qj);
        }
        if(allEmpty){
          success = true;
          clickOn(qi,qj);
        }
      }

      totalMultCases *= tank_solutions.size();

      
      // Calculate probabilities, in case we need it
      if(success) continue;
      int maxEmpty = -10000;
      int iEmpty = -1;
      for(int i=0; i<segregated.get(s).size(); i++){
        int nEmpty = 0;
        for(boolean[] sln : tank_solutions){
          if(!sln[i]) nEmpty++;
        }
        if(nEmpty > maxEmpty){
          maxEmpty = nEmpty;
          iEmpty = i;
        }
      }
      double probability = (double)maxEmpty / (double)tank_solutions.size();

      if(probability > prob_best){
        prob_best = probability;
        prob_besttile = iEmpty;
        prob_best_s = s;
      }

    }

    // But wait! If there's any hope, bruteforce harder (by a factor of 32x)!
    if(BF_LIMIT == 8 && numOutSquares > 8 && numOutSquares <= 13){
      System.out.println("Extending bruteforce horizon...");
      BF_LIMIT = 13;
      tankSolver();
      BF_LIMIT = 8;
      return;
    }

    tankTime = System.currentTimeMillis() - tankTime;
    if(success){
      System.out.printf(
          "TANK Solver successfully invoked at step %d (%dms, %d cases)%s\n",
          numMines, tankTime, totalMultCases, (borderOptimization?"":"*"));
      return;
    }


    // Take the guess, since we can't deduce anything useful
    System.out.printf(
        "TANK Solver guessing with probability %1.2f at step %d (%dms, %d cases)%s\n",
        prob_best, numMines, tankTime, totalMultCases,
        (borderOptimization?"":"*"));
    Pair<Integer,Integer> q = segregated.get(prob_best_s).get(prob_besttile);
    int qi = q.getFirst();
    int qj = q.getSecond();
    clickOn(qi,qj);

  }


  // Segregation routine: if two regions are independant then consider
  // them as separate regions
  static ArrayList<ArrayList<Pair>>
            tankSegregate(ArrayList<Pair> borderTiles){

    ArrayList<ArrayList<Pair>> allRegions = new ArrayList<ArrayList<Pair>>();
    ArrayList<Pair> covered = new ArrayList<Pair>();

    while(true){

      LinkedList<Pair> queue = new LinkedList<Pair>();
      ArrayList<Pair> finishedRegion = new ArrayList<Pair>();

      // Find a suitable starting point
      for(Pair firstT : borderTiles){
        if(!covered.contains(firstT)){
          queue.add(firstT);
          break;
        }
      }

      if(queue.isEmpty())
        break;

      while(!queue.isEmpty()){

        Pair<Integer,Integer> curTile = queue.poll();
        int ci = curTile.getFirst();
        int cj = curTile.getSecond();

        finishedRegion.add(curTile);
        covered.add(curTile);

        // Find all connecting tiles
        for(Pair<Integer,Integer> tile : borderTiles){
          int ti = tile.getFirst();
          int tj = tile.getSecond();

          boolean isConnected = false;

          if(finishedRegion.contains(tile))
            continue;
          
          if(Math.abs(ci-ti)>2 || Math.abs(cj-tj) > 2)
            isConnected = false;

          else{
            // Perform a search on all the tiles
            tilesearch:
            for(int i=0; i<BoardHeight; i++){
              for(int j=0; j<BoardWidth; j++){
                if(onScreen(i,j) > 0){
                  if(Math.abs(ci-i) <= 1 && Math.abs(cj-j) <= 1 &&
                      Math.abs(ti-i) <= 1 && Math.abs(tj-j) <= 1){
                    isConnected = true;
                    break tilesearch;
                  }
                }
              }
            }
          }
          
          if(!isConnected) continue;

          if(!queue.contains(tile))
            queue.add(tile);

        }
      }

      allRegions.add(finishedRegion);

    }

    return allRegions;

  }



  static int[][] tank_board = null;
  static boolean[][] knownMine = null;
  static boolean[][] knownEmpty = null;
  static ArrayList<boolean[]> tank_solutions;
 
  // Should be true -- if false, we're bruteforcing the endgame
  static boolean borderOptimization;
  static int BF_LIMIT = 8;

  // Recurse from depth k (0 is root)
  // Assumes the tank variables are already set; puts solutions in
  // the static arraylist.
  static void tankRecurse(ArrayList<Pair> borderTiles, int k){

    // Return if at this point, it's already inconsistent
    int flagCount = 0;
    for(int i=0; i<BoardHeight; i++)
      for(int j=0; j<BoardWidth; j++){

        // Count flags for endgame cases
        if(knownMine[i][j])
          flagCount++;

        int num = tank_board[i][j];
        if(num < 0) continue;

        // Total bordering squares
        int surround = 0;
        if((i==0&&j==0) || (i==BoardHeight-1 && j==BoardWidth-1))
          surround = 3;
        else if(i==0 || j==0 || i==BoardHeight-1 || j==BoardWidth-1)
          surround = 5;
        else surround = 8;

        int numFlags = countFlagsAround(knownMine, i,j);
        int numFree = countFlagsAround(knownEmpty, i,j);
        
        // Scenario 1: too many mines
        if(numFlags > num) return;

        // Scenario 2: too many empty
        if(surround - numFree < num) return;
      }

    // We have too many flags
    if(flagCount > TOT_MINES)
      return;


    // Solution found!
    if(k == borderTiles.size()){

      // We don't have the exact mine count, so no
      if(!borderOptimization && flagCount < TOT_MINES)
        return;

      boolean[] solution = new boolean[borderTiles.size()];
      for(int i=0; i<borderTiles.size(); i++){
        Pair<Integer,Integer> s = borderTiles.get(i);
        int si = s.getFirst();
        int sj = s.getSecond();
        solution[i] = knownMine[si][sj];
      }
      tank_solutions.add(solution);
      return;
    }

    Pair<Integer,Integer> q = borderTiles.get(k);
    int qi = q.getFirst();
    int qj = q.getSecond();

    // Recurse two positions: mine and no mine
    knownMine[qi][qj] = true;
    tankRecurse(borderTiles, k+1);
    knownMine[qi][qj] = false;

    knownEmpty[qi][qj] = true;
    tankRecurse(borderTiles, k+1);
    knownEmpty[qi][qj] = false;

  }


  /*
todo -
read / write calibration settings
Minor defects / bugs:
1) Calibration routine kind of sucks, can't calibrate at
small resolutions and can't calibrate non-empty board
2) Death / win detection kind of sucks, can't distinguish
win from loss and sometimes fails to detect either
3) Clicking order is highly non-human
4) Endgame solver is inefficient: we could make it kick in earlier if
it was more efficient

Known but non-fixable defects:
1) Cannot automatically detect number of mines
*/

  public static void main(String[] args) throws Throwable {
    Thread.sleep(2000);
    robot = new Robot();

    // Keep these as these are the most common settings
    BoardWidth = 30;
    BoardHeight = 16;
    /*
BoardPix = 35.267857;
BoardTopW = 175;
BoardTopH = 120;
*/
    BoardPix = 42.035714;
    BoardTopW = 193;
    BoardTopH = 134;

    // Determine board height and width and position
    calibrate();
    if(BoardWidth < 9 || BoardHeight < 9 || BoardWidth > 30 || BoardWidth > 30){
      System.out.println("Calibration Failed.");
      return;
    }


    // Initialize internal constructs
    onScreen = new int[BoardHeight][BoardWidth];
    flags = new boolean[BoardHeight][BoardWidth];
    for(int i=0; i<BoardHeight; i++) for(int j=0; j<BoardWidth; j++) flags[i][j]=false;

    // Debugging: is it reading correctly?
    /*
updateOnScreen();
dumpPosition();
tankSolver();
exit();
*/
 

    firstSquare();
    for(int c=0; c<1000000; c++){
      int status = updateOnScreen();
      if(!checkConsistency()){
        robot.mouseMove(0,0);
        status = updateOnScreen();
        robot.mouseMove(mouseLocX,mouseLocY);
        if(status == -10) exit();
        continue;
      }
      // Exit on death
      if(status == -10) exit();
      attemptFlagMine();
      attemptMove();
    }


  }

  static void exit(){
    // For any reason, we want to exit
    //System.out.println("Steps: " + numMines);
    System.exit(0);
  }


  // Debugging: for whatever reason, dump the board
  static void dumpPosition(){
    for(int i = 0; i < BoardHeight; i++){
      for(int j=0; j < BoardWidth; j++){

        int d = onScreen(i,j);
        if(flags[i][j])
          System.out.print(".");
        else if(d >= 1)
          System.out.print(d);
        else if(d == 0)
          System.out.print(" ");
        else System.out.print("#");

      }
      System.out.println();
    }
    System.out.println();
  }
}


// Copied from http://stackoverflow.com/questions/156275/
class Pair<A, B> {
    private A first;
    private B second;

    public Pair(A first, B second) {
        super();
        this.first = first;
        this.second = second;
    }

    public int hashCode() {
        int hashFirst = first != null ? first.hashCode() : 0;
        int hashSecond = second != null ? second.hashCode() : 0;

        return (hashFirst + hashSecond) * hashSecond + hashFirst;
    }

    public boolean equals(Object other) {
        if (other instanceof Pair) {
                Pair otherPair = (Pair) other;
                return
                (( this.first == otherPair.first ||
                        ( this.first != null && otherPair.first != null &&
                          this.first.equals(otherPair.first))) &&
                 ( this.second == otherPair.second ||
                        ( this.second != null && otherPair.second != null &&
                          this.second.equals(otherPair.second))) );
        }

        return false;
    }

    public String toString()
    {
           return "(" + first + ", " + second + ")";
    }

    public A getFirst() {
        return first;
    }

    public void setFirst(A first) {
        this.first = first;
    }

    public B getSecond() {
        return second;
    }

    public void setSecond(B second) {
        this.second = second;
    }
}

posted on 2013-02-28 12:08  吴昊系列  阅读(317)  评论(0编辑  收藏  举报

导航