Fork me on GitHub

LabyREnth 2017 - 3D Maze Writeup

Published: Mon 24 July 2017
By Nelson

In CTF.

LabyREnth's second annual CTF's programming track contained an interesting challenge: solve a 3D maze that will attempt to "cheat".

LabyREnth's Maze Challenge Introduction Inside The Maze

I decided that there would be three steps to solving this: 1. Determine how the maze cheats 2. Write code to parse the server's output 3. Implement a pathfinding algorithm

Determining How the Maze Cheats

It doesn't take long in the maze to realize something is wrong. Not long into each attempt I found myself walking face first into a new wall, finding that the maze had become only a few cells long. After some testing, I found that you would only walk into a random wall if your turn number was a multiple of 10, and that it would not happen if you spent that move walking backwards. This led to a lot of useless code (like turning in a circle every move to check if the maze had been modified), but eventually brought me to the conclusion that there was a wall being placed in the direction I moved every 10th move.

Once you know how the maze cheats, it is not too hard to circumvent. I just had to turn into a wall every 8th or 9th turn (depending on my current orientation) and walk into it until the 10th turn has passed. Since there is already a wall in the direction I was moving on the 10th turn, the maze cannot place another wall there and will therefore be unable to cheat.

2D to 3D

The second step to solving this challenge was writing a program that understood what the maze is showing it. While the maze describes what is ahead ("ahead you see some turns", "ahead you see a right turn"), these descriptions not accurate in all situations. For example, when faced with a right turn and a continuing hallway, or a hallway that splits off in two directions, the maze only says that there are "some turns" ahead. While one could turn in a circle after each move forward to determine their surroundings, I decided to use regular expressions on certain lines of output to determine what the maze was displaying.

Additionally, while the maze displays multiple moves in front of you, I decided I only needed to look a single move forward. As such, regular expressions were only done on two lines. One line to determine if the next node contains any turns, and another to determine if there is a wall at the end of the next node.

Determining Turns

I checked for any turns ahead by matching the 12th line from the top to a regular expression.

Left: ^ +\| +\\ +$ Right: ^ +/ +\| +$
Left Right
Straight: ^ +/ +\\ +$ Double: ^ +\| +\| +$
Straight Double

Determining Continuation

To determine whether the next cell has a wall at the end, I would match the 5th line from the top with a regular expression.

Continues: ^[ \|]+/ +\\[ \|]+$ Stops: ELSE
Continues Stops

Solving Algorithm

I betted on the maze being simply connected and used a basic wall follower algorithm. The algorithm is strong and very simple to implement. If all of the maze's walls are connected one can follow the left (or right) wall and will eventually reach the end. This may not produce the shortest path, but it is simple to use and should never fail.

In code, this can be implemented by attempting to move left, then forward, then right.

End Result

After implementing the above, I let my code run and watched the maze's output.

The maze being solved

The flag

Code

You can view my code below, but beware the large amount of debug output.

Launcher.java

Download here.

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Launcher {

    /** Buffer that sends a command to the server. "char + '\n'" */
    private static byte[] outBuffer = new byte[2];
    /** The connected socket's output stream. */
    private static OutputStream conOut;

    /** The current solver. */
    public static Solver solver;
    /** A frame that displays the current progress. */
    public static JFrame displayFrame;

    /**
     * Connects and solves the maze.
     * @param args
     */
    public static void main(String[] args) {
        // Globals
        outBuffer[1] = "\n".getBytes()[0];

        // Create solver
        solver = new Solver();

        // Create demo display
        displayFrame = new JFrame("Debug display");
        displayFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        displayFrame.add(new JPanel() {
            @Override
            public void paint(Graphics g) {
                paintUpdate(g);
            }
        });
        displayFrame.setVisible(true);

        // Solve the maze
        Socket soc = null;

        try {
            // Create connection
            soc = new Socket("34.211.117.64", 16000);
            BufferedReader in = new BufferedReader(new InputStreamReader(soc.getInputStream()));
            conOut = soc.getOutputStream();

            // Throw away the first line
            in.readLine();

            // Attempt to solve
            solver.solve(in);

            soc.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (RuntimeException e) {
            try{Thread.sleep(500);}catch(Exception err){err.printStackTrace();}
            e.printStackTrace();
        } finally {
            try {
                soc.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Paints the current solver status to the given graphics.
     * Functionized for GIF writing.
     * @param g Graphics object to paint
     */
    public static void paintUpdate(Graphics g) {
        // Font
        g.setFont(new Font("Monospaced", Font.PLAIN, 14));

        // Background
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, displayFrame.getComponent(0).getWidth(), displayFrame.getComponent(0).getHeight());

        // Orientation
        g.setColor(Color.BLACK);
        switch (solver.orientation) {
            case Solver.NORTH:
                g.drawString("^", 10, 500);
                break;
            case Solver.EAST:
                g.drawString(">", 10, 500);
                break;
            case Solver.SOUTH:
                g.drawString("V", 10, 500);
                break;
            case Solver.WEST:
                g.drawString("<", 10, 500);
                break;
        }

        // Nodes
        int offx = 300;
        int offy = 500;

        for (Node n : solver.nodes) {
            // Node background
            g.setColor(n == solver.currentNode ? Color.BLUE : Color.BLACK);
            g.fillRect(offx + n.x * 10, offy + -n.y * 10, 10, 10);

            // Node walls
            g.setColor(n.wallNorth ? Color.RED : Color.GREEN);
            g.fillRect(offx + n.x * 10, offy + -n.y * 10, 10, 1);
            g.setColor(n.wallEast ? Color.RED : Color.GREEN);
            g.fillRect(offx + n.x * 10 + 10 - 1, offy + -n.y * 10 + 1, 1, 8);
            g.setColor(n.wallSouth ? Color.RED : Color.GREEN);
            g.fillRect(offx + n.x * 10, offy + -n.y * 10 + 10 - 1, 10, 1);
            g.setColor(n.wallWest ? Color.RED : Color.GREEN);
            g.fillRect(offx + n.x * 10, offy + -n.y * 10 + 1, 1, 8);
        }

        // Maze message
        g.setColor(Color.BLACK);
        String[] lines = solver.lastPositionDisplay.split("\n");
        for (int i = 0; i < lines.length; i++) {
            g.drawString(lines[i], 30, 30 + 14 * i);
        }
    }

    /**
     * Sends the given direction to the server.
     * @param ch The direction to move (WASD).
     * @throws IOException
     */
    public static void sendDirection(char ch) throws IOException {
        System.out.println("SearchSend: '" + ch + "'\n\n");
        outBuffer[0] = (byte) ch;
        conOut.write(outBuffer);
        conOut.flush();
    }

}

Solver.java

Download here.

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.regex.Pattern;

/** A class to solve the LabyREnth 2017 programming challenge 3 maze. */
public class Solver {

    // Constants for directions of movement and looking
    public static final int NORTH = 0; // y++
    public static final int EAST = 1; // x++
    public static final int SOUTH = 2; // y--
    public static final int WEST = 3; // x--

    // Constants for directions of movement local to the current orientation
    public static final int FORWARD = 0;
    public static final int LEFT = 1;
    public static final int BACKWARD = 2;
    public static final int RIGHT = 3;

    // Constants for determining the maze's output
    public static final Pattern PAT_STRAIGHT = Pattern.compile("^ +/ +\\\\ +$");
    public static final Pattern PAT_RIGHT_TURN = Pattern.compile("^ +/ +\\| +$");
    public static final Pattern PAT_LEFT_TURN = Pattern.compile("^ +\\| +\\\\ +$");
    public static final Pattern PAT_DOUBLE_TURN = Pattern.compile("^ +\\| +\\| +$");
    public static final Pattern PAT_CONTINUES = Pattern.compile("^[ \\|]+/ +\\\\[ \\|]+$");
    public static final String WALL_TOP = "--------------------------------------------------------------------------------";
    public static final String WALL_BOTTOM = WALL_TOP;

    // Nodes describing the current surroundings
    public ArrayList<Node> nodes = new ArrayList<Node>();

    // Information about our location in the maze
    public int posX = 0;
    public int posY = 0;
    public Node currentNode = new Node();
    public int orientation = NORTH;
    public int moveNumber = 0;

    // For debugging
    public String lastPositionDisplay = "";
    //public GifSequenceWriter writer;

    public Solver() {
        // Initially, the only direction we can go is forward
        currentNode.setWall(false, orientation);
        currentNode.setWall(true, translateDirection(orientation, RIGHT));
        currentNode.setWall(true, translateDirection(orientation, BACKWARD));
        currentNode.setWall(true, translateDirection(orientation, LEFT));

        // Add node
        nodes.add(currentNode);
    }

    /**
     * Solves the maze.
     * @param in
     * @throws IOException
     */
    public void solve(BufferedReader in) throws IOException {
        /*try {
            File mFile = new File("/home/nelson/3dmaze.gif");
            writer = new GifSequenceWriter(new FileImageOutputStream(mFile), BufferedImage.TYPE_INT_RGB, 100, true);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }*/

        // 1. Look forward and determine what is around us
        // 2. Move forward
        // 3. If we can turn left, turns left. If we cannot go forward, turn right.
        // 4. Apply 'move protection': stop the maze from cheating.
        // 5. Repeat.
        while (true) {
            System.out.println("FIRST LINE OF LOOP");

            // Look at the space in front of us
            Node nextNode = null;

            try {
                nextNode = lookForward(in, moveNumber == 0, true);
            } catch (RuntimeException e) {
                //writer.close();
                e.printStackTrace();
                System.exit(1);
            }

            if (nextNode == null) {
                System.out.println("You turned into a wall...");
                throw new RuntimeException("Moved into a wall");
            }

            // Update the current node's understanding of what is ahead of it
            Node oldNode = currentNode.getNode(orientation);
            currentNode.setNode(nextNode, orientation);
            nodes.add(nextNode);
            currentNode = nextNode;

            if (oldNode != null) {
                nodes.remove(oldNode);
            }

            // Step into the new node
            sendMove(FORWARD);
            updatePositionMoveForward();

            // Turn if needed
            if (!currentNode.getWall(translateDirection(orientation, LEFT))) { // Left
                System.out.println("Left: Throw away");
                lookForward(in, false);
                System.out.println("Left: Done throwing away");
                sendMove(LEFT);
                //orientation = translateDirection(orientation, LEFT);
            } else if (!currentNode.getWall(orientation)) { // Straight
                // Nothing
            } else if (!currentNode.getWall(translateDirection(orientation, RIGHT))) { // Right
                System.out.println("Right: Throw away");
                lookForward(in, false);
                System.out.println("Right: Done throwing away");
                sendMove(RIGHT);
                //orientation = translateDirection(orientation, RIGHT);
            } else { // Turn around
                System.out.println("Turn: Throw away");
                lookForward(in, false);
                System.out.println("Turn: Done throwing away");
                sendMoveDiscardOutput(in, LEFT);
                sendMove(LEFT);
                //orientation = translateDirection(orientation, LEFT);
                //orientation = translateDirection(orientation, LEFT);
            }

            System.out.println("PATHFIND MOVE DONE");

            // Move protection
            moveProtection(in);

            // Update display
            Launcher.displayFrame.repaint();

            // Update gif
            //BufferedImage img = new BufferedImage(960, 900, BufferedImage.TYPE_INT_RGB);
            //Launcher.paintUpdate(img.getGraphics());

            //try {
            //  writer.writeToSequence(img);
            //} catch (IOException e) {
            //  e.printStackTrace();
            //}

            // Sleep until a new character is typed
            System.out.println("Press enter to continue");
            System.in.read();
        }
    }

    /**
     * Parses the maze's output to determine what is one node ahead.<br>
     * Invokes lookForward(in, movesShown, false)
     * @param in Input stream to read from.
     * @param movesShown If the server will say "The possible moves are a, w, s, and d."
     * @return The node one cell forward.
     * @throws IOException
     */
    private Node lookForward(BufferedReader in, boolean movesShown) throws IOException {
        return lookForward(in, movesShown, false);
    }

    // Information on the next node directly forwards. The current node can only be seen if we are face first in a wall.
    // Does not set position of node.
    /**
     * Parses the maze's output to determine what is one node ahead.<br>
     * Invokes lookForward(in, movesShown, false)
     * @param in Input stream to read from.
     * @param movesShown If the server will say "The possible moves are a, w, s, and d."
     * @param setDisplay Whether or not the server's output should be displayed on Launcher.displayFrame.
     * @return The node one cell forward.
     * @throws IOException
     */
    private Node lookForward(BufferedReader in, boolean movesShown, boolean setDisplay) throws IOException {
        if (setDisplay) lastPositionDisplay = "";

        // Determine how we are going to read
        String firstLine = nextLine(in);

        if (firstLine.startsWith("Ahead you see")) {
            firstLine = nextLine(in);
            if (setDisplay) lastPositionDisplay += firstLine + "\n";
        }

        if (WALL_TOP.matches(firstLine)) {
            boolean gotFlag = false;

            while (!(firstLine = in.readLine()).equals(WALL_BOTTOM)) {
                System.out.println("THREW: " + firstLine);
                if (setDisplay) lastPositionDisplay += firstLine + "\n";

                if (firstLine.contains("PAN")) {
                    gotFlag = true;
                }
            }

            if (gotFlag) {
                throw new RuntimeException("GOT FLAG!" + firstLine);
            }

            //throw new RuntimeException("Should not be thrown into a wall. If this is bad change the logic, but keep in mind that this function looks forwards and you only see a wall if you are in its node looking at it.");
            return null;
        }

        // Read current view
        String[] view = new String[20];
        view[0] = firstLine;
        System.out.println("READ: " + view[0]);

        for (int i = 1; i < view.length; i++) {
            view[i] = in.readLine();
            System.out.println("READ: " + view[i]);
            if (setDisplay) lastPositionDisplay += view[i] + "\n";
        }

        // Throw away newline at end of map and possible moves message
        if (movesShown) throwAway(in, "The possible moves are a, w, s, and d.");

        // Parse view from bottom up
        // First 7 characters should always be straight
        /*for (int i = view.length - 1; i >= view.length - 7; i--) {
            if (!PAT_STRAIGHT.matcher(view[i]).matches()) {
                throw new RuntimeException("Unexpected line: [see next line]\n" + view[i]);
            }
        }*/

        // Create node to return
        Node node = new Node();
        setNodeForwardLocation(node);
        node.setWall(false, translateDirection(orientation, BACKWARD));

        // Determine turn type
        String turnLine = view[view.length - 9];

        if (PAT_STRAIGHT.matcher(turnLine).matches()) {
            System.out.println("Sees straight!");
            node.setWall(true, translateDirection(orientation, LEFT));
            node.setWall(true, translateDirection(orientation, RIGHT));
            //System.out.println("Forward sees straight");
        } else if (PAT_RIGHT_TURN.matcher(turnLine).matches()) {
            System.out.println("Sees right turn!");
            node.setWall(true, translateDirection(orientation, LEFT));
            node.setWall(false, translateDirection(orientation, RIGHT));
            //System.out.println("Forward sees right");
        } else if (PAT_LEFT_TURN.matcher(turnLine).matches()) {
            System.out.println("Sees left turn!");
            node.setWall(false, translateDirection(orientation, LEFT));
            node.setWall(true, translateDirection(orientation, RIGHT));
            //System.out.println("Forward sees left");
        } else if (PAT_DOUBLE_TURN.matcher(turnLine).matches()) {
            System.out.println("Sees double turn!");
            node.setWall(false, translateDirection(orientation, LEFT));
            node.setWall(false, translateDirection(orientation, RIGHT));
            //System.out.println("Forward sees double");
        } else {
            throw new RuntimeException("Unkown turn type: [see next line]\n" + turnLine);
        }

        // Determine if node ends with a wall
        if (PAT_CONTINUES.matcher(view[4]).matches()) {
            System.out.println("Sees continue!");
            node.setWall(false, translateDirection(orientation, FORWARD));
            //System.out.println("Forward sees continues");
        } else {
            System.out.println("Sees ending!");
            node.setWall(true, translateDirection(orientation, FORWARD));
            //System.out.println("Forward sees a wall");
        }

        // Return the given node
        return node;
    }

    /**
     * Prevents the maze from cheating. Should be invoked every 8th or 9th turn.
     * @param in
     * @throws IOException
     */
    private void moveProtection(BufferedReader in) throws IOException {
        // Determine if move protection is needed
        int movesToUse = 0;

        /*if ((moveNumber + 3) % 10 == 0) {
            movesToUse = 3;
        } else if ((moveNumber + 2) % 10 == 0) {
            movesToUse = 2;
        }*/
        if ((moveNumber + 1) % 10 == 0) {
            movesToUse = 1;
        } else if ((moveNumber + 2) % 10 == 0) {
            movesToUse = 2;
        }

        // Use move protection
        if (movesToUse != 0) {
            System.out.println("MOVE PROTECTION START");

            // Look around for a wall
            if (currentNode.getWall(translateDirection(orientation, BACKWARD))) {
                // Move back for next x turns
                for (int i = 0; i < movesToUse; i++) {
                    sendMoveDiscardOutput(in, BACKWARD);
                }
            } else if (currentNode.getWall(orientation)) {
                // Move forward for next x turns
                for (int i = 0; i < movesToUse; i++) {
                    sendMoveDiscardOutput(in, FORWARD);
                }
            } else {
                // Move left or right
                // Backs into the wall
                if (currentNode.getWall(translateDirection(orientation, LEFT))) {
                    sendMoveDiscardOutput(in, RIGHT);

                    for (int i = 0; i < movesToUse; i++) {
                        sendMoveDiscardOutput(in, BACKWARD);
                    }

                    sendMoveDiscardOutput(in, LEFT);
                } else if (currentNode.getWall(translateDirection(orientation, RIGHT))) {
                    sendMoveDiscardOutput(in, LEFT);

                    for (int i = 0; i < movesToUse; i++) {
                        sendMoveDiscardOutput(in, BACKWARD);
                    }

                    sendMoveDiscardOutput(in, RIGHT);
                } else {
                    throw new RuntimeException("Can't manipulate 10th move! Look further ahead or something bro.");
                }
            }

            // Back 

            /*
            // Send move backwards
            sendMove(BACKWARD);
            lookForward(in, false); // Throw away

            // If we actually moved back, send move forwards
            if (!currentNode.getWall(translateDirection(ori, BACKWARD))) {
                sendMove(FORWARD);
                lookForward(in, false);  // Throw away
            }
            */

            System.out.println("MOVE PROTECTION END");
        }
    }

    /**
     * Moves the node to be one space ahead of our current location in the maze.
     * @param node
     */
    private void setNodeForwardLocation(Node node) {
        switch (orientation) {
            case NORTH:
                node.x = posX;
                node.y = posY + 1;
                break;
            case EAST:
                node.x = posX + 1;
                node.y = posY;
                break;
            case SOUTH:
                node.x = posX;
                node.y = posY - 1;
                break;
            case WEST:
                node.x = posX - 1;
                node.y = posY;
                break;
            default:
                throw new RuntimeException("Invalid orientation");
        }
    }

    /**
     * Updates our position (presumably during a move) from the given direction
     */
    private void updatePositionMoveForward() {
        switch (orientation) {
            case NORTH:
                posY++;
                break;
            case EAST:
                posX++;
                break;
            case SOUTH:
                posY--;
                break;
            case WEST:
                posX--;
                break;
            default:
                throw new RuntimeException("Invalid orientation");
        }
    }

    /** Throws out empty lines until we get one that is not empty. */
    private String nextLine(BufferedReader in) throws IOException {
        String ln;
        while ((ln = in.readLine()).isEmpty()) {}
        return ln;
    }

    /** Throws away lines until we get one that starts with the given string. */
    private void throwAway(BufferedReader in, String line) throws IOException {
        //System.out.println("Trying to throw away '" + line + "'...");
        String ln;
        while ((ln = in.readLine()).isEmpty()) {}

        if (!ln.startsWith(line)) {
            throw new RuntimeException("Does not start with expected string: " + ln);
        }

        //System.out.println("Threw away");
    }

    /**
     * Sends a move to the server and discards the resulting maze output
     * @param in
     * @param dir
     * @throws IOException
     */
    private void sendMoveDiscardOutput(BufferedReader in, int dir) throws IOException {
        sendMove(dir);
        lookForward(in, false);
    }

    /**
     * Sends a move to the server.
     * @param dir
     * @throws IOException
     */
    private void sendMove(int dir) throws IOException {
        // Change stored direction, increment move number
        if (dir != BACKWARD) // Moving backwards does not change the orientation
            orientation = translateDirection(orientation, dir);
        moveNumber++;
        System.out.println("Move number is now: " + moveNumber + " (sendMove)");

        // Send direction change to server
        switch (dir) {
            case FORWARD:
                Launcher.sendDirection('w');
                break;
            case RIGHT:
                Launcher.sendDirection('d');
                break;
            case BACKWARD:
                Launcher.sendDirection('s');
                break;
            case LEFT:
                Launcher.sendDirection('a');
                break;
            default:
                throw new RuntimeException("Unkown move!");
        }
    }

    /**
     * Translates the given direction from location space to global space.
     * @param direction Our current orientation.
     * @param localDirection The local direction.
     * @return localDirection to global direction.
     */
    public static int translateDirection(int direction, int localDirection) {
        // Beware lazy and inefficient orientation systems

        if (localDirection == FORWARD) {
            return direction;
        }

        switch (direction) {
            case NORTH:
                if (localDirection == RIGHT) return EAST;
                if (localDirection == BACKWARD) return SOUTH;
                if (localDirection == LEFT) return WEST;
            case EAST:
                if (localDirection == RIGHT) return SOUTH;
                if (localDirection == BACKWARD) return WEST;
                if (localDirection == LEFT) return NORTH;
            case SOUTH:
                if (localDirection == RIGHT) return WEST;
                if (localDirection == BACKWARD) return NORTH;
                if (localDirection == LEFT) return EAST;
            case WEST:
                if (localDirection == RIGHT) return NORTH;
                if (localDirection == BACKWARD) return EAST;
                if (localDirection == LEFT) return SOUTH;
        }

        throw new RuntimeException("Invalid direction or localDirection.");
    }

}

Node.java

Download here.

/** Represents a single node in the maze.. */
public class Node {

    // Cell's location
    public int x = 0;
    public int y = 0;

    // The walls surrounding this cell
    public boolean wallNorth = false; // y + 1
    public boolean wallEast = false; // x + 1
    public boolean wallSouth = false; // y - 1
    public boolean wallWest = false; // x - 1

    // The cells surrounding this cell
    public Node nodeNorth = null; // y + 1
    public Node nodeEast = null; // x + 1
    public Node nodeSouth = null; // y - 1
    public Node nodeWest = null; // x - 1

    /**
     * Returns if this node has a wall in the given direction.
     * @param direction Global space direction
     * @return True if there is a wall there, false if not.
     */
    public boolean getWall(int direction) {
        switch (direction) {
            case Solver.NORTH:
                return wallNorth;
            case Solver.EAST:
                return wallEast;
            case Solver.SOUTH:
                return wallSouth;
            case Solver.WEST:
                return wallWest;
            default:
                throw new RuntimeException("Invalid direction " + direction);
        }
    }

    /**
     * Sets if this node has a wall in the given direction.
     * @param value True if there is a wall, false if not.
     * @param direction Global space direction
     */
    public void setWall(boolean value, int direction) {
        switch (direction) {
            case Solver.NORTH:
                wallNorth = value;
                break;
            case Solver.EAST:
                wallEast = value;
                break;
            case Solver.SOUTH:
                wallSouth = value;
                break;
            case Solver.WEST:
                wallWest = value;
                break;
        }
    }

    /**
     * Gets the node in the given direction.
     * @param direction True if there is a wall, false if not.
     * @return
     */
    public Node getNode(int direction) {
        switch (direction) {
            case Solver.NORTH:
                return nodeNorth;
            case Solver.EAST:
                return nodeEast;
            case Solver.SOUTH:
                return nodeSouth;
            case Solver.WEST:
                return nodeWest;
            default:
                throw new RuntimeException("Invalid direction " + direction);
        }
    }

    /**
     * Sets the node in the given direction.
     * @param value True if there is a wall, false if not.
     * @param direction
     */
    public void setNode(Node value, int direction) {
        switch (direction) {
            case Solver.NORTH:
                nodeNorth = value;
                break;
            case Solver.EAST:
                nodeEast = value;
                break;
            case Solver.SOUTH:
                nodeSouth = value;
                break;
            case Solver.WEST:
                nodeWest = value;
                break;
        }
    }

}

links

social