Skip to main content
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Michael Klippel and Anna Nidhin
Ben Nadel at CFCamp 2023 (Freising, Germany) with: Michael Klippel Anna Nidhin

jQuery Demo: Creating A Sliding Image Puzzle Plug-In

By on

Since I have been getting into jQuery a lot lately, what with reading the books and playing around with code, I felt I should try a more complicated type of plug-in. As such, I have created this jQuery demo plug-in which creates sliding-image puzzles based on containers that have images. Running the demo page we get this output:

jQuery Demo Sliding Puzzle Plug-In Screen Shot

Click here to see an Online Demo of the page

As with all jQuery plug-ins, hooking it up is super easy; you just drop in the script (after your jQuery core file), then call the method on your jQuery stack object:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
	<title>jQuery Demo: Creating A Sliding Image Puzzle</title>

	<script type="text/javascript" src="jquery-latest.pack.js"></script>
	<script type="text/javascript" src="jquery.puzzle.js"></script>
	<script type="text/javascript">

		// When the document is ready, hook up the puzzle
		// functionality to the targeted DIVs.
		$(
			function(){
				$( "div.puzzle, p" ).puzzle( 100 );
			}
			);

	</script>
</head>
<body>

	<h1>
		jQuery Demo: Creating A Sliding Image Puzzle
	</h1>

	<p>
		Below, we are going to create a sliding image puzzle
		using a DIV that contains a given image.
	</p>

	<!--- First Puzzle. --->
	<div
		class="puzzle"
		style="float: left ; margin-right: 50px ;">

		<img
			src="./puzzle_girl1.jpg"
			width="333"
			height="500"
			alt="Puzzle Girl One"
			/>

	</div>

	<!--- Second Puzzle. --->
	<div
		class="puzzle"
		style="float: left ;">

		<img
			src="./puzzle_girl2.jpg"
			width="333"
			height="500"
			alt="Puzzle Girl Two"
			/>

	</div>

</body>
</html>

It's really simple. Most of the code above is just XHTML. The only part that is actually creating the puzzle is this:

$( "div.puzzle, p" ).puzzle( 100 );

In this case, 100 is the width/height of the individual puzzle pieces. I have left the P tag in the selector to demonstrate that this will only work on "valid" scenarios; meaning, this will only work on containers that contain an IMG element. Since our P tag (page description) has no nested IMG, it remains unaffected. Only the DIV tags which contain IMG tags get the puzzle behavior.

The Javascript code for the plug-in is a bit complicated, but not crazy:

// Here, we are going to define the jQuery puzzle
// plugin that will create the interface for each
// DIV that contains an image.
jQuery.fn.puzzle = function( intUserSize ){

	// Make sure that each of the parent elements
	// has a nested IMG tag. We don't want elements
	// that lack the image. Once we get those, then
	// loop over them to initialize functionality.
	return this.filter( ":has( img )" ).each(

		function( intI ){

			// This is the functionality that will initialize
			// the container and img for puzzle. This will only
			// be called ONCE the image has been loaded, and once
			// per each instance of the target puzzle.
			function InitPuzzle(){
				var jPiece = null;
				var intRowIndex, intColIndex, intI = 0;

				// Get the number of columns and rows.
				intColumns = Math.floor( jImg.width() / intSize );
				intRows = Math.floor( jImg.height() / intSize );

				// Get the puzzle width and height based on
				// the number of pieces (this may require some
				// cropping of the image).
				intPuzzleWidth = (intColumns * intSize);
				intPuzzleHeight = (intRows * intSize);

				// Empty the container element. We don't actually
				// want the image inside of it (or any of the
				// other elements that might be there).
				jContainer.empty();

				// Set the container CSS and dimensions.
				jContainer
					.css(
						{
							border: "1px solid black",
							overflow: "hidden",
							display: "block"
						}
						)
					.width( intPuzzleWidth )
					.height( intPuzzleHeight )
				;

				// Check to see how the container is positioned.
				// If is relative or absolute, we can keep it,
				// but if it is not those, then we need to set
				// is to relative explicitly.
				if (
					(jContainer.css( "position" ) != "relative") &&
					(jContainer.css( "position" ) != "absolute")
					){

					// The container element is not explicitly
					// positioned, so position it to be relative.
					jContainer.css( "position", "relative" );

				}


				// Loop over the columns and row to create each
				// of the pieces. At this point, we are not going to worry
				// about the dimensions of the board - that will happen next.
				for (var intRowIndex = 0 ; intRowIndex < intRows ; intRowIndex++){

					// For this row, add a new array.
					arr2DBoard[ intRowIndex ] = [];

					for (var intColIndex = 0 ; intColIndex < intColumns ; intColIndex++){

						// Create a new Div tag. We are using a DIV tag as
						// opposed to an anchor tag to get around the IE
						// bug that has flickering background images on links
						// when the browser is not caching images.
						jPiece = $( "<div><br /></div>" );

						// Set the css properties. Since all of the
						// pieces have the same background image, they
						// all have to have different offset positions.
						jPiece
							.css(
								{
									display: "block",
									float: "left",
									cursor: "pointer",
									backgroundImage: "url( '" + jImg.attr( "src" ) + "' )",
									backgroundRepeat: "no-repeat",
									backgroundPosition: (
										(intColIndex * -intSize) + "px " +
										(intRowIndex * -intSize) + "px"
										),
									position: "absolute",
									top: ((intSize * intRowIndex) + "px"),
									left: ((intSize * intColIndex) + "px")
								}
								)
							.width( intSize )
							.height( intSize )
						;

						// Set the HREF so that the click even registers.
						// Then, set up the click handler.
						jPiece
							.attr( "href", "javascript:void( 0 );" )
							.click( PieceClickHandler )
						;

						// Add the piece to the 2-D representation of the board.
						arr2DBoard[ intRowIndex ][ intColIndex ] = jPiece;

						// Add to DOM.
						jContainer.append( jPiece );

					}
				}


				// Make the last one opaque and give it a special "rel"
				// value so that we can easily loacate this one later on.
				arr2DBoard[ intRows - 1 ][ intColumns - 1 ]
					.css( "opacity", 0 )
					.attr( "rel", "empty" )
				;


				// In order to shuffle the board, we are going to simulate
				// a certain number of clicks. This is to ensure that any
				// state the board gets into, it is certain that the board
				// can get back into a "winning" state.
				for (intI = 0 ; intI < 100 ; intI++){

					// Select the piece that we want to "click".
					// We will do this by randomly selecting a row
					// and a column to click.
					jPiece = arr2DBoard[
						(Math.floor( Math.random() * intRows * intRows ) % intRows)
						][
						(Math.floor( Math.random() * intColumns * intColumns ) % intColumns)
						];

					// Simulate the click.
					jPiece.click();
				}


				// Now that we have initialized, turn on the animation.
				blnShowAnimation = true;

				// Return out.
				return( true );
			}


			// This sets up the click handler for the pieces.
			function PieceClickHandler( objEvent ){
				// Get the jQuery objects for the piece clicked as
				// well as the empty square within the board.
				var jPiece = $( this );
				var jEmpty = jContainer.find( "div[ rel = 'empty' ]" );

				// Get the CSS position for the current piece.
				var objPiecePos = {
					top: parseInt( jPiece.css( "top" ) ),
					left: parseInt( jPiece.css( "left" ) )
					};

				// Get the CSS position for the empty piece
				var objEmptyPos = {
					top: parseInt( jEmpty.css( "top" ) ),
					left: parseInt( jEmpty.css( "left" ) )
					};

				var intRowIndex, intColIndex = 0;


				// Check to see if we are in the middle of an animation.
				// If we are, then just return out since we don't want
				// to update values yet.
				if (blnInAnimation){
					return( false );
				}


				// Blur the current piece to get rid of the dotted box.
				jPiece.blur();

				// Base on the CSS of the current piece and the size of
				// each of the pieces, we can calculate the row and column
				// of the given piece.
				objPiecePos.row = (objPiecePos.top / intSize);
				objPiecePos.col = (objPiecePos.left / intSize);

				// Base on the CSS of the empty piece and the size of
				// each of the pieces, we can calculate the row and column
				// of the given piece.
				objEmptyPos.row = (objEmptyPos.top / intSize);
				objEmptyPos.col = (objEmptyPos.left / intSize);


				// Now that we have the row and column of the target piece
				// as well as the empty piece, we can check to see if anything
				// needs to be moved. Remember, we ONLY need to move pieces
				// if the target piece and the empty piece share a row
				// or a column.

				// Check to see if they share the same row.
				if (objPiecePos.row == objEmptyPos.row){

					// Check to see which direction we are moving in.
					if (objPiecePos.col > objEmptyPos.col){

						// Move left.
						for (intColIndex = objEmptyPos.col ; intColIndex < objPiecePos.col ; intColIndex++){
							arr2DBoard[ objPiecePos.row ][ intColIndex ] = arr2DBoard[ objPiecePos.row ][ intColIndex + 1 ];
						}

						// Put empty in place.
						arr2DBoard[ objPiecePos.row ][ intColIndex ] = jEmpty;

					} else {

						// Move right.
						for (intColIndex = objEmptyPos.col ; intColIndex > objPiecePos.col ; intColIndex--){
							arr2DBoard[ objPiecePos.row ][ intColIndex ] = arr2DBoard[ objPiecePos.row ][ intColIndex - 1 ];
						}

						// Put empty in place.
						arr2DBoard[ objPiecePos.row ][ intColIndex ] = jEmpty;

					}


					// Update the CSS of the entire row (to make it easy).
					for (intColIndex = 0 ; intColIndex < intColumns ; intColIndex++){

						if (blnShowAnimation){

							// Flag that an animation is about to being.
							blnInAnimation = true;

							// Animate the CSS move.
							arr2DBoard[ objPiecePos.row ][ intColIndex ].animate(
								{
									left: ((intSize * intColIndex) + "px")
								},
								200,
								function(){
									blnInAnimation = false;
								}
								);

						} else {

							// Update the CSS for the given piece.
							arr2DBoard[ objPiecePos.row ][ intColIndex ].css(
								"left",
								((intSize * intColIndex) + "px")
								);

						}

					}


				// Check to see if we should move vertically.
				} else if (objPiecePos.col == objEmptyPos.col){

					// Check to see which direction we are moving in.
					if (objPiecePos.row > objEmptyPos.row){

						// Move up.
						for (intRowIndex = objEmptyPos.row ; intRowIndex < objPiecePos.row ; intRowIndex++){
							arr2DBoard[ intRowIndex ][ objPiecePos.col ] = arr2DBoard[ intRowIndex + 1 ][ objPiecePos.col ];
						}

						// Put empty in place.
						arr2DBoard[ intRowIndex ][ objPiecePos.col ] = jEmpty;

					} else {

						// Move down.
						for (intRowIndex = objEmptyPos.row ; intRowIndex > objPiecePos.row ; intRowIndex--){
							arr2DBoard[ intRowIndex ][ objPiecePos.col ] = arr2DBoard[ intRowIndex - 1 ][ objPiecePos.col ];
						}

						// Put empty in place.
						arr2DBoard[ intRowIndex ][ objPiecePos.col ] = jEmpty;

					}


					// Update the CSS of the entire column (to make it easy).
					for (intRowIndex = 0 ; intRowIndex < intRows ; intRowIndex++){

						if (blnShowAnimation){

							// Flag that an animation is about to being.
							blnInAnimation = true;

							// Animate the CSS move.
							arr2DBoard[ intRowIndex ][ objPiecePos.col ].animate(
								{
									top: ((intSize * intRowIndex) + "px")
								},
								200,
								function(){
									blnInAnimation = false;
								}
								);

						} else {

							// Update the CSS for the given piece.
							arr2DBoard[ intRowIndex ][ objPiecePos.col ].css(
								"top",
								((intSize * intRowIndex) + "px")
								);

						}

					}


				}


				// Return false so nothing happens.
				return( false );
			}



			// ASSERT: At this point, we have defined all the class
			// methods for this plugin instance. Now, we can act on
			// the instance properties and call methods.


			// Get a jQUery reference to the container.
			var jContainer = $( this );

			// Get a jQuery reference to the first image
			// - this is the one that we will use to make
			// the image puzzle.
			var jImg = jContainer.find( "img:first" );

			// This is the array that will hold the 2-dimentional
			// representation of the board.
			var arr2DBoard = [];

			// The height and width of the puzzle.
			var intPuzzleWidth = 0;
			var intPuzzleHeight = 0;

			// The width / height of each piece. This can be overriden
			// by the user when the initialize the puzzle plug-in.
			var intSize = intUserSize || 100;

			// The number of columns that are in the board.
			var intColumns = 0;

			// The number of rows that in the board.
			var intRows = 0;

			// Flag for wether or not to show animation.
			var blnShowAnimation = false;

			// Flag for wether or not an animation is in the midst. We
			// are going to need this to prevent further clicking during
			// and anmiation sequence.
			var blnInAnimation = false;


			// Check check to make sure that the size value is valid.
			// Since this can be overridden by the user, we want to
			// make sure that it is not crazy.
			intSize = Math.floor( intSize );

			if ((intSize < 40) || (intSize > 200)){
				intSize = 100;
			}

			// Check to see if the image has complietely
			// loaded (for some reason, this does NOT
			// work with the attr() function). If the
			// image is complete, call Init right away.
			// If it has not loaded, then set an onload
			// handler for initialization.
			if ( jImg[ 0 ].complete ){

				// The image has loaded so call Init.
				InitPuzzle();

			} else {

				// The image has not loaded so set an
				// onload event handler to call Init.
				jImg.load(
					function(){
						InitPuzzle();
					}
					);

			}
		}

		);

There's some really cool stuff going on here (in my opinion). For starters, as I said above, we only want to act on elements that have valid XHTML configuration (container and nested img). To ensure this, we don't act on the initial jQuery stack - we act on a filtering of it:

// Make sure that each of the parent elements
// has a nested IMG tag. We don't want elements
// that lack the image. Once we get those, then
// loop over them to initialize functionality.
return this.filter( ":has( img )" ).each( .. );

Here, we are taking the current jQuery stack, and then further filtering it down to elements that have a nested IMG. Notice also that we are returning the result to maintain method chaining abilities. Be careful though! This will return the new jQuery stack, NOT the one containing the original selector result. I didn't feel that that would be desirable, but if it is, you could simply tack on .end() to the each() method.

Once I have the puzzle configured, I have to "shuffle" it so that it doesn't start out with a winning board. At first, I was just going to randomly place all the elements, but I was worried that this could potentially lead to a board that was not solvable. As such, to finish off the board initialization, I "mimic" 100 user clicks to the board:

// In order to shuffle the board, we are going to simulate
// a certain number of clicks. This is to ensure that any
// state the board gets into, it is certain that the board
// can get back into a "winning" state.
for (intI = 0 ; intI < 100 ; intI++){

	// Select the piece that we want to "click".
	// We will do this by randomly selecting a row
	// and a column to click.
	jPiece = arr2DBoard[
		(Math.floor( Math.random() * intRows * intRows ) % intRows)
		][
		(Math.floor( Math.random() * intColumns * intColumns ) % intColumns)
		];

	// Simulate the click.
	jPiece.click();

}

By doing this, jQuery will fire any click events that were bound to the target puzzle piece via jQuery. This will ensure that any initialized board will have a possible backwards path to success. The only real issue here is that the Math.random() method leaves a little bit to be desired, I think.

The puzzle pieces themselves are just DIV elements that use the nested image as their background image. The background images are positioned relative to the puzzle piece's initial layout prior to shuffling. Because all of the pieces are uniform, the plug-in will crop the main image slightly to ensure that the board maintains a specific aspect ratio. Therefore, it may be shorter and/or narrower than the original image.

Anyway, thought someone might find this interesting. It was a blast to write.

Want to use code from this post? Check out the license.

Reader Comments

15,640 Comments

@Todd,

Thanks. It was a lot of fun to build, and a great learning process. Getting a better handle on the plug-in process.

1 Comments

Hi Ben,

I've noticed that you posted a number of posts about jQuery lately. I was wondering what your thoughts are on jQuery in terms of putting it next to other frameworks, especially Prototype and Spry.

Personally I do not very much like Prototype because it tries to solve every single possible problem (ala Ruby). This also brings in the learning curve for Prototype because you're not able to just add it to your already existing knowledge of JavaScript (and programming) but instead you have to re-learn what looks like a programming language in its own right.

The problem I had with jQuery is that its so fragmented. As I understand it, jQuery is only a small library to manipulate the DOM and some basic Ajax stuff. If you want more you need plugins (which in itself is a cool idea). However, most of this extended functionality comes from a lot of different sources and is programmed by a lot of different people. What does this mean for the quality of these plugins? The other thing I noticed was how jQuery seems to be built around nameless functions, which in small snippets of code (like examples) look quite minimalistic and almost ideal. However, in the larger context, this nesting with nameless functions becomes very unreadable to me (especially if you have to revisit some code you wrote some months earlier).

What I really liked about Spry is its Object Oriented approach. The OO mindset that I already have I can immediately apply when using Spry. You can notice this in the way you instantiate Spry objects (with the 'new' keyword as in a normal programming language). However, my main problem with Spry was the way you put Spry elements (as with datasets) in the actual markup.

Do you have any thoughts on this?

15,640 Comments

@Luke,

I do not have much experience with the other Javascript frameworks. I have seen them in use, and I have dabbled very slightly, but I have never really used them much. When I saw jQuery, though, there was just something about it that sucked me in. It just seemed to be so simple, yet so powerful.

I think part of the problem was that most of the time, when I saw the other frameworks, it was all User Interface stuff (ie. things sliding around, sortable lists, modal windos)... these things were all nice, but I was never excited by them. When I saw how easy jQuery made it to select multiple elements AND act on ALL those elements with implicit iteration, I was floored. Maybe it was just right example, right time kind of a thing, but this framework felt powerful where as all the others I saw just felt "fancy".

I think one of the readability issues you have to deal with is just use a notation that YOU are comfortable with. Personally, I think the majority of jQuery code out there has horrible formatting. In every example you see, these nameless functions are put on the same line as function to which they are being passed:

$( "a" ).click( function(){
.... // click code here
....});

I find this to be highly unreadable, and even after reading several jQuery text books, I cannot get used to it. That is why you will see that my code (just take a look at the code in my blog post), places all function and object literals on a new line:

$.get(
...."ajax.cfm",
....{
........a:"b"
....},
....function( strData ){
........// call back code
....}
....);

Obviously the "...." is just for spacing in my comment, but as you can see, I place all my "new" entities on separate line. This has helped my readability tremendously.

As far as the OOP aspect, while I am not all that good at OOP, I find the jQuery interface to be highly object oriented. Take a look my code above (in the blog post); every time the .each() method is called on the original jQuery stack, it creates what amounts to an Object that has object methods (ex. InitPuzzle(), PieceClickHandler()) and object properties (ex. intSize, intPuzzleHeight, intPuzzleWidth). Then, user events (ie. clicking the puzzle piece), call object methods on that puzzle instance, which in tern act on object properties for that instance. This feels highly OOP to me.

I think jQuery is awesome. At first, I was afraid that I was giving in and admitting that I cannot write powerful Javascript on my own, but now that I have taken the plunge, I am seeing that in fact, my Javascript can be more powerful than it ever was before! jQuery makes me a better Javascript programmer, not a dependent one.

1 Comments

Hi Ben,

fun to see other people playing around with sliding puzzles!

Some weeks ago, I've released jqPuzzle, just another sliding puzzle for jQuery.

http://www.2meter3.de/jqPuzzle/

While your code is incredibly short, jqPuzzle adds a bunch of optional parameters for individual customization (number of rows/cols, initial appearance, animation speeds).

Watch out http://www.2meter3.de/jqPuzzle/demos.html for some styled demos.

I'd especially like to point you to my method of generating solvable boards: It's a simple function that is passed an initial board and returns a boolean value. The method itself uses some math magic (I'm not able to explain it in full detail, but it checks some permutations, read http://en.wikipedia.org/wiki/Fifteen_puzzle for more information).
On the other hand, I really like your approach as it is a perfect work-around for that particular issue.

Thanks for sharing your thoughts about writing this plugin!

15,640 Comments

@Ralf,

Your puzzle game is looking pretty slick. I especially like how you keep a faded out image of the puzzle in the board background. You also keep track of the moves... very slick. And, that Shuffle method is cool too. A lot of cool going on there :)

15,640 Comments

@Vince,

I am not sure how you could have user's play side by side since the computer mouse only allows for a single input.

1 Comments

two player or more is relatively simple: 1) allow for a solved puzzle to be recognized in the JS on a per click basis (ie, click, move, check for complete). 2) have a multi player page demo and have a 'ready to play' button - .click goes to $.ajax call and records them as ready for 2 minutes or so... after 2 min, if no 2nd (or more) player, resets the 'ready to play' and they can click again... if two people or more are ready in a 2 minute period, show them all a countdown and at zero enable the .click for the div's holding the photo slice BGs so the players can move them... when someone hits a solution OR after each click you could update the other players screens with how many clicks the other people have made or whether or not someone finished - record users by IP address and start/finish time so you can show the right people the right message. :)

Im working on #1 right now, thought it would be nice if when you finish the puzzle it fills in the missing square for you and says 'congrats'.

Thanks for a great clean piece of code!

15,640 Comments

@Scott,

That could be a fun little project. I am not sure how the numbers actually get generated. If I can look that up, building the game would definitely be doable.

15,640 Comments

@Raj,

Good question - I don't have a way in the script right now. I suppose you could number the DIVs and check their order after each move. When they get back in order, the puzzle is solved.

2 Comments

@raj,

That was another question I had. If I have time I will try and look into the div suggestion like Ben said.

15,640 Comments

@Mutuku,

Going back to original image can be done; but I wouldn't try to animate it (ie. solve the puzzle programmatically). What you could do is number the pieces and then simply re-order them based on number.

1 Comments

I am a trainer and much of what I train are procedures that must be done in a particular sequence. I was wondering if there was a way to set it up so that each tile of the puzzle was a different image. once the user got them all in order another image or some text would appear to tell them they got it right. Any ideas?

1 Comments

May i know how to count the puzzle movement steps ?
In which code line can i triggered for counting step?

1 Comments

I modified your shuffling routine so that it did not count down by just any simulated click, but by clicks that resulted in a movement of a row or column. It was easy:

1. Declare a global boolean to represent that the row/column did indeed move.

2. In the click handler for the pieces, add another 'else' after the two 'if/else' blocks that catch when the clicked piece is in the same row or column as the empty spot, and set the boolean from #1 to false.

3. In the for loop within the shuffling routine, set the boolean from #1 to 'true' at the top of the loop routine, and check after the .click() that it is still true, if so intI++ from within the loop. Remove the intI++ from the for loop constructor.

This resulted in a much better shuffled board, the 100 random clicks that ignored non-shifting of row/column were doing nothing a lot of the time.

One other enhancement I added was a "dummy" piece, with zindex:-1 and opacity:0.2 and backgroundcolor: red, that remains fixed at the grid location where the empty spot will be upon puzzle solving. The zindex property I lowered so that it did not block clicks on a moving piece in that position.

Thanks for a fine puzzle, I put it in my test page I made to play with jQuery while I learn it.

1 Comments

I had created a similar plugin, it also incorporates jigsaw with it, its called jquery jigsaw, not published but you can see it in my website http://www.markupmonks.com/
It was interesting to see your idea, probably much changed by now, I had to change many code to use it now as jQuery had many changes like no support for css('background') :)
Cheers!!

1 Comments

Hi Ben, thanks for the script. I really like how simple to generate a puzzle.
Anw, how can I detect, if the puzzle is completed?! is there any function or callback function?
Thanks Ben.

1 Comments

Hi Ben,

great work!

Can I use your puzzle source code for my projects? I guess "YES" :-)

Otherwise you wouldn't publish your source code.

Greatings from Germany :-D
Kerim

1 Comments

Hi,

Great puzzle script. I was wondering if you, or anybody else can help me with something. I want to turn on and off the puzzle.

<script style="text/javascript">
$("span#play").live("click", function(){
$( ".entry-content > p" ).toggleClass("puzzle");
		$(function(){
		$( ".puzzle" ).puzzle( 100 );
		});
 
});
</script>

When the page loads, adding the class "puzzle" to the "p" and loading the puzzle function transforms the image into a puzzle. I m not sure how to turn it off when taking the "puzzle" class away from the "p".

Regards,
Ciprian

1 Comments

Hi,

Your code is really great, thank you so much!
One question - is there a way to determine which piece will be left empty? e.g., if I'd prefer the top left piece of the original picture be blank every time (in a random position after the shuffle)?

Thanks again,
-Einav

1 Comments

Hi

My name is Riyaj. I saw your code. It is very interesting. I had some similar code but it's different and I had some hard time in implementing. If don't mind, can I use your code for my project.

Thank You

2 Comments

Great work,
can I use this code in my website?

how can i know if the user completed the game ? i need a callback function?

2 Comments

code with callback function
.................
// Here, we are going to define the jQuery puzzle
// plugin that will create the interface for each
// DIV that contains an image.
jQuery.fn.puzzle = function( intUserSize, callbackFunc ){

// Make sure that each of the parent elements
// has a nested IMG tag. We don't want elements
// that lack the image. Once we get those, then
// loop over them to initialize functionality.
return this.filter( ":has( img )" ).each(
function( intI ){

// This is the functionality that will initialize
// the container and img for puzzle. This will only
// be called ONCE the image has been loaded, and once
// per each instance of the target puzzle.
function InitPuzzle() {
shuffle = true;

var jPiece = null;
var intRowIndex, intColIndex, intI = 0;

// Get the number of columns and rows.
intColumns = Math.floor( jImg.width() / intSize );
intRows = Math.floor( jImg.height() / intSize );

// Get the puzzle width and height based on
// the number of pieces (this may require some
// cropping of the image).
intPuzzleWidth = (intColumns * intSize);
intPuzzleHeight = (intRows * intSize);

// Empty the container element. We don't actually
// want the image inside of it (or any of the
// other elements that might be there).
jContainer.empty();

// Set the container CSS and dimensions.
jContainer
.css(
{
border: "1px solid black",
overflow: "hidden",
display: "block"
}
)
.width( intPuzzleWidth )
.height( intPuzzleHeight )
;

// Check to see how the container is positioned.
// If is relative or absolute, we can keep it,
// but if it is not those, then we need to set
// is to relative explicitly.
if (
(jContainer.css( "position" ) != "relative") &&
(jContainer.css( "position" ) != "absolute")
){

// The container element is not explicitly
// positioned, so position it to be relative.
jContainer.css( "position", "relative" );

}


// Loop over the columns and row to create each
// of the pieces. At this point, we are not going to worry
// about the dimensions of the board - that will happen next.

for (var intRowIndex = 0 ; intRowIndex < intRows ; intRowIndex++){

// For this row, add a new array.
arr2DBoard[ intRowIndex ] = [];

for (var intColIndex = 0 ; intColIndex < intColumns ; intColIndex++){
// Create a new Div tag. We are using a DIV tag as
// opposed to an anchor tag to get around the IE
// bug that has flickering background images on links
// when the browser is not caching images.
jPiece = $( "<div></div>" ); //<br />

// Set the css properties. Since all of the
// pieces have the same background image, they
// all have to have different offset positions.
jPiece
.css(
{
display: "block",
float: "left",
cursor: "pointer",
backgroundImage: "url( '" + jImg.attr( "src" ) + "' )",
backgroundRepeat: "no-repeat",
backgroundPosition: (
(intColIndex * -intSize) + "px " +
(intRowIndex * -intSize) + "px"
),
position: "absolute",
border: "1px dashed rgb(255, 255, 255)",
top: ((intSize * intRowIndex) + "px"),
left: ((intSize * intColIndex) + "px")
}
)
.width( intSize )
.height( intSize )

;


// Set the HREF so that the click even registers.
// Then, set up the click handler.
jPiece
.attr( "href", "javascript:void( 0 );" )
//.attr( 'id', 's_'+intRowIndex+'_'+intColIndex)
.addClass('jPieces')
.attr('id', 's_'+ssort)
.attr('pos_top', intSize * intRowIndex + 'px')
.attr('pos_left', intSize * intColIndex + 'px')
.click( PieceClickHandler )
;

// Create id tag for every jPiece has a "s_1_2" string and his original number
ssort ++;

// Add the piece to the 2-D representation of the board.
arr2DBoard[ intRowIndex ][ intColIndex ] = jPiece;

// Add to DOM.
jContainer.append( jPiece );

}
}


// Make the last one opaque and give it a special "rel"
// value so that we can easily loacate this one later on.
arr2DBoard[ intRows - 1 ][ intColumns - 1 ]
.css( "opacity", 0 )
.attr( "rel", "empty" )
;


// In order to shuffle the board, we are going to simulate
// a certain number of clicks. This is to ensure that any
// state the board gets into, it is certain that the board
// can get back into a "winning" state.
for (intI = 0 ; intI < 100 ; intI++){

// Select the piece that we want to "click".
// We will do this by randomly selecting a row
// and a column to click.
jPiece = arr2DBoard[
(Math.floor( Math.random() * intRows * intRows ) % intRows)
][
(Math.floor( Math.random() * intColumns * intColumns ) % intColumns)
];

// Simulate the click.
jPiece.click();
}

shuffle = false;
// Now that we have initialized, turn on the animation.
blnShowAnimation = true;

// Return out.
return( true );
}


// This sets up the click handler for the pieces.
function PieceClickHandler( objEvent ){
// Get the jQuery objects for the piece clicked as
// well as the empty square within the board.
var jPiece = $( this );
var jEmpty = jContainer.find( "div[ rel = 'empty' ]" );

// Get the CSS position for the current piece.
var objPiecePos = {
top: parseInt( jPiece.css( "top" ) ),
left: parseInt( jPiece.css( "left" ) )
};

// Get the CSS position for the empty piece
var objEmptyPos = {
top: parseInt( jEmpty.css( "top" ) ),
left: parseInt( jEmpty.css( "left" ) )
};

var intRowIndex, intColIndex = 0;


// Check to see if we are in the middle of an animation.
// If we are, then just return out since we don't want
// to update values yet.
if (blnInAnimation){
return( false );
}


// Blur the current piece to get rid of the dotted box.
jPiece.blur();

// Base on the CSS of the current piece and the size of
// each of the pieces, we can calculate the row and column
// of the given piece.
objPiecePos.row = (objPiecePos.top / intSize);
objPiecePos.col = (objPiecePos.left / intSize);

// Base on the CSS of the empty piece and the size of
// each of the pieces, we can calculate the row and column
// of the given piece.
objEmptyPos.row = (objEmptyPos.top / intSize);
objEmptyPos.col = (objEmptyPos.left / intSize);


// Now that we have the row and column of the target piece
// as well as the empty piece, we can check to see if anything
// needs to be moved. Remember, we ONLY need to move pieces
// if the target piece and the empty piece share a row
// or a column.

// Check to see if they share the same row.
if (objPiecePos.row == objEmptyPos.row){

// Check to see which direction we are moving in.
if (objPiecePos.col > objEmptyPos.col){

// Move left.
for (intColIndex = objEmptyPos.col ; intColIndex < objPiecePos.col ; intColIndex++){
arr2DBoard[ objPiecePos.row ][ intColIndex ] = arr2DBoard[ objPiecePos.row ][ intColIndex + 1 ];
}

// Put empty in place.
arr2DBoard[ objPiecePos.row ][ intColIndex ] = jEmpty;

} else {

// Move right.
for (intColIndex = objEmptyPos.col ; intColIndex > objPiecePos.col ; intColIndex--){
arr2DBoard[ objPiecePos.row ][ intColIndex ] = arr2DBoard[ objPiecePos.row ][ intColIndex - 1 ];
}

// Put empty in place.
arr2DBoard[ objPiecePos.row ][ intColIndex ] = jEmpty;

}


// Update the CSS of the entire row (to make it easy).
for (intColIndex = 0 ; intColIndex < intColumns ; intColIndex++){

if (blnShowAnimation){

// Flag that an animation is about to being.
blnInAnimation = true;

// Animate the CSS move.
arr2DBoard[ objPiecePos.row ][ intColIndex ].animate(
{
left: ((intSize * intColIndex) + "px")
},
200,
function(){
blnInAnimation = false;

/// see if complete sort ok
//////////////////////////////////////////////////////////
if(!shuffle) {

var elms = $(this).parent('div').children();
for (var i =0; i< intRows * intColumns; i++ ) {
if(
elms.eq(i).css('top') != elms.eq(i).attr('pos_top')
||
elms.eq(i).css('left') != elms.eq(i).attr('pos_left')
)
return false;
}
done(elms);//alert('true');
}
//////////////////////////////////////////////////////////

}
);

} else {

// Update the CSS for the given piece.
arr2DBoard[ objPiecePos.row ][ intColIndex ].css(
"left",
((intSize * intColIndex) + "px")
);

}

}


// Check to see if we should move vertically.
} else if (objPiecePos.col == objEmptyPos.col){

// Check to see which direction we are moving in.
if (objPiecePos.row > objEmptyPos.row){

// Move up.
for (intRowIndex = objEmptyPos.row ; intRowIndex < objPiecePos.row ; intRowIndex++){
arr2DBoard[ intRowIndex ][ objPiecePos.col ] = arr2DBoard[ intRowIndex + 1 ][ objPiecePos.col ];
}

// Put empty in place.
arr2DBoard[ intRowIndex ][ objPiecePos.col ] = jEmpty;

} else {

// Move down.
for (intRowIndex = objEmptyPos.row ; intRowIndex > objPiecePos.row ; intRowIndex--){
arr2DBoard[ intRowIndex ][ objPiecePos.col ] = arr2DBoard[ intRowIndex - 1 ][ objPiecePos.col ];
}

// Put empty in place.
arr2DBoard[ intRowIndex ][ objPiecePos.col ] = jEmpty;

}


// Update the CSS of the entire column (to make it easy).
for (intRowIndex = 0 ; intRowIndex < intRows ; intRowIndex++){

if (blnShowAnimation){

// Flag that an animation is about to being.
blnInAnimation = true;

// Animate the CSS move.
arr2DBoard[ intRowIndex ][ objPiecePos.col ].animate(
{
top: ((intSize * intRowIndex) + "px")
},
200,
function(){
blnInAnimation = false;

/// see if complete sort ok
//////////////////////////////////////////////////////////
if(!shuffle) {

var elms = $(this).parent('div').children();
for (var i =0; i< intRows * intColumns; i++ ) {
if(
elms.eq(i).css('top') != elms.eq(i).attr('pos_top')
||
elms.eq(i).css('left') != elms.eq(i).attr('pos_left')
)
return false;
}
done(elms);//alert('true');
}
//////////////////////////////////////////////////////////

}
);

} else {

// Update the CSS for the given piece.
arr2DBoard[ intRowIndex ][ objPiecePos.col ].css(
"top",
((intSize * intRowIndex) + "px")
);


}

}
}




// Return false so nothing happens.
return( false );
}

function done(elms) {

//alert('');
elms.css("border", "none").animate({opacity: 0}, 600 );
elms.animate({opacity: 1}, 300, callbackFunc );

}


// ASSERT: At this point, we have defined all the class
// methods for this plugin instance. Now, we can act on
// the instance properties and call methods.

var shuffle = true;
var ssort=0;
// Get a jQUery reference to the container.
var jContainer = $( this );

// Get a jQuery reference to the first image
// - this is the one that we will use to make
// the image puzzle.
var jImg = jContainer.find( "img:first" );

// This is the array that will hold the 2-dimentional
// representation of the board.
var arr2DBoard = [];

// The height and width of the puzzle.
var intPuzzleWidth = 0;
var intPuzzleHeight = 0;

// The width / height of each piece. This can be overriden
// by the user when the initialize the puzzle plug-in.
var intSize = intUserSize || 100;

// The number of columns that are in the board.
var intColumns = 0;

// The number of rows that in the board.
var intRows = 0;

// Flag for wether or not to show animation.
var blnShowAnimation = false;

// Flag for wether or not an animation is in the midst. We
// are going to need this to prevent further clicking during
// and anmiation sequence.
var blnInAnimation = false;


// Check check to make sure that the size value is valid.
// Since this can be overridden by the user, we want to
// make sure that it is not crazy.
intSize = Math.floor( intSize );

if ((intSize < 40) || (intSize > 200)){
intSize = 100;
}

// Check to see if the image has complietely
// loaded (for some reason, this does NOT
// work with the attr() function). If the
// image is complete, call Init right away.
// If it has not loaded, then set an onload
// handler for initialization.
if ( jImg[ 0 ].complete ){

// The image has loaded so call Init.
InitPuzzle();

} else {

// The image has not loaded so set an
// onload event handler to call Init.
jImg.load (
function() {
InitPuzzle();
}
);

}
}

);
}

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel