Minesweeper master from Google Code Jam(2014) Qualification round
Asked Answered
P

11

30

This is a problem from Google Code Jam qualification round (which is over now). How to solve this problem?

Note: If you have a different method from the ones discussed in answers, please share it so we can expand our knowledge of the different ways to solve this problem.

Problem Statement:

Minesweeper is a computer game that became popular in the 1980s, and is still included in some versions of the Microsoft Windows operating system. This problem has a similar idea, but it does not assume you have played Minesweeper.

In this problem, you are playing a game on a grid of identical cells. The content of each cell is initially hidden. There are M mines hidden in M different cells of the grid. No other cells contain mines. You may click on any cell to reveal it. If the revealed cell contains a mine, then the game is over, and you lose. Otherwise, the revealed cell will contain a digit between 0 and 8, inclusive, which corresponds to the number of neighboring cells that contain mines. Two cells are neighbors if they share a corner or an edge. Additionally, if the revealed cell contains a 0, then all of the neighbors of the revealed cell are automatically revealed as well, recursively. When all the cells that don't contain mines have been revealed, the game ends, and you win.

For example, an initial configuration of the board may look like this ('*' denotes a mine, and 'c' is the first clicked cell):

*..*...**.
....*.....
..c..*....
........*.
..........

There are no mines adjacent to the clicked cell, so when it is revealed, it becomes a 0, and its 8 adjacent cells are revealed as well. This process continues, resulting in the following board:

*..*...**.
1112*.....
00012*....
00001111*.
00000001..

At this point, there are still un-revealed cells that do not contain mines (denoted by '.' characters), so the player has to click again in order to continue the game.

You want to win the game as quickly as possible. There is nothing quicker than winning in one click. Given the size of the board (R x C) and the number of hidden mines M, is it possible (however unlikely) to win in one click? You may choose where you click. If it is possible, then print any valid mine configuration and the coordinates of your click, following the specifications in the Output section. Otherwise, print "Impossible".

My Tried Solution:

So for the solution, you need to make sure that each non-mine node is in a 3x3 matrix with other non-mine nodes, or a 3x2 or 2x2 matrix if the node is on an edge of the grid; lets call this a 0Matrix. So any node in a 0Matrix have all non-mine neighbors.

Firstly, Check whether less mines are required, or less empty nodes

if(# mines required < 1/3 of total grid size)
    // Initialize the grid to all clear nodes and populate the mines
    foreach (Node A : the set of non-mine nodes)
        foreach (Node AN : A.neighbors)
            if AN forms a OMatrix with it's neighbors, continue
            else break;
        // If we got here means we can make A a mine since all of it's neighbors 
        // form 0Matricies with their other neighbors
    // End this loop when we've added the number of mines required

else
    // We initialize the grid to all mines and populate the clear nodes
    // Here I handle grids > 3x3; 
    // For smaller grids, I hard coded the logic, eg: 1xn grids, you just populate in 1 dimension

    // Now we know that the # clear nodes required will be 3n+2 or 3n+4
    // eg: if a 4x4 grid need 8 clear nodes : 3(2) + 2

    For (1 -> num 3's needed)
        Add 3 nodes going horizontally
        When horizontal axis is filled, add 3 nodes going vertically
           When vertical axis is filled, go back to horizontal then vertical and so on.

    for(1 -> num 2's needed)
        Add 2 nodes going horizontally or continuing in the direction from above
        When horizontal axis is filled, add 2 nodes going vertically

For example, say we have an 4x4 grid needing 8 clean nodes, here are the steps:

// Initial grid of all mines
* * * *
* * * *
* * * *
* * * *

// Populating 3's horizontally
. * * *
. * * *
. * * *
* * * *     

. . * *
. . * *
. . * *
* * * *    

// Populating 2's continuing in the same direction as 3's
. . . *
. . . *
. . * *
* * * *        

Another Example: 4x4 grid with 11 clear nodes needed; output:

. . . .
. . . .
. . . *
* * * * 

Another Example: 4x4 grid with 14 clear nodes needed; output:

// Insert the 4 3's horizontally, then switch to vertical to insert the 2's
. . . .
. . . .
. . . .
. . * *  

Now here we have a grid that is fully populated and can be solved in one click if we click on (0, 0).

My solution works for most cases, but it didn't pass the submission (I did check an entire 225 cases output file), so I'm guessing it has some problems, and I'm pretty sure there are better solutions.

Promulgate answered 13/4, 2014 at 5:29 Comment(0)
A
24

Algorithm

Let's first define N, the number of non-mine cells:

N = R * C - M

A simple solution is to fill an area of N non-mine cells line-by-line from top to bottom. Example for R=5, C=5, M=12:

c....
.....
...**
*****
*****

That is:

  • Always start in the top-left corner.
  • Fill N / C rows with non-mines from top to bottom.
  • Fill the next line with N % C non-mines from left to right.
  • Fill the rest with mines.

There are only a few special cases you have to care about.

Single non-mine

If N=1, any configuration is a correct solution.

Single row or single column

If R=1, simply fill in the N non-mines from left-to-right. If C=1, fill N rows with a (single) non-mine.

Too few non-mines

If N is even, it must be >= 4.

If N is odd, it must be >= 9. Also, R and C must be >= 3.

Otherwise there's no solution.

Can't fill first two rows

If N is even and you can't fill at least two rows with non-mines, then fill the first two rows with N / 2 non-mines.

If N is odd and you can't fill at least two rows with non-mines and a third row with 3 non-mines, then fill the first two rows with (N - 3) / 2 non-mines and the third row with 3 non-mines.

Single non-mine in the last row

If N % C = 1, move the final non-mine from the last full row to the next row.

Example for R=5, C=5, M=9:

c....
.....
....*
..***
*****

Summary

It is possible to write an algorithm that implements these rules and returns a description of the resulting mine field in O(1). Drawing the grid takes O(R*C), of course. I also wrote an implementation in Perl based on these ideas which was accepted by the Code Jam Judge.

Ajaajaccio answered 13/4, 2014 at 12:24 Comment(5)
what's your nick on google code jam, so I can search your solution? This part is not clear: "If N is even and you can't fill at least two rows with non-mines" How he would know that?Truncate
@Leandro: try to fill 8 mines in a 5x5 grid. Given the generic algorithm, you will have 5 in the first row and 3 in the second row. However, this doesn't work, so you must fill 4 in the first row and 4 in the second row.Arkansas
@Truncate "If N is even and you can't fill at least two rows with non-mines", can be interpreted as: if n % 2 == 0 and n / c < 2: do_somethingStout
How can you prove that it's possible to win in one click if and only if you can win in one click using this strategy?Tadzhik
"Too few non-mines". I notice that with an N x N grid with (N*N)-1 mines (1 empty space), this would also be an automatic win when first click is on that empty cell. But I suppose you could say this is just a "trivial" exception.British
M
15

There is a more general solution to this problem that passes both the small and large test cases. It avoids having to think of all the special cases, it doesn't care what the dimensions of the board are and doesn't require any back tracking.

ALGORITHM

The basic idea is start with a grid full of mines and remove them starting from cell {0, 0} until there are the correct number of mines on the board.

Obviously there needs to be some way to determine which mines to remove next and when it is impossible to remove the correct number of mines. To do this we can keep an int[][] that represents the board. Each cell with a mine contains -1 and those without mines contain an integer which is the number of mines adjacent to the cell; the same as in the actual game.

Also define the concept of a 'frontier' which is all non-mine cells that are non-zero i.e. those cells with mines adjacent.

For example the configuration:

c . *
. . *
. . *
* * *

Would be represented as:

 0  2 -1
 0  3 -1
 2  5 -1
-1 -1 -1

And the frontier would contain the cells with values: 2, 3, 5, 2

When removing the mines the strategy is:

  • Find a cell in the frontier that has the same value as the remaining number of mines to remove. So in the example above if we had 5 more mines to remove, the cells with value 5 on the frontier would be chosen.
  • Failing that chose the smallest frontier cell. So either of the 2's in the example above.
  • If the value of the chosen cell is greater than the number of mines left to remove then it's impossible to build this board, so return false.
  • Else remove all mines surrounding the chosen frontier cell.
  • Repeat until the correct number of mines are present on the board - the constraints of the problem have been met.

In java this looks like:

// Tries to build a board based on the nMines in the test case
private static boolean buildBoard(TestCase t) throws Exception {
    if (t.nMines >= t.Board.rows() * t.Board.cols()) { 
        return false;
    }
    // Have to remove the cell at 0,0 as the click will go there
    t.Board.removeMine(0, 0);
    while (!t.boardComplete()) {
        Cell c = nextCell(t);
        // If the value of this cell is greater than what we need to remove we can't build a board
        if (t.Board.getCell(c.getRow(), c.getCol()) > t.removalsLeft()) {
            return false;
        }
        t.Board.removeNeighbourMines(c.getRow(), c.getCol());
    }

    return true;
}

// Find frontier cell matching removals left, else pick the smallest valued cell to keep things balanced
private static Cell nextCell(TestCase t) {
    Cell minCell = null;
    int minVal = Integer.MAX_VALUE;
    for (Cell c : t.Board.getFrontier()) {
        int cellVal = t.Board.getCell(c.getRow(), c.getCol());
        if (cellVal == t.removalsLeft()) {
            return c;
        }
        if (cellVal < minVal) {
            minVal = cellVal;
            minCell = c;
        }
    }
    if (minCell == null) {
        throw new NullPointerException("The min cell wasn't set");
    }
    return minCell;
}

PROOF / INTUITION

Firstly a board is defined as valid if it can be solved by a single click, even if there is only one cell on the board where this click can occur. Therefore for a board to be valid all non-mine cells must be adjacent to a cell with value 0, if this is not the case the cell is defined as unreachable. This is because we know with certainty that all cells adjacent to a 0 cell are non mines, so they can be safely revealed and the game will do it automatically for the player.

A key point to prove for this algorithm is that it is always necessary to remove all the mines surrounding the smallest frontier cell in order to keep the board in a valid state. This is quite easy to prove just by drawing out a board (such as the one above) and picking the lowest value cell (in this case the top right 2). If only a single mine is removed then the board would not be valid, it would be in either of these two states:

 0  1  1
 0  2 -1
 2  5 -1
-1 -1 -1

or

 0  1 -1
 0  2  2
 2  5 -1
-1 -1 -1

which both have unreachable cells.

So it is now true that always choosing the smallest frontier cell will keep the board in a valid state and my first instinct was that continuing to choose these cells would go through all valid states, however this is not correct. This can be illustrated by a test case such as 4 4 7 (so there are 9 non-mine cells). Then consider the following board:

 0  2 -1 -1
 2  5 -1 -1
-1 -1 -1 -1
-1 -1 -1 -1

Continuing to chose the smallest frontier cell may result in the algorithm doing the following:

 0  2 -1 -1
 0  3 -1 -1
 0  3 -1 -1
 0  2 -1 -1

Which means it is now impossible to remove just a single mine to complete the board for this case. However choosing a frontier cell that matches the number of remaining mines (if one exists) ensures that the 5 would have been chosen resulting in a 3x3 square of non-mines and a correct solution to the test case.

At this point I decided to give the algorithm a try on all test cases in the following range:

0 < R < 20
0 < C < 20
0 ≤ M < R * C

and found that it managed to correctly identify all the impossible configurations and build what looked like sensible solutions to the possible configurations.

Further intuition behind why choosing the frontier cell with the same value as the remaining number of mines (should it exist) is correct is that it allows the algorithm to find configurations for solutions requiring an odd number of non-mines.

When originally implementing this algorithm I was intending on writing heuristics that built the non-mine area in a square arrangement. Considering the 4 4 7 test case again it would end-up doing this:

 0  0  2 -1
 0  1  4 -1
 2  4 -1 -1
-1 -1 -1 -1

notice how we now have a 1 on the frontier which would ensure the final cell removed completed the square to give:

c . . *
. . . *
. . . *
* * * *

This would mean the heuristics change slightly to:

  • Pick the smallest frontier cell
  • In the case of a tie, pick the first cell added to the frontier list

This could be implemented by keeping a FIFO queue of frontier cells, but I quickly realised that it is trickier than it first seems. This is due to the fact that the values of the frontier cells are interdependent and so care needs to be taken to keep the collection of frontier cells in the correct state and also not to use the cells value in any kind of hash value etc. I'm sure this could be done, but upon realising that just adding the extra heuristic to pick any cells with values equal to the remaining removals worked, this seemed liked the easier approach.

Mallen answered 15/4, 2014 at 10:36 Comment(7)
Say you need to remove 5 mines from the board and the frontier consists of [1,2,3] valued cells. According to the heuristics you wrote, this would remove 1, then remove 2, then fail. Where you could remove 5 mines by removing 2 and 3.Giveandtake
I can't think of a situation where this would be the entire state of the frontier when starting this from the cell {0,0}. Just giving the values in the frontier doesn't indicate if any of the cells are sharing mines or not. For example removing the 1 might reduce the value of the 3 by 1 and in which case the remaining 4 mines would be removed to satisfy the condition. If you can think of an actual board that this approach fails for (or that gives this frontier) I would be interested to know. I couldn't come up with any situations that this approach got stuck for.Mallen
Can you prove that the smallest frontier is always the correct choice?Coursing
This is what I was looking for! Genius! The method you used to remove the mines works great, but can you tell us like how you came to this conclusion that THIS is the way to go? What thought process led you to this solution? Thanks!Peeress
@ThomasAhle I've added some information about why the smallest frontier cell is correct, but note that the algorithm doesn't always pick the smallest cell.Mallen
@Peeress I've added some intuition about how I came up with this solution, but what really led me to it was a desire to come up with something that didn't just require thinking of all the special cases.Mallen
@Hanfeng See the edits for a proof of why this algorithm works.Mallen
K
3

This is my code. I solved taking different cases like if number of rows=1 or number of columns=1 or if number of mines=(r*c)-1 and few other cases.

The position on the layout to click is placed at a[r-1][c-1]('0' indexed) every time.

For this question I had given few wrong attempts and every time I kept finding a new case. I eliminated few cases in which solution is not possible using goto and let it jump to end where it prints out impossible. A very simple solution(Indeed can be said a brute force solution since I coded for different cases possible individually). This is an editorial for my code. And on github.

Karissakarita answered 13/4, 2014 at 12:33 Comment(0)
H
2

I used search with backtracking, but I could solve only the small input.

Basically the algorithm starts with the board full of mines and tries to remove the mines in a way that the first "click" would solve the board. The catch is that to allow a "click" to expand to another cell, the expansion will come from another cell that must have all other neighbor cells cleared too. Sometimes, to expand to another cell you would need to remove other mines and end up with less mines than required. The algorithm will backtrack if it reaches such a position.

Maybe it is simpler to do the opposite. Start with an empty board and add each mine in a way that would not prevent the "expansion" of the initial click.

The full Python code is below:

directions = [
    [-1, -1], [-1, 0], [-1, 1],
    [0, -1],           [0, 1],
    [1,  -1],  [1, 0],  [1, 1],
]

def solve(R, C, M):
    def neighbors(i, j):
        for di, dj in directions:
            if 0 <= (i + di) < R and 0 <= (j + dj) < C:
                yield (i + di, j + dj)

    def neighbors_to_clear(i, j, board):
        return [(ni, nj) for ni, nj in neighbors(i, j) if board[ni][nj] == "*"]

    def clear_board(order):
        to_clear = R * C - M - 1
        board = [["*" for _ in range(C)] for _ in range(R)]
        for i, j in order:
            board[i][j] = "."
            for ni, nj in neighbors_to_clear(i, j, board):
                to_clear -= 1
                board[ni][nj] = "."
        return board, to_clear

    def search(ci, cj):
        nodes = []
        board = []
        to_clear = 1
        nodes.append((ci, cj, []))
        while nodes and to_clear > 0:
            i, j, order = nodes.pop()
            board, to_clear = clear_board(order)
            neworder = order + [(i, j)]
            if to_clear == 0:
                board[ci][cj] = "c"
                return board
            elif to_clear > 0:
                for ni, nj in neighbors_to_clear(i, j, board):
                    board[ni][nj] = "."
                    nodes.append([ni, nj, neworder])

    for i in range(R):
        for j in range(C):
            board = search(i, j)
            if board:
                for row in board:
                    print "".join(row)
                return

    print "Impossible"
    return

T = int(raw_input())
for i in range(1, T + 1):
    R, C, M = map(int, raw_input().split(" "))
    print("Case #%d:" % i)
    solve(R, C, M)
Hereditable answered 13/4, 2014 at 11:12 Comment(0)
B
2

my strategy was very similar to yours and I passed both small and large. Did you think about cases below?

  • R * C - M = 1

  • There is only one row

  • There are only two rows


I flipped R and C when R > C.

Bondage answered 13/4, 2014 at 11:37 Comment(0)
H
2

I separated this into two initial special cases, then had a general algorithm. The tl;dr version is to build a square of blank spaces from the top left. Similar to other answers, but with fewer special cases.

Special cases

Case 1

Only 1 blank space. Just click in the top left corner and finish.

Case 2

2 or 3 blank spaces, with a grid that is not either Rx1 or 1xC. This is impossible, so we fail early.

Algorithm

Always click in the top left corner. Start with a 2x2 blank square in the top left (we have at least 4 blanks). Now we need to add the remaining blanks. We then expand the square along one edge then the other, until we have no more blank spaces.

Example of blanking order:

C  2  6 12
1  3  7 13
4  5  8 14
9 10 11 15

Impossible case

Note that when beginning a new edge, we must place at least two blank spaces for this to be valid. So if we have only one blank to place, then this must be invalid (unless our edge has length one). My logic looked like this:

if maxEdgeLength > 1 and remainingBlanks == 1:
    print('Impossible')
    return

However, we could have left off the end of the last edge, which would give us two blanks now. Of course we can only leave off the last blank if the last edge was more than 2 blanks long!

My logic for this special case looked like this:

if remainingBlanks == 1 and lastEdgeSize > 2:
    mineMatrix[lastBlank] = '*'
    blanks += 1
Harmonics answered 13/4, 2014 at 18:54 Comment(0)
A
1

Code z self explanatory with comments. O(r+c)

import java.util.Scanner;
    public class Minesweeper {
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            int n = sc.nextInt();
            for(int j=0;j<n;j++) {
                int r =sc.nextInt(),
                    c = sc.nextInt(),
                    m=sc.nextInt();
                //handling for only one space.
                if(r*c-m==1) {
                    System.out.println("Case #"+(int)(j+1)+":");
                    String a[][] = new String[r][c];
                    completeFill(a,r-1,c-1,"*");
                    printAll(a, r-1, c-1);
                }
                //handling for 2 rows or cols if num of mines - r*c < 2 not possible.
                //missed here the handling of one mine.
                else if(r<2||c<2) {
                    if(((r*c) - m) <2) {
                        System.out.println("Case #"+(int)(j+1)+":");
                        System.out.println("Impossible");
                    }
                    else {
                        System.out.println("Case #"+(int)(j+1)+":");
                        draw(r,c,m);
                    }
                }
                //for all remaining cases r*c - <4 as the click box needs to be zero to propagate
                else if(((r*c) - m) <4) {
                    System.out.println("Case #"+(int)(j+1)+":");
                    System.out.println("Impossible");
                }
                //edge cases found during execution.
                //row or col =2 and m=1 then not possible.
                //row==3 and col==3 and m==2 not possible.
                else {
                    System.out.println("Case #"+(int)(j+1)+":");
                    if(r==3&&m==2&&c==3|| r==2&&m==1 || c==2&&m==1) {
                        System.out.println("Impossible");
                    }
                    else {
                        draw(r,c,m);
                    }
                }
            }
        }
        /*ALGO : IF m < (r and c) then reduce r or c which ever z max 
         * by two first time then on reduce by factor 1. 
         * Then give the input to filling (squarefill) function which files max one line 
         * with given input. and returns the vals of remaining rows and cols.
         * checking the r,c==2 and r,c==3 edge cases.
         **/
        public static void draw(int r,int c, int m) {
            String a[][] = new String[r][c];
            int norow=r-1,nocol=c-1;
            completeFill(a,norow,nocol,".");
            int startR=0,startC=0;
            int red = 2;
            norow = r;
            nocol = c;
            int row=r,col=c;
            boolean first = true;
            boolean print =true;
            while(m>0&&norow>0&&nocol>0) {
                if(m<norow&&m<nocol) {
                    if(norow>nocol) {
                        norow=norow-red;
                        //startR = startR + red;
                    }
                    else if(norow<nocol){
                        nocol=nocol-red;
                        //startC = startC + red;
                    }
                    else {
                        if(r>c) {
                            norow=norow-red;
                        }
                        else {
                            nocol=nocol-red;
                        }
                    }
                    red=1;
                }
                else {
                    int[] temp = squareFill(a, norow, nocol, startR, startC, m,row,col,first);
                    norow = temp[0];
                    nocol = temp[1];
                    startR =r- temp[0];
                    startC =c -temp[1];
                    row = temp[3];
                    col = temp[4];
                    m = temp[2];
                    red=2;
                    //System.out.println(norow + " "+ nocol+ " "+m);
                    if(norow==3&&nocol==3&&m==2 || norow==2&&m==1 || nocol==2&&m==1) {
                        print =false;
                        System.out.println("Impossible");
                        break;
                    }
                }
                first = false;
            }
            //rectFill(a, 1, r, 1, c);
            if(print)
                printAll(a, r-1, c-1);
        }
        public static void completeFill(String[][] a,int row,int col,String x) {
            for(int i=0;i<=row;i++) {
                for(int j=0;j<=col;j++) {
                    a[i][j] = x;
                }
            }
            a[row][col] = "c";
        }
        public static void printAll(String[][] a,int row,int col) {
            for(int i=0;i<=row;i++) {
                for(int j=0;j<=col;j++) {
                    System.out.print(a[i][j]);
                }
                System.out.println();
            }
        }
        public static int[] squareFill(String[][] a,int norow,int nocol,int startR,int startC,int m,int r, int c, boolean first) {
            if(norow < nocol) {
                int fil = 1;
                m = m - norow;
                for(int i=startR;i<startR+norow;i++) {
                    for(int j=startC;j<startC+fil;j++) {
                        a[i][j] = "*";
                    }
                }
                nocol= nocol-fil;
                c = nocol;
                norow = r;
            }
            else {
                int fil = 1;
                m = m-nocol;
                for(int i=startR;i<startR+fil;i++) {
                    for(int j=startC;j<startC+nocol;j++) {
                        a[i][j] = "*";
                    }
                }
                norow = norow-fil;
                r= norow;
                nocol = c;
            }
            return new int[] {norow,nocol,m,r,c};
        }
    }
Awildaawkward answered 14/4, 2014 at 3:55 Comment(0)
V
1

My approach to this problem was as follows:

  • For a 1x1 grid, M has to be zero otherwise it's impossible
  • For a Rx1 or 1xC grid, we need M <= R * C - 2 (place 'c' on the last cell with an empty cell next to it)
  • For a RxC grid, we need M <= R * C - 4 (place 'c' on a corner with 3 empty cells around it)

In summary, c will always have non-mine cells next to it no matter what, otherwise it's impossible. This solution makes sense to me, and I have checked the output against their sample and small inputs, however it was not accepted.

Here is my code:

import sys

fname = sys.argv[1]

handler = open(fname, "r")
lines = [line.strip() for line in handler]

testcases_count = int(lines.pop(0))

def generate_config(R, C, M):
    mines = M

    config = []
    for row in range(1, R+1):
        if mines >= C:
            if row >= R - 1:
                config.append(''.join(['*' * (C - 2), '.' * 2]))
                mines = mines - C + 2
            else:
                config.append(''.join('*' * C))
                mines = mines - C
        elif mines > 0:
            if row == R - 1 and mines >= C - 2:
                partial_mines = min(mines, C - 2)
                config.append(''.join(['*' * partial_mines, '.' * (C - partial_mines)]))
                mines = mines - partial_mines
            else:
                config.append(''.join(['*' * mines, '.' * (C - mines)]))
                mines = 0
        else:
            config.append(''.join('.' * C))

    # click the last empty cell
    config[-1] = ''.join([config[-1][:-1], 'c'])

    return config

for case in range(testcases_count):
    R, C, M = map(int, lines.pop(0).split(' '))

    # for a 1x1 grid, M has to be zero
    # for a Rx1 or 1xC grid, we must have M <= # of cells - 2
    # for others, we need at least 4 empty cells
    config_possible = (R == 1 and C == 1 and M==0) or ((R == 1 or C == 1) and M <= R * C - 2) or (R > 1 and C > 1 and M <= R * C - 4)

    config = generate_config(R, C, M) if config_possible else None

    print "Case #%d:" % (case+1)
    if config:
        for line in config: print line
    else:
        print "Impossible"

handler.close()

It worked pretty well against their sample on the website and against the small input they provided, but it looks like I'm missing something.

Here is the output to the sample:

Case #1:
Impossible
Case #2:
*
.
c
Case #3:
Impossible
Case #4:
***....
.......
.......
......c
Case #5:
**********
**********
**********
**********
**********
**********
**********
**********
**........
.........c

Update: Reading vinaykumar's editorial, I understand what's wrong with my solution. Basic rules of minesweeper that I should have covered, pretty much.

Vesting answered 14/4, 2014 at 10:30 Comment(0)
P
1

Pre-Checks

M = (R * C) - 1

Fill grid with all mines and put click anywhere.

R == 1 || C == 1

Fill left/right (or up/down) in order: click, non-mines, mines (ex. c...****).

M == (R * C) - 2 || M == (R * C) - 3

Impossible

Algorithm

I started with an "empty" grid (all .s) and placed the click in a corner (I will use the top-left corner for the click, and begin filling with mines from the bottom-right).
We will use R1 and C1 as our "current" rows and columns.

While we have enough mines to fill a row or column which, when removed, does not leave a single row or column left (while((M >= R1 && C1 > 2) || (M >= C1 && R1 > 2))), we "trim" the grid (fill with mines and reduce R1 or C1) using the shortest side and remove that many mines. Thus, a 4x5 with 6 mines left would become a 4x4 with 2 mines left.

  • If we end up with 2 x n grid we will either have 0 mines (we are done) or 1 mine left (Impossible to win).
  • If we end up with a 3 x 3 grid we will either have 0 mines (we are done), 1 mine (continue below), or 2 mines (Impossible to win).
  • Any other combination is winnable. We check if M == min(R1,C1)-1, if so we will need to put a single mine one row or column in from the shortest edge, then fill the shortest edge with the remaining mines.

Example

I will show the order I enter mines into the grid with numbers, just to help with visualization
R = 7, C = 6, M = 29

c...42
....42
...742
..6742
555542
333332
111111  

It took me a few different tries to get my algorithm correct, but I wrote mine in PHP and got both the small and large correct.

Procephalic answered 20/4, 2014 at 6:44 Comment(0)
T
0

I tried my luck in this question also but for some reason didn't pass checks.

I figured that it's solvable for (3x3 matrix) if there are less than (rows*cols-4) mines, as I needed only 4 cells for "c" and its boundaries as "."

My algorithms follows:

Solvable?:

  1. Checks if there is enough room for mines (rows*cols - 4 == maximum mines)
  2. Exceptions like rows == 1, cols == 1; then it's rows*cols-2
  3. Conditional whether possible or impossible

Build solution

  1. Build rows*cols matrix, with default value nil
  2. Go to m[0][0] and assign 'c'
  3. Define m[0][0] surroundings with '.'
  4. Loop from bottom right of Matrix and assign '*' until mines are over, then assign '.'
These answered 13/4, 2014 at 6:17 Comment(4)
Based off your answer, I suspect there was at least one case where there was only one non-mine square and you flagged it as impossible instead of a grid full of mines with a single 'c'.Parentage
your algo gives impossible as solution for case 1 5 4. But the output should be ****cKarissakarita
Also it fails for the case 4 4 7. Your solution is c... .... .*** **** But when you actually click at that position 'c', it returns 0000 1232 .*** **** So game is not completed yet and you did not win in one step.Karissakarita
I submitted similar solution. However I have missed corner cases.Speech
T
0

The solution can be found here. Contents of page below.

There are many ways to generate a valid mine configuration. In this analysis, we try to enumerate all possible cases and try to generate a valid configuration for each case (if exists). Later, after having some insight, we provide an easier to implement algorithm to generate a valid mine configuration (if exists).

Enumerating all possible cases

We start by checking the trivial cases:

If there is only one empty cell, then we can just fill all cells with mines except the cell where you click. If R = 1 or C = 1, the mines can be placed from left to right or top to bottom respectively and click on the right-most or the bottom-most cell respectively. If the board is not in the two trivial cases above, it means the board has at least 2 x 2 size. Then, we can manually check that:

If the number of empty cells is 2 or 3, it is Impossible to have a valid configuration. If R = 2 or C = 2, valid configurations exists only if M is even. For example, if R = 2, C = 7 and M = 5, it is Impossible since M is odd. However, if M = 6, we can place the mines on the left part of the board and click on the bottom right, like this: *.... *...c If the board is not in any of the above case, it means the board is at least 3 x 3 size. In this case, we can always find a valid mine configuration if the number of empty cells is bigger than 9. Here is one way to do it:

If the number of empty cells is equal or bigger than 3 * C, then the mines can be placed row by row starting from top to bottom. If the number of remaining mines can entirely fill the row or is less than C - 2 then place the mines from left to right in that row. Otherwise, the number of remaining mines is exactly C - 1, place the last mine in the next row. For example: ****** ****** *****. ****.. ...... -> *..... ...... ...... .....c .....c If the number of empty cells is less than 3 * C but at least 9, we first fill all rows with mines except the last 3 rows. For the last 3 rows, we fill the remaining mines column by column from the left most column. If the remaining mines on the last column is two, then last mine must be put in the next column. For example: ****** ****** .... -> *... **.... *..... *....c *....c Now, we are left with at most 9 empty cells which are located in the 3 x 3 square cells at the bottom right corner. In this case, we can check by hand that if the number of empty cells is 5 or 7, it is Impossible to have a valid mine configuration. Otherwise, we can hard-coded a valid configuration for each number of empty cell in that 3 x 3 square cells.

Sigh... that was a lot of cases to cover! How do we convince ourselves that when we code the solution, we do not miss any corner case?

Brute-force approach

For the small input, the board size is at most 5 x 5. We can check all (25 choose M) possible mine configurations and find one that is valid (i.e., clicking an empty cell in the configuration reveal all other empty cells). To check whether a mine configuration is valid, we can run a flood-fill algorithm (or a simple breath-first search) from the clicked empty cell and verify that all other empty cells are reachable (i.e., they are in one connected component). Note that we should also check all possible click positions. This brute-force approach is fast enough for the small input.

The brute-force approach can be used to check (for small values of R, C, M) whether there is a false-negative in our enumeration strategy above. A false-negative is found when there exist a valid mine configuration, but the enumeration strategy above yields Impossible. Once we are confident that our enumeration strategy does not produce any false-negative, we can use it to solve the large input.

An easier to implement approach

After playing around with several valid mine configurations using the enumeration strategy above, you may notice a pattern: in a valid mine configuration, the number of mines in a particular row is always equal or larger than the number of mines of the rows below it and all the mines are left-aligned in a row. With this insight, we can implement a simpler backtracking algorithm that places mines row by row from top to bottom with non-increasing number of mines as we proceed to fill in the next row and prune if the configuration for the current row is invalid (it can be checked by clicking at the bottom right cell). This backtracking with pruning can handle up to 50 x 50 sized board in reasonable time and is simpler to implement (i.e., no need to enumerate corner / tricky cases).

If the contest time were shorter, we may not have enough time to enumerate all possible cases. In this case, betting on the backtracking algorithm (or any other algorithm that is easier to implement) may be a good idea. Finding such algorithms is an art :).

Tadzhik answered 3/8, 2015 at 1:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.