381 lines
10 KiB
JavaScript
381 lines
10 KiB
JavaScript
/*jPuzzle
|
|
jQuery plugin (http://jquery.com/)
|
|
jQuery => Copyright 2010, John Resig
|
|
jPuzzle => Copyright 2010, Adrien Guéret
|
|
Dual licensed under the MIT or GPL Version 2 licenses.
|
|
http://jquery.org/license
|
|
Last update: 26/07/2011*/
|
|
(function($)
|
|
{
|
|
$.fn.jPuzzle=function(options)
|
|
{
|
|
var defaults=
|
|
{
|
|
'rows': 5,
|
|
'columns': 5,
|
|
'help': true,
|
|
'fixed': true,
|
|
'showHelpButton': true,
|
|
'textPlayButton': 'Play !',
|
|
'textHelpButtonShow': 'Show final image',
|
|
'textHelpButtonHide': 'Hide final image',
|
|
'onstart': null,
|
|
'onend': null
|
|
};
|
|
|
|
var parameters=$.extend(defaults,options);
|
|
|
|
parameters.rows=Math.max(1,parameters.rows);
|
|
parameters.columns=Math.max(1,parameters.columns);
|
|
|
|
//Format time in seconds to HH"MM'SS
|
|
function formatTime(s)
|
|
{
|
|
s=parseInt(s,10);
|
|
|
|
var h=Math.floor(s/3600);
|
|
s-=h*3600;
|
|
var formatTime=(h>9?h:'0'+h)+'"';
|
|
|
|
var m=Math.floor(s/60);
|
|
s-=m*60;
|
|
formatTime+=(m>9?m:'0'+m)+'\'';
|
|
|
|
return formatTime+((s>9?s:'0'+s));
|
|
}
|
|
|
|
//Function waiting for the loading of the image
|
|
function loading()
|
|
{
|
|
if($(this).is('img'))
|
|
{
|
|
//If the image is not loaded, we have to wait for it
|
|
if(this.complete)
|
|
{
|
|
init($(this));
|
|
}
|
|
else
|
|
{
|
|
$(this).load(function()
|
|
{
|
|
init($(this));
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function init(obj)
|
|
{
|
|
//We store the image dimensions...
|
|
var width=obj.width();
|
|
var height=obj.height();
|
|
|
|
//... and the source of the image
|
|
var src=obj.attr('src');
|
|
|
|
//We calculate the pieces dimensions
|
|
var pieceWidth=width/parameters.columns;
|
|
var pieceHeight=height/parameters.rows;
|
|
|
|
//And the height of the box containing all the pieces
|
|
var sizeBox=pieceHeight>height/2?pieceHeight:height/2;
|
|
|
|
//The main container
|
|
var game=$('<span></span>').css(
|
|
{
|
|
'width': width+'px',
|
|
'display': 'inline-block',
|
|
'position': 'relative'
|
|
}).addClass('jPuzzle-game').insertAfter(obj);
|
|
|
|
//The tag containing the puzzle
|
|
var puzzle=$('<span></span>').css(
|
|
{
|
|
'width': width+'px',
|
|
'height': height+'px',
|
|
'display': 'inline-block',
|
|
'position': 'relative'
|
|
}).addClass('jPuzzle-container').appendTo(game);
|
|
|
|
//The tag displaying the image as a background
|
|
var image=$('<span></span>').css(
|
|
{
|
|
'width': width+'px',
|
|
'height': height+'px',
|
|
'opacity': 1,
|
|
'background-image': 'url("'+src+'")',
|
|
'display': 'inline-block'
|
|
}).addClass('jPuzzle-image').appendTo(puzzle);
|
|
|
|
//Box where the pieces will be generated
|
|
var box=$('<span></span>').css(
|
|
{
|
|
'width': width+'px',
|
|
'minHeight': sizeBox+'px',
|
|
'display': 'inline-block',
|
|
'position': 'relative'
|
|
}).addClass('jPuzzle-box').droppable(
|
|
{
|
|
'accept': '.jPuzzle-piece',
|
|
'addClasses': false,
|
|
'drop': function(event,ui)
|
|
{
|
|
var piece=ui.draggable;
|
|
var lastLocation=$.data(piece[0],'lastLocation');
|
|
if(lastLocation[0]!=this)
|
|
{
|
|
/* If the piece comes from a puzzle location,
|
|
we have to adjust its position */
|
|
piece.appendTo($(this));
|
|
piece.css(
|
|
{
|
|
'left': '+='+lastLocation.position().left,
|
|
'top':(piece.position().top+lastLocation.position().top-puzzle.outerHeight())+'px'
|
|
});
|
|
$.data(piece[0],'lastLocation',$(this));
|
|
}
|
|
}
|
|
}).hide().insertAfter(puzzle);
|
|
|
|
//Foot bar
|
|
var infos=$('<span></span>').css(
|
|
{
|
|
'width': width+'px',
|
|
'display': 'inline-block'
|
|
}).addClass('jPuzzle-infos').insertAfter(box);
|
|
|
|
//The data-time attribute will contain the elapsed time
|
|
var infosTime=$('<span></span>').addClass('jPuzzle-time').attr('data-time',0).hide().appendTo(infos);
|
|
|
|
var playButton=$('<input type="button" />').val(parameters.textPlayButton).addClass('jPuzzle-playButton').click(function()
|
|
{
|
|
startGame();
|
|
$(this).hide();
|
|
}).appendTo(infos);
|
|
|
|
if(parameters.showHelpButton)
|
|
{
|
|
var helpButton=$('<input type="button" />').val(parameters.textHelpButtonShow).addClass('jPuzzle-helpButton').toggle(function()
|
|
{
|
|
//The final image is not displayed, we show it!
|
|
$(this).val(parameters.textHelpButtonHide).attr('disabled',true);
|
|
image.animate(
|
|
{
|
|
'opacity': 1
|
|
},'slow',function()
|
|
{
|
|
helpButton.removeAttr('disabled');
|
|
});
|
|
|
|
//We hide the pieces of the puzzle to avoid the cheating
|
|
$('.jPuzzle-piece').hide();
|
|
},function()
|
|
{
|
|
//The final image is displayed, we hide it!
|
|
$(this).val(parameters.textHelpButtonShow).attr('disabled',true);
|
|
|
|
image.animate(
|
|
{
|
|
'opacity': (parameters.help?0.1:0)
|
|
},'slow',function()
|
|
{
|
|
helpButton.removeAttr('disabled');
|
|
});
|
|
|
|
$('.jPuzzle-piece').show();
|
|
}).hide().appendTo(infos);
|
|
}
|
|
|
|
//And we remove the initial image!
|
|
obj.remove();
|
|
|
|
var timer;
|
|
function startGame()
|
|
{
|
|
if(parameters.onstart)
|
|
{
|
|
parameters.onstart(game);
|
|
}
|
|
|
|
box.show();
|
|
infosTime.show();
|
|
infosTime.text('00"00\'00');
|
|
|
|
if(parameters.showHelpButton)
|
|
{
|
|
helpButton.show();
|
|
}
|
|
|
|
image.animate(
|
|
{
|
|
'opacity': (parameters.help?0.1:0)
|
|
},'slow');
|
|
|
|
for(var i=0;i<parameters.rows;i++)
|
|
{
|
|
for(var j=0;j<parameters.columns;j++)
|
|
{
|
|
var maxX=width-pieceWidth;
|
|
var maxY=sizeBox-pieceHeight;
|
|
|
|
var piece=$('<span></span>').addClass('jPuzzle-piece')
|
|
.attr('data-num',i+'_'+j).css(
|
|
{
|
|
'width': pieceWidth+'px',
|
|
'height': pieceHeight+'px',
|
|
'display': 'block',
|
|
'left': Math.floor((Math.random()*(maxX+1)))+'px',
|
|
'top': Math.floor((Math.random()*(maxY+1)))+'px',
|
|
'z-index': Math.floor((Math.random()*(10))+1),
|
|
'background-image': 'url("'+src+'")',
|
|
'cursor': 'move',
|
|
'background-position': (-j*pieceWidth)+'px '+(-i*pieceHeight)+'px',
|
|
'position': 'absolute'
|
|
}).draggable(
|
|
{
|
|
'start': function(event,ui)
|
|
{
|
|
/* If the piece is already on its
|
|
good location, we stop the dragging */
|
|
if($.data(this,'ok') && parameters.fixed)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Otherwise, we put the piece in front of the others
|
|
var zIndex=0;
|
|
game.find('.jPuzzle-piece').each(function()
|
|
{
|
|
var x=parseInt($(this).css('z-index'));
|
|
zIndex=Math.max(x,zIndex);
|
|
});
|
|
$(this).css('z-index',zIndex+1);
|
|
},
|
|
'revert': 'invalid',
|
|
'containment': game
|
|
}).appendTo(box);
|
|
|
|
$.data(piece[0],'ok',false);
|
|
$.data(piece[0],'lastLocation',box);
|
|
|
|
var location=$('<span></span>').addClass('jPuzzle-location')
|
|
.attr('data-num',i+'_'+j).css(
|
|
{
|
|
'width': pieceWidth+'px',
|
|
'height': pieceHeight+'px',
|
|
'left': j*pieceWidth+'px',
|
|
'top': i*pieceHeight+'px',
|
|
'display': 'block',
|
|
'position': 'absolute'
|
|
}).droppable(
|
|
{
|
|
'accept': function(ui)
|
|
{
|
|
/* We accept only puzzle pieces, and only if the location is empty
|
|
We also check if the piece and the location are in the same puzzle game */
|
|
return (ui.hasClass('jPuzzle-piece') && $(this).is(':empty') && $(this).parents('.jPuzzle-game')[0]==ui.parents('.jPuzzle-game')[0]);
|
|
},
|
|
'addClasses': false,
|
|
'hoverClass': 'jPuzzle-location-hover',
|
|
'drop': function(event,ui)
|
|
{
|
|
var obj=$(this);
|
|
|
|
ui.draggable.appendTo(obj);
|
|
$.data(ui.draggable[0],'lastLocation',obj);
|
|
|
|
ui.draggable.css(
|
|
{
|
|
'left': '0px',
|
|
'top': '0px'
|
|
});
|
|
|
|
/* If piece and location have the same data-num attribute,
|
|
then the piece is correctly placed !*/
|
|
$.data(ui.draggable[0],'ok',false);
|
|
if(obj.attr('data-num')==ui.draggable.attr('data-num'))
|
|
{
|
|
$.data(ui.draggable[0],'ok',true);
|
|
|
|
/* If we can't remove a piece which is correctly
|
|
placed, we can apply a small effect on it*/
|
|
if(parameters.fixed)
|
|
{
|
|
/* We modify the cursor to indicate
|
|
the piece is not longer draggable */
|
|
ui.draggable.css('cursor','default');
|
|
|
|
/* This span will add a "flash
|
|
effect" on the piece */
|
|
$('<span></span>').css(
|
|
{
|
|
'position': 'absolute',
|
|
'left': '0px',
|
|
'top': '0px',
|
|
'display':'block',
|
|
'width': '100%',
|
|
'height': '100%',
|
|
'background-color': '#fff'
|
|
}).appendTo(ui.draggable)
|
|
.fadeOut(2000,function()
|
|
{
|
|
$(this).remove();
|
|
});
|
|
}
|
|
|
|
//The player has correctly placed one piece: what about the others?
|
|
var total=0;
|
|
game.find('.jPuzzle-piece').each(function()
|
|
{
|
|
if(!$.data(this,'ok'))
|
|
{
|
|
return false;
|
|
}
|
|
total++;
|
|
});
|
|
if(total==parameters.rows*parameters.columns)
|
|
{
|
|
endGame();
|
|
}
|
|
}
|
|
}
|
|
}).appendTo(puzzle);
|
|
}
|
|
}
|
|
|
|
timer=setInterval(function()
|
|
{
|
|
infosTime.attr('data-time',parseInt(infosTime.attr('data-time'),10)+1);
|
|
infosTime.text(formatTime(infosTime.attr('data-time')));
|
|
},1000);
|
|
}
|
|
|
|
function endGame()
|
|
{
|
|
//We stop the timer
|
|
clearInterval(timer);
|
|
var time=parseInt(infosTime.attr('data-time'),10);
|
|
|
|
//We set the puzzle to its initial state
|
|
box.hide();
|
|
game.find('.jPuzzle-piece,.jPuzzle-location').remove();
|
|
image.css('opacity','1');
|
|
if(parameters.showHelpButton)
|
|
{
|
|
helpButton.hide();
|
|
}
|
|
infosTime.hide().attr('data-time',0);
|
|
playButton.show();
|
|
|
|
//We eventually call a function defined by the user
|
|
if(parameters.onend)
|
|
{
|
|
//We send to this function three useful variables
|
|
parameters.onend(game,time,formatTime(time));
|
|
}
|
|
}
|
|
}
|
|
|
|
return this.each(loading);
|
|
};
|
|
})(jQuery); |