Assignment 5: Minesweeper

This assignment is worth 25 points and has two parts.

1. CSP Solver (15 pts)

First clone (or update your existing copy of) the ai_1 repository. Look in the csp/src subdirectory, which has source files for the classes described in this section.

Consider constraint satisfaction problems of the following nature:

For example, here is one such problem:

It's not difficult to see that this problem has a unique solution:

In this part of the assignment you will write a solver for constraint satisfaction problems such as this.

The class Constraint (in csp/Constraint.java) represents a single constraint:

public class Constraint {
    public int count;
    public List<Integer> vars;
}

The class BooleanCSP (in csp/BooleanCSP.java) represents a constraint satisfaction problem (CSP):

public class BooleanCSP {
    public int numVars;
    public Boolean[] value;
    public Set<Constraint> constraints;

    public ArrayList<Set<Constraint>> varConstraints;
    public Queue<Constraint> unchecked;
    
}

The first three fields define a CSP:

The remaining two fields will be useful in your solver:

The BooleanCSP class contains several useful methods including these:

Write a class Solver with the following API:

public class Solver {
    public Solver();

    public List<Integer> forwardCheck(BooleanCSP csp) {  }

    public List<Integer> solve(BooleanCSP csp) {  }

    public int inferVar(BooleanCSP csp) {  }
}

The methods above should behave as follows:

To illustrate the use of these methods, let's create a BooleanCSP that represents the four-variable constraint satisfaction problem that we saw above:

BooleanCSP csp = new BooleanCSP(4);
csp.addConstraint(new Constraint(1, List.of(0, 1)));
csp.addConstraint(new Constraint(1, List.of(1, 2)));
csp.addConstraint(new Constraint(1, List.of(2, 3)));
csp.addConstraint(new Constraint(2, List.of(0, 2, 3)));

Let's attempt to infer values by constraint propagation alone:

Solver solver = new Solver();
List<Integer> vars = solver.forwardCheck(csp);

out.printf("inferred %d variables\n", vars.size());

This will print

inferred 0 variables

That's because no values can be inferred from any individual constraint. In other words, the system is locally consistent with respect to individual constraints.

So if we'd like to infer a variable value, we need to use a more powerful tool:

int var = solver.inferVar(csp);
out.printf("var = %d, value = %b\n", var, csp.value[var]);

This might print

var = 2, value = true

Now that we have inferred one value, constraint propagation can easily find the rest:

        List<Integer> vars2 = solver.forwardCheck(csp);
        out.printf("inferred %d variables\n", vars2.size());
        for (int v = 0 ; v < csp.numVars ; ++v)
            out.printf("var = %d, value = %b\n", v, csp.value[v]);        

This will print

inferred 3 variables
var = 0, value = true
var = 1, value = false
var = 2, value = true
var = 3, value = false

As an alternative to calling forwardCheck() and inferVar(), we could have called solve(), which would find this same solution. However, note an important difference between these approaches. solve() searches for any solution to a CSP and returns it. By contrast, forwardCheck() and inferVar() infer variable values that must be true in every solution of a CSP. And so our calls to forwardCheck() and inferVar() have proven that the above solution is unique.

hints

testing

It would be wise to test your CSP solver before you attempt to apply it to Minesweeper. The script csp_test will run the tests in SolverTest.java, which will test your solver methods forwardCheck(), solve(), and inferVar() on a number of constraint satisfaction problems (including some that were derived from Minesweeper game boards).

2. Minesweeper (10 pts)

Write an agent that plays Minesweeper. As always in this tutorial you may use any technique you like, but probably it will be easiest to use the CSP solver you just wrote. :)

Download the Minesweeper code from the Minesweeper4J repository. Use the Minesweeper API. Write your agent by modifying the dummy implementation src/MyAgent.java.

To evaluate your agent, run Minesweeper with the -sim option, e.g.

$ ./minesweeper MyAgent -sim 20

Your agent should be able to solve randomly generated Minesweeper boards of size 50 x 50 with the default mine density of 20% in under 300 ms on average on a typical desktop or laptop computer, asking for an average of fewer than 7 hints while still solving the board safely. (The option -size 50 will select a board of this size.) For boards of this size and mine density, typically 1-10 hints will be necessary, but occasionally as many as 20.

hints

$ ./minesweeper MyAgent -size 3 -sim 10
board size is 3 x 3, 2 mines
seed 0: solved in 2 ms, hints = 1
seed 1: solved in 2 ms, hints = 1
seed 2: solved in 2 ms, hints = 4
seed 3: solved in 1 ms, hints = 3
seed 4: solved in 0 ms, hints = 1
seed 5: solved in 0 ms, hints = 2
seed 6: solved in 0 ms, hints = 2
seed 7: solved in 0 ms, hints = 1
seed 8: solved in 0 ms, hints = 2
seed 9: solved in 0 ms, hints = 1
average over 10 runs: time = 0 ms, hints = 1.8

$ ./minesweeper MyAgent -size 5 -sim 10
board size is 5 x 5, 5 mines
seed 0: solved in 4 ms, hints = 1
seed 1: solved in 1 ms, hints = 1
seed 2: solved in 3 ms, hints = 2
seed 3: solved in 1 ms, hints = 1
seed 4: solved in 1 ms, hints = 3
seed 5: solved in 0 ms, hints = 1
seed 6: solved in 1 ms, hints = 1
seed 7: solved in 0 ms, hints = 1
seed 8: solved in 2 ms, hints = 3
seed 9: solved in 2 ms, hints = 3
average over 10 runs: time = 1 ms, hints = 1.7

$ ./minesweeper MyAgent -size 8 -sim 10
board size is 8 x 8, 13 mines
seed 0: solved in 7 ms, hints = 2
seed 1: solved in 11 ms, hints = 5
seed 2: solved in 3 ms, hints = 3
seed 3: solved in 0 ms, hints = 2
seed 4: solved in 1 ms, hints = 2
seed 5: solved in 6 ms, hints = 2
seed 6: solved in 1 ms, hints = 4
seed 7: solved in 5 ms, hints = 4
seed 8: solved in 0 ms, hints = 2
seed 9: solved in 2 ms, hints = 3
average over 10 runs: time = 3 ms, hints = 2.9