Welcome to HBH! If you had an account on hellboundhacker.org you will need to reset your password using the Lost Password system before you will be able to login.

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