6
\$\begingroup\$

I created a basic jQuery plugin to make a basic sliding tile puzzle.

/*
 * tilePuzzle.src.js
 *
 * A simple tile puzzle jQuery plugin by @matthewfedak
 *
 */

/*jslint browser: true*/
/*global $, jQuery, alert*/

(function ($) {

"use strict";

$.fn.tilePuzzle = function (options) {

    var self = $(this);

    var tilePuzzle = null;

    var defaults = {
        keyAccess: false,
        tileContainerClass: 'tiles',
        level: 4,
        maxWidth: 600
    };

    var construct = {

        container : null,

        imageDimensions : {},

        tileDimensions : {},

        spareTile: null,

        init : function () {

            var _this = this;

            _this.container = $('<ul/>').addClass(tilePuzzle.tileContainerClass);

            self.append(_this.container);

            if (tilePuzzle.level < 3) {

                tilePuzzle.level = 3;
            }

            _this.loadRemoteImage();

            _this.container.on('click', 'li', function () {

                _this.moveTile($(this), $(this).css('top'), $(this).css('left'));

            });

            return _this;

        },

        loadRemoteImage : function () {

            var _this = this;

            var img = new Image();

            img.onload = function () {

                _this.imageDimensions = { 'width': this.width, 'height': this.height };

                if (_this.imageDimensions.width > _this.maxWidth) {

                    var newHeight = (_this.maxWidth / _this.imageDimensions.width) * this.height;
                    _this.imageDimensions.width = _this.maxWidth;
                    _this.imageDimensions.height = newHeight;

                }

                _this.createTiles();

            };

            img.src = tilePuzzle.imageUrl;

            return _this;

        },

        createTiles : function () {

            var _this = this;
            var x, n, t = null;

            _this.tileDimensions = {
                'width': Math.abs(_this.imageDimensions.width / tilePuzzle.level),
                'height': Math.abs(_this.imageDimensions.height / tilePuzzle.level)
            };

            for (x = 0, n = 0, t = 0; t < tilePuzzle.level * tilePuzzle.level; t++) {

                _this.container.append(
                    $('<li/>').css({
                        'width': _this.tileDimensions.width,
                        'height': _this.tileDimensions.height,
                        'background-image': 'url(' + tilePuzzle.imageUrl + ')',
                        'background-position': '-' + (t * this.tileDimensions.width) + 'px -' + (x * _this.tileDimensions.height) + 'px',
                        'background-size': _this.imageDimensions.width + 'px ' + _this.imageDimensions.height + 'px',
                        'top': (x * _this.tileDimensions.height) + 'px',
                        'left': (n * _this.tileDimensions.width) + 'px'
                    })
                );

                n++;

                if (((t + 1) % tilePuzzle.level) === 0) {
                    x++;
                    n = 0;
                }

            }

            _this.container
                .children(':last-child')
                .addClass('spare');

            _this.spareTile = _this.container.find('.spare');

            self.css({
                'width': _this.imageDimensions.width,
                'height': _this.imageDimensions.height
            }).fadeIn();

            _this.scrambleTiles(tilePuzzle.level);

            return _this;

        },

        scrambleTiles : function (limit) {

            var _this, i, tile1, tile2, tile1Position, timer;

            _this = this;

            limit = limit * limit;

            i = 0;

            setTimeout(function () {
                timer = setInterval(function () {

                    tile1 = _this.container.children().eq(Math.floor((Math.random() * (tilePuzzle.level * tilePuzzle.level)) + 1));

                    tile2 = _this.container.children().eq(Math.floor((Math.random() * (tilePuzzle.level * tilePuzzle.level)) + 1));

                    tile1Position = { 'top': tile1.css('top'), 'left': tile1.css('left') };

                    tile1.css({
                        'top': tile2.css('top'),
                        'left': tile2.css('left')
                    });

                    tile2.css({
                        'top': tile1Position.top,
                        'left': tile1Position.left
                    });
                    i++;
                    if (i > limit) {
                        clearInterval(timer);
                    }
                }, 50);
            }, 1000);

        },

        moveTile: function (tile, top, left) {

            var _this, topDistance, leftDistance;

            _this = this;

            topDistance = Math.abs(parseInt(top, 10) - parseInt(_this.spareTile.css('top'), 10));

            leftDistance = Math.abs(parseInt(left, 10) - parseInt(_this.spareTile.css('left'), 10));

            if ((leftDistance + topDistance) <= Math.round(_this.tileDimensions.width) || (leftDistance + topDistance) <= Math.round(_this.tileDimensions.height)) {

                tile.css({
                    'top': _this.spareTile.css('top'),
                    'left': _this.spareTile.css('left')
                });

                _this.spareTile.css({
                    'left': left,
                    'top': top
                });

            }
        }
    };

    tilePuzzle = $.extend(construct, defaults, options);

    tilePuzzle.init();

    return self;
};

}(jQuery));

Links to demo is on my github page.

tilePuzzle is available on GitHub

Any sort of feedback is welcome.

\$\endgroup\$
1
\$\begingroup\$

The first thing I've noticed was this line:

(function ($) {

And I though to myself: No way!

Then I read at the end, and I saw this:

}(jQuery));

In the name of all Javascript developers, I thank you for taking this very simple step! You have no idea how much is helps us and helps you!


Inside your function, you have this:

var self = $(this);

Please, don't do that! Don't set anything on the self variable! The self points to the window object. You can use it by itself or using the dot notation. That variable is the only way to access the current window on a WebWorker. Please, leave it unset. Use $parent or something.


On your init, you have this:

_this.container.on('click', 'li', function () {

    _this.moveTile($(this), $(this).css('top'), $(this).css('left'));

});

You are calling jQuery just so that you can pass an instance to move the tile somewhere? You know, you would be better off without jQuery inside that handler. Like this:

_this.container.on('click', 'li', function () {

    _this.moveTile(this, this.style.top, this.style.left);

});

See? So much simpler!!! You can change your moveTile into this:

    moveTile: function (tile, top, left) {

        var _this = this;

        var topDistance = Math.abs(parseInt(top, 10) - parseInt(_this.spareTile.style.top, 10));

        var leftDistance = Math.abs(parseInt(left, 10) - parseInt(_this.spareTile.style.left, 10));

        if ((leftDistance + topDistance) <= Math.round(_this.tileDimensions.width)
            || (leftDistance + topDistance) <= Math.round(_this.tileDimensions.height)) {

            tile.style.top = _this.spareTile.style.top;
            tile.style.left = _this.spareTile.style.left;

            _this.spareTile.style.top = top;
            _this.spareTile.style.left = left;

        }
    }

Just make sure that _this.spareTile is an Element and not a jQuery instance. For that, on your createTiles, you have this:

_this.container
    .children(':last-child')
    .addClass('spare');

_this.spareTile = _this.container.find('.spare');

Which can be changed to:

_this.spareTile = _this.container
    .children(':last-child')
    .addClass('spare')[0];

If you want to keep using jQuery, you can change the handler to:

_this.container.on('click', 'li', function () {

    var $this = $(this);
    _this.moveTile($this, $this.css('top'), $this.css('left'));

});

Remember that event handlers must do what they have to do as quick as possible! If you can shave off 1ms, shave it!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.