Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <body>
  <div class="container">
    <main class="content">
      <div id="draw_area"></div>
      <div id="play_panel" class="">
        <button id="button1" class="control_button">Start</button>
        <button id="button2" class="control_button">Pause</button>
        <!--button id="button3" class="control_button">Clear Search</button-->
      </div>
  </main>
  <nav class="controls">

    <div id="algorithm_panel" class="panel right_panel">
      <header>
      <h3 class="header_title">Algorithm</h3>
      </header>
      <p>Select the search strategy and search options<p>

      <div class="accordion">
        <h3 id="astar_header"><a href="#">A*</a></h3>
        <div id="astar_section" class="finder_section">
          <header class="option_header">
            <h3>Heuristic</h3>
          </header>
          <div id="astar_heuristic" class="sub_options">
            <input type="radio" name="astar_heuristic" value="manhattan" checked />
            <label class="option_label">Manhattan</label> <br>
            <input type="radio" name="astar_heuristic" value="euclidean"/>
            <label class="option_label">Euclidean</label> <br>
            <input type="radio" name="astar_heuristic" value="octile"/>
            <label class="option_label">Octile</label> <br>
            <input type="radio" name="astar_heuristic" value="chebyshev"/>
            <label class="option_label">Chebyshev</label> <br>
          </div>

          <header class="option_header">
            <h3>Options</h3>
          </header>
          <div class="optional sub_options">
            <input type="checkbox" class="allow_diagonal" checked>
            <label class="option_label">Allow Diagonal</label> <br>
            <input type="checkbox" class="bi-directional">
            <label class="option_label">Bi-directional</label> <br>
            <input type="checkbox" class="dont_cross_corners">
            <label class="option_label">Don't Cross Corners</label> <br>
            <input class="spinner" name="astar_weight" value="1">
            <label class="option_label">Weight</label> <br>
          </div>
        </div>

        <h3 id="breadthfirst_header"><a href="#">Breadth-First-Search</a></h3>
        <div id="breadthfirst_section" class="finder_section">
          <header class="option_header">
            <h3>Options</h3>
          </header>
          <div class="optional sub_options">
            <input type="checkbox" class="allow_diagonal" checked>
            <label class="option_label">Allow Diagonal</label> <br>
            <input type="checkbox" class="bi-directional">
            <label class="option_label">Bi-directional</label> <br>
            <input type="checkbox" class="dont_cross_corners">
            <label class="option_label">Don't Cross Corners</label> <br>
          </div>
        </div>

        <h3 id="bestfirst_header"><a href="#">Best-First-Search</a></h3>
        <div id="bestfirst_section" class="finder_section">
          <header class="option_header">
            <h3>Heuristic</h3>
          </header>
          <div id="bestfirst_heuristic" class="sub_options">
            <input type="radio" name="bestfirst_heuristic" value="manhattan" checked />
            <label class="option_label">Manhattan</label> <br>
            <input type="radio" name="bestfirst_heuristic" value="euclidean"/>
            <label class="option_label">Euclidean</label> <br>
            <input type="radio" name="bestfirst_heuristic" value="octile"/>
            <label class="option_label">Octile</label> <br>
            <input type="radio" name="bestfirst_heuristic" value="chebyshev"/>
            <label class="option_label">Chebyshev</label> <br>
          </div>

          <header class="option_header">
            <h3>Options</h3>
          </header>
          <div class="optional sub_options">
            <input type="checkbox" class="allow_diagonal" checked>
            <label class="option_label">Allow Diagonal</label> <br>
            <input type="checkbox" class="bi-directional">
            <label class="option_label">Bi-directional</label> <br>
            <input type="checkbox" class="dont_cross_corners">
            <label class="option_label">Don't Cross Corners</label> <br>
          </div>
        </div>

        <h3 id="dijkstra_header"><a href="#">Dijkstra</a></h3>
        <div id="dijkstra_section" class="finder_section">
          <header class="option_header">
            <h3>Options</h3>
          </header>
          <div class="optional sub_options">
            <input type="checkbox" class="allow_diagonal" checked>
            <label class="option_label">Allow Diagonal</label> <br>
            <input type="checkbox" class="bi-directional">
            <label class="option_label">Bi-directional</label> <br>
            <input type="checkbox" class="dont_cross_corners">
            <label class="option_label">Don't Cross Corners</label> <br>
          </div>
        </div>

        <h3 id="jump_point_header"><a href="#">Jump Point Search</a></h3>
        <div id="jump_point_section" class="finder_section">
          <header class="option_header">
            <h3>Heuristic</h3>
          </header>
          <div id="jump_point_heuristic" class="sub_options">
            <input type="radio" name="jump_point_heuristic" value="manhattan" checked />
            <label class="option_label">Manhattan</label> <br>
            <input type="radio" name="jump_point_heuristic" value="euclidean"/>
            <label class="option_label">Euclidean</label> <br>
            <input type="radio" name="jump_point_heuristic" value="octile"/>
            <label class="option_label">Octile</label> <br>
            <input type="radio" name="jump_point_heuristic" value="chebyshev"/>
            <label class="option_label">Chebyshev</label> <br>
          </div>
          <header class="option_header">
            <h3>Options</h3>
          </header>
          <div class="optional sub_options">
            <input type="checkbox" class="track_recursion" checked>
            <label class="option_label">Visualize recursion</label> <br>
          </div>
        </div>

                <h3 id="ida_header"><a href="#">IDA*</a></h3>
        <div id="ida_section" class="finder_section">
          <header class="option_header">
            <h3>Heuristic</h3>
          </header>
          <div id="ida_heuristic" class="sub_options">
            <input type="radio" name="ida_heuristic" value="manhattan" checked />
            <label class="option_label">Manhattan</label> <br>
            <input type="radio" name="ida_heuristic" value="euclidean"/>
            <label class="option_label">Euclidean</label> <br>
            <input type="radio" name="ida_heuristic" value="octile"/>
            <label class="option_label">Octile</label> <br>
            <input type="radio" name="ida_heuristic" value="chebyshev"/>
            <label class="option_label">Chebyshev</label> <br>
          </div>
          <header class="option_header">
            <h3>Options</h3>
          </header>
          <div class="optional sub_options">
            <input type="checkbox" class="allow_diagonal" checked>
            <label class="option_label">Allow Diagonal</label> <br>
            <input type="checkbox" class="dont_cross_corners">
            <label class="option_label">Don't Cross Corners</label> <br>
            <input class="spinner" name="astar_weight" value="1">
            <label class="option_label">Weight</label> <br>
            <input class="spinner" name="time_limit" value="10">
            <label class="option_label">Seconds limit</label> <br>
            <input type="checkbox" class="track_recursion" checked />
            <label class="option_label">Visualize recursion</label> <br>
          </div>
        </div>

        <!-- Not working for some reason -->
        <!--h3 id="orth_jump_point_header"><a href="#">Orthogonal Jump Point Search</a></h3>
        <div id="orth_jump_point_section" class="finder_section">
          <header class="option_header">
            <h3>Heuristic</h3>
          </header>
          <div id="orth_jump_point_heuristic" class="sub_options">
            <input type="radio" name="orth_jump_point_heuristic" value="manhattan" checked />
            <label class="option_label">Manhattan</label> <br>
            <input type="radio" name="orth_jump_point_heuristic" value="euclidean"/>
            <label class="option_label">Euclidean</label> <br>
            <input type="radio" name="orth_jump_point_heuristic" value="octile"/>
            <label class="option_label">Octile</label> <br>
            <input type="radio" name="orth_jump_point_heuristic" value="chebyshev"/>
            <label class="option_label">Chebyshev</label> <br>
          </div>
          <header class="option_header">
            <h3>Options</h3>
          </header>
          <div class="optional sub_options">
            <input type="checkbox" class="track_recursion" checked>
            <label class="option_label">Visualize recursion</label> <br>
          </div>
        </div-->
      </div><!-- .accordion -->
    </div><!-- #algorithm_panel -->
  </nav>
  <aside class="help">
    <div id="instructions_panel" class="panel">
      <header>
        <h3 class="header_title">Help</h3>
        <!--span id="hide_instructions">hide</span-->
      </header>
      <div>
      <p>This is a visualization of the excellent <a href="https://github.com/qiao/PathFinding.js">Pathfinding.js</a> library. </p>
      <p>Draw a maze by clicking on the grid Then select a Search algorithm on the left and select start.</p>
      <h3 class="header_title">Stats</h3>
      <p>Results of the search:</p>
      <div id="stats" ></div>
      </div>
    </div>


  </aside>
  </div>
</body>
              
            
!

CSS

              
                body {
    background: #454C51;
    padding: 0px;
    margin: 0px;
    overflow: hidden;
    font-family: Helvetica;
    cursor:crosshair;
}

input {
    cursor: pointer;
}

h1, h2, h3, h4, h5, h6 {
    margin: 0px;
    padding: 0px;
    font-weight: normal;
}

.panel {
    position: fixed;
    background-color: #383D41;
    color: #e0e0e0;
    border-radius: 2px;
    cursor: default;
}

#algorithm_panel {
    left: 0px;
    top: 0px;
    width: 20vw;
    right: 20px;
    height: 100%;
}

#instructions_panel {
    top: 0px;
    right: 0px;
    width: 20vw;
    height: 100%;
}

#algorithm_panel p, #instructions_panel p {
    padding-left: 1.2vw;
    padding-right: 1.2vw;
    font-size: 1.32vw;
    line-height: 1.6vw;
}

#instructions_panel a {
  color: #66A3EB !important;
}

#play_panel {
    background: 0px;
    position: absolute;
    top: 10px !important;
    left: 21vw;
    width:20vw;
}

#stats {
    color: #B8E986;
    margin:1vw;
    padding: 5px;
    background: #272A2D;
    font-family: "Lucida Console", Monaco, monospace;
    font-size: 1.1vw;
    line-height: 1.5vw;
    border-radius: 2px;
    width: 17vw;
}

#hide_instructions {
    position: absolute;
    right: 25px;
    top: 10px;
    font-size: 90%;
    cursor: pointer;
}

#hide_instruction:hover {
    color: #fff;
    text-decoration: underline;
}

.header_title {
    line-height: 1.8vw;
    font-weight: 400;
    color:#CDE8FF;
    padding-left: 1.2vw;
    position: relative;
    padding-top: 2vw;
}

h1, h2, h3, h4, h5, h6 {
    font-weight: normal;
    margin: 0;
    padding: 0;
}

.panel {
    color: #e0e0e0;
    cursor: default;
}

.option_header {
    font-size: 80%;
    margin-left: 20px;
}

.option_label {
    cursor: pointer;
}

button {
    border: none;
    background: rgba(255, 255, 255, 0.7);
    border-radius: 5px;
    cursor: pointer;
    font-size: 1.3vw;
    min-height: 40px;
    padding: 2px 8px;
    margin: 0 2%;
    width: auto;
}

button:hover {
    background: rgba(255, 255, 255, 0.9);
}

#button1 {
    background: #3490E7;
    margin:8px;
    box-shadow: inset 0px -3px 0px 0px #2E81CF;
    border-radius: 4px;
    color: #FFFFFF;
    line-height: 15px;
}

.finder_section {
    border: solid 1px #ddd;
    border-radius: 5px;
    margin: 5px;
}

.sub_options {
    color: #A0AFB7;
    padding: 2px;
    font-size: 14px;
    margin: 2px 12px 0;
}

a {
    color: #33f;
}

footer {
    color: #333;
    font-size: 70%;
    text-align: center;
    position: fixed;
    width: 200px;
    bottom: 10px;
    left: 50%;
    margin-left: -100px;
    background: rgba(255, 255, 255, 0.5);
    padding: 5px;
    border-radius: 5px;
}

.white {
    color: #fff;
}

.green {
    color: #0d0;
}

.red {
    color: #ff9166;
}

.spinner {
    width: 2em;
    margin: 5px;
}

/* Overrides */
.ui-accordion {
    margin: 1vw !important;
    width: 18vw !important;
}
.ui-accordion .ui-accordion-header {
    background: #444a50 !important;
    padding: 6px;
    font-size: 16px;
    border-radius: 0px;
    border-bottom: solid 1px #636A70 !important;
}

.ui-accordion-icons .ui-accordion-header a {
    padding-left: 1.2em !important;
}

.ui-accordion .ui-accordion-header {
    background: none repeat scroll 0 0 rgba(0, 0, 0, 0.5);
    border: 1px solid #333;
    cursor: pointer;
    margin-top: 1px;
    position: relative;
}

.ui-accordion .ui-accordion-header {
    background: none repeat scroll 0 0 #444a50 !important;
    border-bottom: 1px solid #636a70 !important;
    border-radius: 0;
    font-size: 16px;
    padding: 6px;
}

.ui-accordion .ui-accordion-content {
    border-radius: 0 0 2px 2px;
    background: #272A2D;
    border: none;
    display: none;
    padding: 1vw;
    margin: 0px;
    overflow: auto;
    padding: 8px;
    position: relative;
}

              
            
!

JS

              
                $(document).ready(function() {
    if (!Raphael.svg) {
        window.location = './notsupported.html';
    }

    // suppress select events
    $(window).bind('selectstart', function(event) {
        event.preventDefault();
    });

    // initialize visualization
    Panel.init();
    Controller.init();
});

/**
 * The visualization controller will works as a state machine.
 * See files under the `doc` folder for transition descriptions.
 * See https://github.com/jakesgordon/javascript-state-machine
 * for the document of the StateMachine module.
 */
var Controller = StateMachine.create({
    initial: 'none',
    events: [
        {
            name: 'init',
            from: 'none',
            to:   'ready'
        },
        {
            name: 'search',
            from: 'starting',
            to:   'searching'
        },
        {
            name: 'pause',
            from: 'searching',
            to:   'paused'
        },
        {
            name: 'finish',
            from: 'searching',
            to:   'finished'
        },
        {
            name: 'resume',
            from: 'paused',
            to:   'searching'
        },
        {
            name: 'cancel',
            from: 'paused',
            to:   'ready'
        },
        {
            name: 'modify',
            from: 'finished',
            to:   'modified'
        },
        {
            name: 'reset',
            from: '*',
            to:   'ready'
        },
        {
            name: 'clear',
            from: ['finished', 'modified'],
            to:   'ready'
        },
        {
            name: 'start',
            from: ['ready', 'modified', 'restarting'],
            to:   'starting'
        },
        {
            name: 'restart',
            from: ['searching', 'finished'],
            to:   'restarting'
        },
        {
            name: 'dragStart',
            from: ['ready', 'finished'],
            to:   'draggingStart'
        },
        {
            name: 'dragEnd',
            from: ['ready', 'finished'],
            to:   'draggingEnd'
        },
        {
            name: 'drawWall',
            from: ['ready', 'finished'],
            to:   'drawingWall'
        },
        {
            name: 'eraseWall',
            from: ['ready', 'finished'],
            to:   'erasingWall'
        },
        {
            name: 'rest',
            from: ['draggingStart', 'draggingEnd', 'drawingWall', 'erasingWall'],
            to  : 'ready'
        },
    ],
});

$.extend(Controller, {

    gridSize: [Math.round($(window).width() / 30)  + 1, Math.round($(window).height() / 30)  + 1], // number of nodes horizontally and vertically
    operationsPerSecond: 300,

    /**
     * Asynchronous transition from `none` state to `ready` state.
     */
    onleavenone: function() {
        var numCols = this.gridSize[0],
            numRows = this.gridSize[1];

        this.grid = new PF.Grid(numCols, numRows);

        View.init({
            numCols: numCols,
            numRows: numRows
        });
        View.generateGrid(function() {
            Controller.setDefaultStartEndPos();
            Controller.bindEvents();
            View.generateMaze();
            //Controller.transition(); // transit to the next state (ready)

        });

        this.$buttons = $('.control_button');

        this.hookPathFinding();

        return StateMachine.ASYNC;
        // => ready
    },
    ondrawWall: function(event, from, to, gridX, gridY) {
        this.setWalkableAt(gridX, gridY, false);
        // => drawingWall
    },
    oneraseWall: function(event, from, to, gridX, gridY) {
        this.setWalkableAt(gridX, gridY, true);
        // => erasingWall
    },
    onsearch: function(event, from, to) {
        var grid,
            timeStart, timeEnd,
            finder = Panel.getFinder();

        timeStart = window.performance ? performance.now() : Date.now();
        grid = this.grid.clone();
        this.path = finder.findPath(
            this.startX, this.startY, this.endX, this.endY, grid
        );
        this.operationCount = this.operations.length;
        timeEnd = window.performance ? performance.now() : Date.now();
        this.timeSpent = (timeEnd - timeStart).toFixed(4);

        this.loop();
        // => searching
    },
    onrestart: function() {
        // When clearing the colorized nodes, there may be
        // nodes still animating, which is an asynchronous procedure.
        // Therefore, we have to defer the `abort` routine to make sure
        // that all the animations are done by the time we clear the colors.
        // The same reason applies for the `onreset` event handler.
        setTimeout(function() {
            Controller.clearOperations();
            Controller.clearFootprints();
            Controller.start();
        }, View.nodeColorizeEffect.duration * 1.2);
        // => restarting
    },
    onpause: function(event, from, to) {
        // => paused
    },
    onresume: function(event, from, to) {
        this.loop();
        // => searching
    },
    oncancel: function(event, from, to) {
        this.clearOperations();
        this.clearFootprints();
        // => ready
    },
    onfinish: function(event, from, to) {
        View.showStats({
            pathLength: PF.Util.pathLength(this.path),
            timeSpent:  this.timeSpent,
            operationCount: this.operationCount,
        });
        View.drawPath(this.path);
        // => finished
    },
    onclear: function(event, from, to) {
        this.clearOperations();
        this.clearFootprints();
        // => ready
    },
    onmodify: function(event, from, to) {
        // => modified
    },
    onreset: function(event, from, to) {
        setTimeout(function() {
            Controller.clearOperations();
            Controller.clearAll();
            Controller.buildNewGrid();
        }, View.nodeColorizeEffect.duration * 1.2);
        // => ready
    },

    /**
     * The following functions are called on entering states.
     */

    onready: function() {
        console.log('=> ready');
        this.setButtonStates({
            id: 1,
            text: 'Start',
            enabled: true,
            callback: $.proxy(this.start, this),
        }, {
            id: 2,
            text: 'Pause',
            enabled: false,
        }, {
            id: 3,
            text: 'Clear',
            enabled: true,
            callback: $.proxy(this.reset, this),
        });
        // => [starting, draggingStart, draggingEnd, drawingStart, drawingEnd]
    },
    onstarting: function(event, from, to) {
        console.log('=> starting');
        // Clears any existing search progress
        this.clearFootprints();
        this.setButtonStates({
            id: 2,
            enabled: true,
        });
        this.search();
        // => searching
    },
    onsearching: function() {
        console.log('=> searching');
        this.setButtonStates({
            id: 1,
            text: 'Restart',
            enabled: true,
            callback: $.proxy(this.restart, this),
        }, {
            id: 2,
            text: 'Pause',
            enabled: true,
            callback: $.proxy(this.pause, this),
        });
        // => [paused, finished]
    },
    onpaused: function() {
        console.log('=> paused');
        this.setButtonStates({
            id: 1,
            text: 'Resume',
            enabled: true,
            callback: $.proxy(this.resume, this),
        }, {
            id: 2,
            text: 'Cancel',
            enabled: true,
            callback: $.proxy(this.cancel, this),
        });
        // => [searching, ready]
    },
    onfinished: function() {
        console.log('=> finished');
        this.setButtonStates({
            id: 1,
            text: 'Restart',
            enabled: true,
            callback: $.proxy(this.restart, this),
        }, {
            id: 2,
            text: 'Clear',
            enabled: true,
            callback: $.proxy(this.clear, this),
        });
    },
    onmodified: function() {
        console.log('=> modified');
        this.setButtonStates({
            id: 1,
            text: 'Start',
            enabled: true,
            callback: $.proxy(this.start, this),
        }, {
            id: 2,
            text: 'Clear',
            enabled: true,
            callback: $.proxy(this.clear, this),
        });
    },

    /**
     * Define setters and getters of PF.Node, then we can get the operations
     * of the pathfinding.
     */
    hookPathFinding: function() {

        PF.Node.prototype = {
            get opened() {
                return this._opened;
            },
            set opened(v) {
                this._opened = v;
                Controller.operations.push({
                    x: this.x,
                    y: this.y,
                    attr: 'opened',
                    value: v
                });
            },
            get closed() {
                return this._closed;
            },
            set closed(v) {
                this._closed = v;
                Controller.operations.push({
                    x: this.x,
                    y: this.y,
                    attr: 'closed',
                    value: v
                });
            },
            get tested() {
                return this._tested;
            },
            set tested(v) {
                this._tested = v;
                Controller.operations.push({
                    x: this.x,
                    y: this.y,
                    attr: 'tested',
                    value: v
                });
            },
        };

        this.operations = [];
    },
    bindEvents: function() {
        $('#draw_area').mousedown($.proxy(this.mousedown, this));
        $(window)
            .mousemove($.proxy(this.mousemove, this))
            .mouseup($.proxy(this.mouseup, this));
    },
    loop: function() {
        var interval = 1000 / this.operationsPerSecond;
        (function loop() {
            if (!Controller.is('searching')) {
                return;
            }
            Controller.step();
            setTimeout(loop, interval);
        })();
    },
    step: function() {
        var operations = this.operations,
            op, isSupported;

        do {
            if (!operations.length) {
                this.finish(); // transit to `finished` state
                return;
            }
            op = operations.shift();
            isSupported = View.supportedOperations.indexOf(op.attr) !== -1;
        } while (!isSupported);

        View.setAttributeAt(op.x, op.y, op.attr, op.value);
    },
    clearOperations: function() {
        this.operations = [];
    },
    clearFootprints: function() {
        View.clearFootprints();
        View.clearPath();
    },
    clearAll: function() {
        this.clearFootprints();
        View.clearBlockedNodes();
    },
    buildNewGrid: function() {
        this.grid = new PF.Grid(this.gridSize[0], this.gridSize[1]);
    },
    mousedown: function (event) {
        var coord = View.toGridCoordinate(event.pageX, event.pageY),
            gridX = coord[0],
            gridY = coord[1],
            grid  = this.grid;

        if (this.can('dragStart') && this.isStartPos(gridX, gridY)) {
            this.dragStart();
            return;
        }
        if (this.can('dragEnd') && this.isEndPos(gridX, gridY)) {
            this.dragEnd();
            return;
        }
        if (this.can('drawWall') && grid.isWalkableAt(gridX, gridY)) {
            this.drawWall(gridX, gridY);
            return;
        }
        if (this.can('eraseWall') && !grid.isWalkableAt(gridX, gridY)) {
            this.eraseWall(gridX, gridY);
        }
    },
    mousemove: function(event) {
        var coord = View.toGridCoordinate(event.pageX, event.pageY),
            grid = this.grid,
            gridX = coord[0],
            gridY = coord[1];

        if (this.isStartOrEndPos(gridX, gridY)) {
            return;
        }

        switch (this.current) {
        case 'draggingStart':
            if (grid.isWalkableAt(gridX, gridY)) {
                this.setStartPos(gridX, gridY);
            }
            break;
        case 'draggingEnd':
            if (grid.isWalkableAt(gridX, gridY)) {
                this.setEndPos(gridX, gridY);
            }
            break;
        case 'drawingWall':
            this.setWalkableAt(gridX, gridY, false);
            break;
        case 'erasingWall':
            this.setWalkableAt(gridX, gridY, true);
            break;
        }
    },
    mouseup: function(event) {
        if (Controller.can('rest')) {
            Controller.rest();
        }
    },
    setButtonStates: function() {
        $.each(arguments, function(i, opt) {
            var $button = Controller.$buttons.eq(opt.id - 1);
            if (opt.text) {
                $button.text(opt.text);
            }
            if (opt.callback) {
                $button
                    .unbind('click')
                    .click(opt.callback);
            }
            if (opt.enabled === undefined) {
                return;
            } else if (opt.enabled) {
                $button.removeAttr('disabled');
            } else {
                $button.attr({ disabled: 'disabled' });
            }
        });
    },
    /**
     * When initializing, this method will be called to set the positions
     * of start node and end node.
     * It will detect user's display size, and compute the best positions.
     */
    setDefaultStartEndPos: function() {
        var width, height,
            marginRight, availWidth,
            centerX, centerY,
            endX, endY,
            nodeSize = View.nodeSize;

        width  = $(window).width();
        height = $(window).height();

        marginRight = $('#algorithm_panel').width();
        availWidth = width - marginRight;

        centerX = Math.ceil(availWidth / 2 / nodeSize);
        centerY = Math.floor(height / 2 / nodeSize);

        this.setStartPos(centerX - 2, centerY);
        this.setEndPos(centerX + 8, centerY);
    },

    setStartPos: function(gridX, gridY) {
        this.startX = gridX;
        this.startY = gridY;
        View.setStartPos(gridX, gridY);
    },
    setEndPos: function(gridX, gridY) {
        this.endX = gridX;
        this.endY = gridY;
        View.setEndPos(gridX, gridY);
    },
    setWalkableAt: function(gridX, gridY, walkable) {
        this.grid.setWalkableAt(gridX, gridY, walkable);
        View.setAttributeAt(gridX, gridY, 'walkable', walkable);
    },
    isStartPos: function(gridX, gridY) {
        return gridX === this.startX && gridY === this.startY;
    },
    isEndPos: function(gridX, gridY) {
        return gridX === this.endX && gridY === this.endY;
    },
    isStartOrEndPos: function(gridX, gridY) {
        return this.isStartPos(gridX, gridY) || this.isEndPos(gridX, gridY);
    },
});

/**
 * The pathfinding visualization.
 * It uses raphael.js to show the grids.
 */
var View = {
    nodeSize: 30, // width and height of a single node, in pixel
    nodeStyle: {
        normal: {
            fill: '#454C51',
            stroke: '#999',
            'stroke-opacity': 0.2, // the border
        },
        blocked: {
            fill: '#25476E',
            stroke: '#2D6FC9',
            'stroke-width': 1.5,
            'stroke-opacity': 0.8
        },
        start: {
            fill: '#E5DE81',
            stroke: '#FFF',
            'stroke-width': 1,
            'stroke-opacity': 0.8
        },
        end: {
            fill: '#6CA929',
            stroke: '#B8E986',
            'stroke-opacity': 0.5
        },
        opened: {
            fill: '#47668A',
            stroke: '#86B5EC',
            opacity: 0.5,
            'stroke-opacity': 0.2,
        },
        closed: {
            fill: '#5D7B9E',
            stroke: '#FFF',
            opacity: 0.5,
            'stroke-width': 0.5,
            'stroke-opacity': 0.8
        },
        failed: {
            fill: '#ff8888',
            'stroke-opacity': 0.2,
        },
        tested: {
            fill: '#e5e5e5',
            'stroke-opacity': 0.2,
        },
    },
    nodeColorizeEffect: {
        duration: 50,
    },
    nodeZoomEffect: {
        duration: 200,
        transform: 's1.2', // scale by 1.2x
        transformBack: 's1.0',
    },
    pathStyle: {
        stroke: '#FFCF00',
        'stroke-width': 1,
    },
    supportedOperations: ['opened', 'closed', 'tested'],
    init: function(opts) {
        this.numCols      = opts.numCols;
        this.numRows      = opts.numRows;
        this.paper        = Raphael('draw_area');
        this.$stats       = $('#stats');
    },
    /**
     * Generate the grid asynchronously.
     * This method will be a very expensive task.
     * Therefore, in order to not to block the rendering of browser ui,
     * I decomposed the task into smaller ones. Each will only generate a row.
     */
    generateGrid: function(callback) {
        var i, j, x, y,
            rect,
            normalStyle, nodeSize,
            createRowTask, sleep, tasks,
            nodeSize    = this.nodeSize,
            normalStyle = this.nodeStyle.normal,
            numCols     = this.numCols,
            numRows     = this.numRows,
            paper       = this.paper,
            rects       = this.rects = [],
            $stats      = this.$stats;

        paper.setSize(numCols * nodeSize, numRows * nodeSize);

        //this.generateMaze();

        createRowTask = function(rowId) {
            return function(done) {
                rects[rowId] = [];
                for (j = 0; j < numCols; ++j) {
                    x = j * nodeSize;
                    y = rowId * nodeSize;

                    rect = paper.rect(x, y, nodeSize, nodeSize);
                    rect.attr(normalStyle);
                    rects[rowId].push(rect);
                }
                $stats.text(
                    'generating grid ' +
                    Math.round((rowId + 1) / numRows * 100) + '%'
                );
                done(null);
            };
        };

        sleep = function(done) {
            setTimeout(function() {
                done(null);
            }, 0);
        };

        tasks = [];
        for (i = 0; i < numRows; ++i) {
            tasks.push(createRowTask(i));
            tasks.push(sleep);
        }

        async.series(tasks, function() {
            if (callback) {
                callback();
            }
        });
    },

    generateMaze: function(callback) {

        // Generate a very simple maze that introduces the idea to the user

        var x = Controller.startX + 5;
        var y = Controller.startY + -2;

        for (var i = 5 - 1; i >= 0; i--) {
            Controller.setWalkableAt(x, y, false);

            y++;
        };
        
        
        //Controller.setWalkableAt(13, 9, false);
        //Controller.setWalkableAt(13, 10, false);
        //Controller.setWalkableAt(13, 11, false);
        //Controller.setWalkableAt(13, 12, false);

        //console.log(maze)
        /* TODO: Generate Random Maze 
        maze = new MazeGenerator(30, 30);
        maze.generate();
        for (var i = 0; i < maze.graph.width; i++) {
            for(var j = 0; j < maze.graph.width; j++) {
                console.log(i, j)
                var start, end;

                View.setWalkableAt(i, j, false)
            }
        }
        */
    },

    setStartPos: function(gridX, gridY) {
        var coord = this.toPageCoordinate(gridX, gridY);
        if (!this.startNode) {
            this.startNode = this.paper.image(
                "https://s3-us-west-2.amazonaws.com/s.cdpn.io/230399/start.png",
                coord[0],
                coord[1],
                28,
                28
            ).attr(this.nodeStyle.normal)
             .animate(this.nodeStyle.start, 1000)
             .mouseover(function (e) {
                this[0].style.cursor = "move";                
             });
             
        } else {
            this.startNode.attr({ x: coord[0], y: coord[1] }).toFront();
        }
    },
    setEndPos: function(gridX, gridY) {
        var coord = this.toPageCoordinate(gridX, gridY);
        if (!this.endNode) {
            this.endNode = this.paper.image(
                "https://s3-us-west-2.amazonaws.com/s.cdpn.io/230399/end.png",
                coord[0] - 4,
                coord[1] - 4,
                40,
                40
            ).attr(this.nodeStyle.normal)
             .animate(this.nodeStyle.end, 1000)
             .mouseover(function (e) {
                this[0].style.cursor = "move";                
             });
        } else {
            this.endNode.attr({ x: coord[0], y: coord[1] }).toFront();
        }
    },
    /**
     * Set the attribute of the node at the given coordinate.
     */
    setAttributeAt: function(gridX, gridY, attr, value) {
        var color, nodeStyle = this.nodeStyle;
        switch (attr) {
        case 'walkable':
            color = value ? nodeStyle.normal.fill : nodeStyle.blocked.fill;
            this.setWalkableAt(gridX, gridY, value);
            break;
        case 'opened':
            this.colorizeNode(this.rects[gridY][gridX], nodeStyle.opened.fill);
            this.setCoordDirty(gridX, gridY, true);
            break;
        case 'closed':
            var coord = this.toPageCoordinate(gridX, gridY);
            this.colorizeNode(this.rects[gridY][gridX], nodeStyle.closed.fill);            
            this.setCoordDirty(gridX, gridY, true);
            break;
        case 'tested':
            color = (value === true) ? nodeStyle.tested.fill : nodeStyle.normal.fill;

            this.colorizeNode(this.rects[gridY][gridX], color);
            this.setCoordDirty(gridX, gridY, true);
            break;
        case 'parent':
            // XXX: Maybe draw a line from this node to its parent?
            // This would be expensive.
            break;
        default:
            console.error('unsupported operation: ' + attr + ':' + value);
            return;
        }
    },
    colorizeNode: function(node, color) {
        node.animate({
            fill: color
        }, this.nodeColorizeEffect.duration);
    },
    zoomNode: function(node) {
        node.toFront().attr({
            transform: this.nodeZoomEffect.transform,
        }).animate({
            transform: this.nodeZoomEffect.transformBack,
        }, this.nodeZoomEffect.duration);
    },
    setWalkableAt: function(gridX, gridY, value) {
        var node, i, blockedNodes = this.blockedNodes;
        if (!blockedNodes) {
            blockedNodes = this.blockedNodes = new Array(this.numRows);
            for (i = 0; i < this.numRows; ++i) {
                blockedNodes[i] = [];
            }
        }
        node = blockedNodes[gridY][gridX];

        if (value) {
            // clear blocked node
            if (node) {
                this.colorizeNode(node, this.rects[gridY][gridX].attr('fill'));
                
                this.zoomNode(node);
                setTimeout(function() {
                    node.remove();
                }, this.nodeZoomEffect.duration);
                blockedNodes[gridY][gridX] = null;
            }
        } else {
            // draw blocked node
            if (node) {
                return;
            }

            console.log(gridY, gridX)
            node = blockedNodes[gridY][gridX] = this.rects[gridY][gridX].clone();

            this.colorizeNode(node, this.nodeStyle.blocked.fill);
            node.attr(this.nodeStyle.blocked)
            this.zoomNode(node);
        }
    },
    clearFootprints: function() {
        var i, x, y, coord, coords = this.getDirtyCoords();
        for (i = 0; i < coords.length; ++i) {
            coord = coords[i];
            x = coord[0];
            y = coord[1];
            this.rects[y][x].attr(this.nodeStyle.normal);
            this.setCoordDirty(x, y, false);
        }
    },
    clearBlockedNodes: function() {
        var i, j, blockedNodes = this.blockedNodes;
        if (!blockedNodes) {
            return;
        }
        for (i = 0; i < this.numRows; ++i) {
            for (j = 0 ;j < this.numCols; ++j) {
                if (blockedNodes[i][j]) {
                    blockedNodes[i][j].remove();
                    blockedNodes[i][j] = null;
                }
            }
        }
    },
    drawPath: function(path) {
        if (!path.length) {
            return;
        }
        var svgPath = this.buildSvgPath(path);
        this.path = this.paper.path(svgPath).attr(this.pathStyle);

        var circles = [];
        
        for (var i = path.length - 1; i >= 0; i--) {
            var coord = this.toPageCoordinate(path[i][0], path[i][1]);

            /* Also draw a dot for each part of the path */
            if (!(this.startNode.attrs.x == coord[0] && this.startNode.attrs.y == coord[1])) {
            
                var circle = this.paper.image(
                "https://s3-us-west-2.amazonaws.com/s.cdpn.io/230399/dot.png",
                coord[0],
                coord[1],
                28,
                28)
                .animate(this.startNode, 1000)

                circles.push(circle)
            }
        };

        this.circles = circles;
        
    },
    /**
     * Given a path, build its SVG represention.
     */
    buildSvgPath: function(path) {
        var i, strs = [], size = this.nodeSize;

        strs.push('M' + (path[0][0] * size + size / 2) + ' ' +
                  (path[0][1] * size + size / 2));
        for (i = 1; i < path.length; ++i) {
            strs.push('L' + (path[i][0] * size + size / 2) + ' ' +
                      (path[i][1] * size + size / 2));
        }

        return strs.join('');
    },
    clearPath: function() {
        if (this.path) {
            this.path.remove();
        }
        
        if (this.circles) {
            for (var i = this.circles.length - 1; i >= 0; i--) {
                console.log(this.circles[i])
                this.circles[i].remove();
            };
            
        }
    },
    /**
     * Helper function to convert the page coordinate to grid coordinate
     */
    toGridCoordinate: function(pageX, pageY) {
        return [
            Math.floor(pageX / this.nodeSize),
            Math.floor(pageY / this.nodeSize)
        ];
    },
    /**
     * helper function to convert the grid coordinate to page coordinate
     */
    toPageCoordinate: function(gridX, gridY) {
        return [
            gridX * this.nodeSize,
            gridY * this.nodeSize
        ];
    },
    showStats: function(opts) {
        var texts = [
            'Path length: ' + Math.round(opts.pathLength * 100) / 100,
            'Number of moves: ' + opts.operationCount,
            'Time: ' + opts.timeSpent + 'ms'
        ];
        $('#stats').show().html(texts.join('<br>'));
    },
    setCoordDirty: function(gridX, gridY, isDirty) {
        var x, y,
            numRows = this.numRows,
            numCols = this.numCols,
            coordDirty;

        if (this.coordDirty === undefined) {
            coordDirty = this.coordDirty = [];
            for (y = 0; y < numRows; ++y) {
                coordDirty.push([]);
                for (x = 0; x < numCols; ++x) {
                    coordDirty[y].push(false);
                }
            }
        }

        this.coordDirty[gridY][gridX] = isDirty;
    },
    getDirtyCoords: function() {
        var x, y,
            numRows = this.numRows,
            numCols = this.numCols,
            coordDirty = this.coordDirty,
            coords = [];

        if (coordDirty === undefined) {
            return [];
        }

        for (y = 0; y < numRows; ++y) {
            for (x = 0; x < numCols; ++x) {
                if (coordDirty[y][x]) {
                    coords.push([x, y]);
                }
            }
        }
        return coords;
    },
};

/**
 * The control panel.
 */
var Panel = {
    init: function() {
        var $algo = $('#algorithm_panel');

        $('.panel').draggable();
        $('.accordion').accordion({
            collapsible: false,
        });
        $('.option_label').click(function() {
            $(this).prev().click();
        });
        $('#hide_instructions').click(function() {
            $('#instructions_panel').slideUp();
        });
        $('#play_panel').css({
            //top: $algo.offset().top + $algo.outerHeight() + 20
        });
        $('#button2').attr('disabled', 'disabled');
    },
    /**
     * Get the user selected path-finder.
     * TODO: clean up this messy code.
     */
    getFinder: function() {
        var finder, selected_header, heuristic, allowDiagonal, biDirectional, dontCrossCorners, weight, trackRecursion, timeLimit;
        
        selected_header = $(
            '#algorithm_panel ' +
            '.ui-accordion-header[aria-selected=true]'
        ).attr('id');
        
        switch (selected_header) {

        case 'astar_header':
            allowDiagonal = typeof $('#astar_section ' +
                                     '.allow_diagonal:checked').val() !== 'undefined';
            biDirectional = typeof $('#astar_section ' +
                                     '.bi-directional:checked').val() !=='undefined';
            dontCrossCorners = typeof $('#astar_section ' +
                                     '.dont_cross_corners:checked').val() !=='undefined';

            /* parseInt returns NaN (which is falsy) if the string can't be parsed */
            weight = parseInt($('#astar_section .spinner').val()) || 1;
            weight = weight >= 1 ? weight : 1; /* if negative or 0, use 1 */

            heuristic = $('input[name=astar_heuristic]:checked').val();
            if (biDirectional) {
                finder = new PF.BiAStarFinder({
                    allowDiagonal: allowDiagonal,
                    dontCrossCorners: dontCrossCorners,
                    heuristic: PF.Heuristic[heuristic],
                    weight: weight
                });
            } else {
                finder = new PF.AStarFinder({
                    allowDiagonal: allowDiagonal,
                    dontCrossCorners: dontCrossCorners,
                    heuristic: PF.Heuristic[heuristic],
                    weight: weight
                });
            }
            break;

        case 'breadthfirst_header':
            allowDiagonal = typeof $('#breadthfirst_section ' +
                                     '.allow_diagonal:checked').val() !== 'undefined';
            biDirectional = typeof $('#breadthfirst_section ' +
                                     '.bi-directional:checked').val() !== 'undefined';
            dontCrossCorners = typeof $('#breadthfirst_section ' +
                                     '.dont_cross_corners:checked').val() !=='undefined';
            if (biDirectional) {
                finder = new PF.BiBreadthFirstFinder({
                    allowDiagonal: allowDiagonal,
                    dontCrossCorners: dontCrossCorners
                });
            } else {
                finder = new PF.BreadthFirstFinder({
                    allowDiagonal: allowDiagonal,
                    dontCrossCorners: dontCrossCorners
                });
            }
            break;

        case 'bestfirst_header':
            allowDiagonal = typeof $('#bestfirst_section ' +
                                     '.allow_diagonal:checked').val() !== 'undefined';
            biDirectional = typeof $('#bestfirst_section ' +
                                     '.bi-directional:checked').val() !== 'undefined';
            dontCrossCorners = typeof $('#bestfirst_section ' +
                                     '.dont_cross_corners:checked').val() !=='undefined';
            heuristic = $('input[name=bestfirst_heuristic]:checked').val();
            if (biDirectional) {
                finder = new PF.BiBestFirstFinder({
                    allowDiagonal: allowDiagonal,
                    dontCrossCorners: dontCrossCorners,
                    heuristic: PF.Heuristic[heuristic]
                });
            } else {
                finder = new PF.BestFirstFinder({
                    allowDiagonal: allowDiagonal,
                    dontCrossCorners: dontCrossCorners,
                    heuristic: PF.Heuristic[heuristic]
                });
            }
            break;

        case 'dijkstra_header':
            allowDiagonal = typeof $('#dijkstra_section ' +
                                     '.allow_diagonal:checked').val() !== 'undefined';
            biDirectional = typeof $('#dijkstra_section ' +
                                     '.bi-directional:checked').val() !=='undefined';
            dontCrossCorners = typeof $('#dijkstra_section ' +
                                     '.dont_cross_corners:checked').val() !=='undefined';
            if (biDirectional) {
                finder = new PF.BiDijkstraFinder({
                    allowDiagonal: allowDiagonal,
                    dontCrossCorners: dontCrossCorners
                });
            } else {
                finder = new PF.DijkstraFinder({
                    allowDiagonal: allowDiagonal,
                    dontCrossCorners: dontCrossCorners
                });
            }
            break;

        case 'jump_point_header':
            trackRecursion = typeof $('#jump_point_section ' +
                                     '.track_recursion:checked').val() !== 'undefined';
            heuristic = $('input[name=jump_point_heuristic]:checked').val();
            
            finder = new PF.JumpPointFinder({
              trackJumpRecursion: trackRecursion,
              heuristic: PF.Heuristic[heuristic],
              diagonalMovement: PF.DiagonalMovement.IfAtMostOneObstacle
            });
            break;
        case 'orth_jump_point_header':
            trackRecursion = typeof $('#orth_jump_point_section ' +
                                     '.track_recursion:checked').val() !== 'undefined';
            heuristic = $('input[name=orth_jump_point_heuristic]:checked').val();

            finder = new PF.JumpPointFinder({
              trackJumpRecursion: trackRecursion,
              heuristic: PF.Heuristic[heuristic],
              diagonalMovement: PF.DiagonalMovement.Never
            });
            break;
        case 'ida_header':
            allowDiagonal = typeof $('#ida_section ' +
                                     '.allow_diagonal:checked').val() !== 'undefined';
            dontCrossCorners = typeof $('#ida_section ' +
                                     '.dont_cross_corners:checked').val() !=='undefined';
            trackRecursion = typeof $('#ida_section ' +
                                     '.track_recursion:checked').val() !== 'undefined';

            heuristic = $('input[name=jump_point_heuristic]:checked').val();

            weight = parseInt($('#ida_section input[name=astar_weight]').val()) || 1;
            weight = weight >= 1 ? weight : 1; /* if negative or 0, use 1 */

            timeLimit = parseInt($('#ida_section input[name=time_limit]').val());

            // Any non-negative integer, indicates "forever".
            timeLimit = (timeLimit <= 0 || isNaN(timeLimit)) ? -1 : timeLimit;

            finder = new PF.IDAStarFinder({
              timeLimit: timeLimit,
              trackRecursion: trackRecursion,
              allowDiagonal: allowDiagonal,
              dontCrossCorners: dontCrossCorners,
              heuristic: PF.Heuristic[heuristic],
              weight: weight
            });

            break;
        }

        return finder;
    }
};

              
            
!
999px

Console