Welcome to HBH! If you have tried to register and didn't get a verification email, please using the following link to resend the verification email.

Tents puzzle solver - Java Code Bank


Tents puzzle solver
This program solves a so-called 'tents puzzle' using backtracking.
                /***************
    Main.java
***************/
package hbhtentspuzzle;

import java.io.IOException;

/**
 * Welcome to my Java program to solve a classic "tents puzzle". I suppose you Google it if you don't know what it is. The program reads the puzzles from files and uses a backtracking algorithm to solve them. Everything is JavaDoc'ed extensively, merely out of a habit.
 * @author GTADarkDude
 */
public class Main
{

    /**
     * Main method that loads a couple of puzzle files and calls placeTents to solve them.
     * @param args Command line arguments (unused)
     */
    public static void main(String[] args)
    {
        String cs [] = {"c1.txt", "c2.txt", "c3.txt"};
        for(String s : cs)
        {
            try
            {
                CampSite c = new CampSite(s, false);
                System.out.println(s + ":");
                System.out.println(c);
                c.placeTents();
            }
            catch(IOException ioe)
            {
                System.out.println("Oops, something went wrong while trying to read "
                                   + s + ": " + ioe.getMessage() );
            }
        }
    }

}


/***************
    Field.java
***************/
package hbhtentspuzzle;

/**
 * Enumeration for the possible values of a single field on the campsite.
 * @author GTADarkDude
 */
public enum Field
{
    Tent, Tree, Grass;

    @Override
    public String toString()
    {
        switch (this)
        {
            case Tent: return "X";
            case Tree: return "T";
            case Grass: return ".";
            default: return "";
        }
    }
}


/***************
    CampSite.java
***************/
package hbhtentspuzzle;

import java.io.FileReader;
import java.io.IOException;
import java.util.Scanner;

/**
 * Representation of the campsite.
 * @author GTADarkDude
 */
public class CampSite
{
    private final boolean allSolutions;
    private final int width, length;
    private Field[][] grid;
    private int[] numberH, numberV;
    private boolean solved;

    /**
     * Constructor reads camping from given file. See the example files for the required format.
     * @param fileName The name of the file to be read from.
     * @param allSolutions True if you want all solutions, false if you think that one solution is sufficient.
     * @throws java.io.IOException
     */
    public CampSite(String fileName, boolean all) throws IOException
    {
        allSolutions = all;
        solved = false;

        FileReader file = new FileReader(fileName);
        Scanner scan = new Scanner(file);
        length = scan.nextInt();
        width = scan.nextInt();
        scan.nextLine();
        grid = new Field[width][length];
        numberV = new int[width];
        numberH = new int[length];
        for(int i = 0; i < length; ++i)
        {
            String line = scan.nextLine();
            if(line.length() <= width)
                throw new IOException ("Line " + i + " is too short.");
            for(int j = 0; j < width; ++j)
                switch(line.charAt(j))
                {
                    case 'T': grid[j][i] = Field.Tree; break;
                    default:  grid[j][i] = Field.Grass;
                }
            numberH[i] = Character.digit(line.charAt(width), 10);
        }
        String line = scan.nextLine();
        if(line.length() < width)
            throw new IOException("Last line is too short.");
        for(int j = 0; j < width; ++j)
            numberV[j] = Character.digit(line.charAt(j), 10);
        file.close();
    }

    /**
     * Extra constructor where allSolutions is defaulted to false.
     * @param fileName The name of the file to be read from.
     * @throws java.io.IOException
     */
    public CampSite(String fileName) throws IOException
    {
        this(fileName, false);
    }

    /**
     * Produces a multi-line string describing the CampSite
     * @return the string
     */
    @Override
    public String toString()
    {
        StringBuilder builder = new StringBuilder();
        for(int i = 0; i < length; ++i)
        {
            for(int j = 0; j < width; ++j)
                builder.append(grid[j][i]);
            builder.append(numberH[i]).append("\n");
        }
        for(int j = 0; j < width; ++j)
            builder.append(numberV[j]);
        builder.append("\n");
        return builder.toString();
    }

    /**
     * Predicate to verify that a given position actually exists.
     * @param x
     * @param y
     * @return True if the position lies within the CampSite.
     */
    private boolean onCampSite(int x, int y)
    {
        return 0 <= x && x < width && 0 <= y && y < length;
    }

    /**
     * Predicate to check if a given position lies next to a tree and is valid.
     * @param x
     * @param y
     * @return True if the position lies within the CampSite and it lies next to a tree.
     */
    private boolean nextToTree(int x, int y)
    {
        if(onCampSite(x + 1, y) && grid[x + 1][y] == Field.Tree)
            return true;
        if(onCampSite(x - 1, y) && grid[x - 1][y] == Field.Tree)
            return true;
        if(onCampSite(x, y + 1) && grid[x][y + 1] == Field.Tree)
            return true;
        if(onCampSite(x, y - 1) && grid[x][y - 1] == Field.Tree)
            return true;
        return false;
    }

    /**
     * Predicate to check if there any tents next to a given position.
     * @param x
     * @param y
     * @return True is the position doesn't have any adjacent tents.
     */
    private boolean noNeighbours(int x, int y)
    {
        for(int i = y - 1; i <= y + 1; ++i)
            for(int j = x - 1; j <= x + 1; ++j)
                if(onCampSite(j, i) && grid[j][i] == Field.Tent && j != i)
                    return false;
        return true;
    }

    /**
     * Method that places the tents on the CampSite.
     */
    public void placeTents()
    {
        placeTents(0, 0);
    }

    /**
     * The real backtracking algorithm that solves the puzzle.
     * @param x
     * @param y
     */
    private void placeTents(int x, int y)
    {
        if(solved && !allSolutions)
            return;

        if(done())
        {
            System.out.println("Found a solution!\n" + this.toString());
            solved = true;
        }
        else if(!onCampSite(x, y))
        {
            if(x > width)
                placeTents(0, y + 1);
        }
        else
        {
            final int xNext = (x + 1) % width;
            final int yNext = y + (xNext == 0 ? 1 : 0);
            if(nextToTree(x, y) && noNeighbours(x, y) && grid[x][y] == Field.Grass && numberH[y] > 0 && numberV[x] > 0)
            {
                grid[x][y] = Field.Tent;
                --numberH[y];
                --numberV[x];
                placeTents(xNext, yNext);
                grid[x][y] = Field.Grass;
                ++numberH[y];
                ++numberV[x];
            }
            placeTents(xNext, yNext);
        }
    }

    /**
     * Predicate to check if a certain solution is finished.
     * @return True if all tents were placed.
     */
    private boolean done()
    {
        for(int i : numberH)
            if(i != 0)
                return false;
        for(int i : numberV)
            if(i != 0)
                return false;
        return true;
    }
}


/***************
    c1.txt
***************/
5
5
..T..1
...T.1
...T.2
T...T0
..T..2
12102

/***************
    c2.txt
***************/
10
10
..........2
T.T.T..T..2
..........2
.T.T......2
......T..T2
T...TT....3
..T.....T.0
...T....T.3
T....T.T..2
..T......T2
3231213032

/***************
    c3.txt
***************/
7 14
.....T......T.4
.T...T..T...T.2
..............2
.T.TTT..T.TTT.4
..............2
....T......T..2
T......T......2
12112111211211
            
Comments
Sorry but there are no comments to display