소스 검색

Initial commit

Fabrice Ecaille 12 년 전
커밋
883157e3a6

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+*~

+ 53 - 0
css/countdown.css

@@ -0,0 +1,53 @@
+#countdown	{ 
+	float: left; 
+	margin-top:12px; 
+	margin-left:12px; 
+	width:210px;
+	padding: 3px;
+	color: white;
+	background-color: black;
+	font-family: Tahoma; 
+	font-size: 50px;
+}
+
+.clock { 
+	background : transparent url("../images/hours.png") no-repeat top left; 
+	height:40px; 
+	width:30px; 
+	float:left;
+}
+
+.separator { 
+	background-position : -30px -220px;
+}
+
+.n0 { 
+	background-position : 0 -7px;
+}
+.n1 { 
+	background-position : 0 -49px;
+}
+.n2 { 
+	background-position : 0 -91px;
+}
+.n3 { 
+	background-position : 0 -133px;
+}
+.n4 { 
+	background-position : 0 -176px;
+}
+.n5 { 
+	background-position : -30px -7px;
+}
+.n6 { 
+	background-position : -30px -50px;
+}
+.n7 { 
+	background-position : -30px -91px;
+}
+.n8 { 
+	background-position : -30px -133px;
+}
+.n9 { 
+	background-position : -30px -176px;
+}

+ 54 - 0
css/scoreboard.css

@@ -0,0 +1,54 @@
+.scoreboard	{ 
+	float: left; 
+/*	margin-top:12px; 
+	margin-left:12px; 
+*/	width: 180px;
+	height: 40px;
+	padding: 3px;
+	color: white;
+	background-color: black;
+	font-family: Tahoma; 
+	font-size: 50px;
+}
+
+.clock { 
+	background : transparent url("../images/hours.png") no-repeat top left; 
+	height:40px; 
+	width:30px; 
+	float:left;
+}
+
+.separator { 
+	background-position : -30px -220px;
+}
+
+.n0 { 
+	background-position : 0 -7px;
+}
+.n1 { 
+	background-position : 0 -49px;
+}
+.n2 { 
+	background-position : 0 -91px;
+}
+.n3 { 
+	background-position : 0 -133px;
+}
+.n4 { 
+	background-position : 0 -176px;
+}
+.n5 { 
+	background-position : -30px -7px;
+}
+.n6 { 
+	background-position : -30px -50px;
+}
+.n7 { 
+	background-position : -30px -91px;
+}
+.n8 { 
+	background-position : -30px -133px;
+}
+.n9 { 
+	background-position : -30px -176px;
+}

+ 102 - 0
css/spaceinvaders.css

@@ -0,0 +1,102 @@
+#playground {
+	background-color: black;
+}
+
+.alien {
+}
+
+.shipShot {
+	background-color: green;
+}
+
+.alienShot {
+	background-color: red;
+}
+
+.weapon_bar {
+	height: 10px;
+	width: 100px;
+	position: absolute;
+	top: 5px;
+	background-color: black;
+}
+
+.weapon_level {
+	height: 8px;
+	position: relative;
+	margin: 1px;
+}
+.weapon_level.good {
+	background-color: green;
+}
+.weapon_level.middle {
+	background-color: yellow;
+}
+.weapon_level.bad {
+	background-color: red;
+}
+
+.life {
+	width: 32px;
+	height: 32px;
+	float: left;
+	background-image: url('../images/sprite.png');
+	background-position: 0px -16px;
+}
+
+/** SCOREBOARD**/
+
+.clock { 
+	background : transparent url("../images/font.png") no-repeat top left; 
+	height:32px; 
+	width:32px; 
+	float:left;
+}
+
+.clock.red {
+	background : transparent url("images/font-red.png") no-repeat top left; 
+}
+
+.clock.yellow {
+	background : transparent url("images/font-yellow.png") no-repeat top left; 
+}
+
+.clock.small {
+	position: relative;
+	top: 45%;
+	height: 16px;
+	width: 16px;
+}
+
+.n0 { 
+	background-position : 0px 0px;
+}
+.n1 { 
+	background-position : -32px 0px;
+}
+.n2 { 
+	background-position : -64px 0px;
+}
+.n3 { 
+	background-position : -96px 0px;
+}
+.n4 { 
+	background-position : -128px 0px;
+}
+.n5 { 
+	background-position : -160px 0px;
+}
+.n6 { 
+	background-position : -192px 0px;
+}
+.n7 { 
+	background-position : -224px 0px;
+}
+.n8 { 
+	background-position : -256px 0px;
+}
+.n9 { 
+	background-position : -288px 0px;
+}
+/** Scoreboard end **/
+

BIN
images/aliensprite.png


BIN
images/background.png


BIN
images/background2.png


BIN
images/farm.png


BIN
images/font.png


BIN
images/invader.png


BIN
images/ufo.png


+ 40 - 0
index.html

@@ -0,0 +1,40 @@
+<html>
+	<head>
+		<title>Space invaders - Story</title>
+		
+		<script src="lib/jquery-1.7.1.min.js" type="text/javascript" ></script>
+		<script src="lib//jquery-ui-1.8.23.custom.min.js" type="text/javascript" ></script>
+		<script src="lib/gamequery-0.7.1.js" type="text/javascript" ></script>
+		<script src="js/tools.js" type="text/javascript" ></script>
+		<script src="js/scoreboard.js" type="text/javascript" ></script>
+		<script src="js/countdown.js" type="text/javasccript" ></script>
+		<script src="js/lettering.js" type="text/javascript" ></script>
+
+		<link href="css/scoreboard.css" type="text/css" rel="stylesheet"/>
+		<link href="css/countdown.css" type="text/css" rel="stylesheet"/>
+		
+		<script src="js/spaceinvaders-ui.js" type="text/javascript" ></script>
+		<script src="js/spaceinvaders-core.js" type="text/javascript" ></script>
+		<script src="js/spaceinvaders-models.js" type="text/javascript" ></script>
+
+		<link href="css/spaceinvaders.css" type="text/css" rel="stylesheet"/>		
+	</head>
+	<body>
+
+		<div id="playgroundContainer">
+                        <div id="playground">
+		             <div id="welcomeScreen">
+ 				<div style="position: absolute; top: 120px; width: 700px; color: white;">
+	                                  <div id="loadingBar"></div>
+                                  </div>
+                            </div>
+                      </div>
+                </div>
+		<script language="text/javascript">
+			 $(document).ready(function () {
+		              //Game.init();
+		         });
+
+		</script>
+	</body>
+</html>

+ 84 - 0
js/countdown.js

@@ -0,0 +1,84 @@
+var COUNTDOWN = {
+	mustStop: false,
+	
+	init: function( minutes, seconds ) {
+		COUNTDOWN.minutes = minutes;
+		COUNTDOWN.seconds = seconds;
+		COUNTDOWN.mustStop = false;
+//		$( "#countdown" ).width( ( minutes > 0 ? 90 : 0 ) + 90);
+//		$( "#countdown" ).css( "background-color", "black" );
+	},
+
+	start: function() {
+		if( COUNTDOWN.mustStop )
+			return;
+		
+		COUNTDOWN.running = true;
+		
+		var currentMinutes = "";
+		var currentSeconds = "";
+		var imageMinutes = "";
+		var imageSeconds = "";
+	
+		currentMinutes = COUNTDOWN.minutes;
+		currentSeconds = COUNTDOWN.seconds;
+		
+		var nextMinutes = COUNTDOWN.minutes;
+		var nextSeconds = COUNTDOWN.seconds - 1;
+		
+		if( nextSeconds < 0 && nextMinutes > 0 ) {
+			nextSeconds = 59;
+			nextMinutes = Math.min(0, nextMinutes -1);
+		}
+		
+		COUNTDOWN.minutes = nextMinutes;
+		COUNTDOWN.seconds = nextSeconds;
+	
+		if( currentMinutes <= 0 && currentSeconds < 10 )
+			$( "#countdown" ).css( "background-color", "red" );
+		
+		if(parseInt(currentMinutes) < 10 ) currentMinutes = "0" + currentMinutes;
+		if(parseInt(currentSeconds) < 10 ) currentSeconds = "0" + currentSeconds;
+		
+		for(i = 0; i < String(currentMinutes).length; i++) {
+			imageMinutes += "<div class='clock n"+ String(currentMinutes)[i]+"'></div>";
+		}
+		
+		for(i = 0; i < String(currentSeconds).length; i++) {
+			imageSeconds += "<div class='clock n"+ String(currentSeconds)[i]+"'></div>";
+		}
+	
+		if( COUNTDOWN.minutes > 0) {
+			$("#subMinutes").empty().removeClass( "hide" ).append( imageMinutes );;
+			$(".clock.clock.separator").removeClass( "hide" );
+		} else {
+			$("#subMinutes").empty().addClass( "hide" );
+			$(".clock.clock.separator").addClass( "hide" );
+		}
+	
+		$("#subSeconds").empty().append( imageSeconds );
+		
+		if( nextMinutes >= 0 && nextSeconds >= 0 )
+			setTimeout( "COUNTDOWN.start()", 1000 );
+		else
+			COUNTDOWN.callback();
+	},
+
+	add: function(seconds) {
+		console.log( "Adding " + seconds + " seconds to countdown" );
+		COUNTDOWN.seconds = COUNTDOWN.seconds + seconds;
+	},
+
+	setTime: function( seconds ) {
+		console.log( "Setting " + seconds + " seconds to countdown" );
+		COUNTDOWN.seconds = seconds;
+	},
+	
+	stop: function() {
+		COUNTDOWN.mustStop = true;
+	},
+	
+	callback: function() {
+		console.log( "COUNTDOWN.callback" );
+	}
+};

+ 71 - 0
js/lettering.js

@@ -0,0 +1,71 @@
+(function($) {
+	function injector(t, splitter, klass, after) {
+		var a = t.text().split(splitter);
+		var html = "",
+		clazz = "clock",
+		letterSize = 32,
+		count = 0,
+		width = 0,
+		height = 0,
+		lineSize,
+		letter, iLetter,
+		i, x, y
+		forceSmall = false;
+		if( typeof customClazz != "undefined" ) {
+			clazz = " clock " + customClazz;
+		}
+		if (a.length) {
+			$(a).each(
+				function(i, letter) {
+					iLetter = (letter.charCodeAt(0) - 97);
+					if( letter === " " ) {
+						html += "<span class='blank'></span>";
+						width += 16;
+						count = count + 1;
+					} else {
+						if( letter.charCodeAt(0) > 47 && letter.charCodeAt(0) < 58 ) {
+							letterSize = 32;
+							if( forceSmall ) {
+								letterSize = 16;
+							}
+							html += "<span class='" + clazz + (forceSmall ? "small" : "") + "' style='top: -50%;background-position: -" + ( parseInt( letter, null ) * letterSize) + "px -" + (forceSmall > -1 ? 128 : 0) +"px'></span>";
+							count = count + 1;
+						} else {
+							if( ( letter.charCodeAt(0) >= 'a'.charCodeAt(0) && letter.charCodeAt(0) <= 'z'.charCodeAt(0)) ) {
+								if( height < 16 ) {
+									height = 16;
+								}
+								width += 16;
+								lineSize = 20;
+								x = (iLetter % lineSize) * 16;
+								y = Math.floor(iLetter / lineSize) * 16 + 144;
+								html += "<span class='" + clazz + " small' style='background-position: -" + x + "px -" + y + "px'></span>";
+								count = count + 1;
+							} else {
+								if( letter.charCodeAt(0) >= 'A'.charCodeAt(0) && letter.charCodeAt(0) <= 'Z'.charCodeAt(0)) {
+									iLetter = letter.charCodeAt(0) - 'A'.charCodeAt(0);
+									if( height < 32 ) {
+										height = 32;
+									}
+									width += 32;
+									lineSize = 10;
+									x = (iLetter % lineSize) * 32;
+									y = Math.floor(iLetter / lineSize) * 32 + 32;
+									html += "<span class='" + clazz + "' style='background-position: -" + x + "px -" + y + "px'></span>";
+									count = count + 1;
+								}
+							}
+						}
+					}
+				});
+			t.empty().append(html);
+		}
+	}
+	
+	$.fn.lettering = function() {
+		return injector( $(this), '', 'char', '' ); // always
+																			// pass
+																			// an
+																			// array
+	};
+})(jQuery);

+ 44 - 0
js/scoreboard.js

@@ -0,0 +1,44 @@
+var SCOREBOARD = {
+	score: 0,
+	scoreLength: 6,
+	
+	init: function(size) {
+		if( typeof size !== "undefined" )
+			SCOREBOARD.scoreLength = size;
+		SCOREBOARD.score = 0;
+		SCOREBOARD.set_score( 0 );
+	},
+	
+	add: function(addToScore, div) {
+		SCOREBOARD.set_score( SCOREBOARD.score + addToScore, div);
+	},
+	
+	set_score: function( score, div ) {
+		var currentScore = "";
+		var imageScore = "";
+		
+		SCOREBOARD.score = score;
+		currentScore = SCOREBOARD.pad();
+
+		for(i = 0; i < String(currentScore).length; i++) {
+			imageScore += "<div class='clock n"+ String(currentScore)[i]+"'></div>";
+		}
+		
+		if( typeof div === "undefined" )
+			div = $(".subScoreboard"); 
+		div.empty();
+		div.append( imageScore );
+	},
+	
+	pad: function() {
+	    var str = '' + SCOREBOARD.score;
+	    while (str.length < SCOREBOARD.scoreLength) {
+	        str = '0' + str;
+	    }
+	    return str;
+	},
+	
+	callback: function() {
+		console.log( "SCOREBOARD.callback" );
+	}
+};

+ 233 - 0
js/spaceinvaders-core.js

@@ -0,0 +1,233 @@
+var WAVES = [
+		{
+			wave : [ [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
+					[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
+					[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
+					[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
+					[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ],
+			move : function() {
+				var offset = (PLAYGROUND_WIDTH - 16 * this.width) / 2;
+				if (Math.abs((this.getOriginX() - this.getX())) >= offset) {
+					this.directionX *= -1;
+					this.y = (this.y + this.height / 4);
+				}
+			}
+		},
+		{
+			wave : [ [ 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 0, 0 ],
+					[ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
+					[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
+					[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ],
+			move : function() {
+				var offset = (PLAYGROUND_WIDTH - 16 * this.width) / 2;
+				if (Math.abs((this.getOriginX() - this.getX())) >= offset) {
+					this.directionX *= -1;
+					this.y = (this.y + this.height / 4);
+				}
+			}
+		} ];
+
+Game = {
+	running : false,
+	wave : -1,
+	aliens : [],
+	ship : null,
+	score : 0,
+	
+	init : function() {
+		"use strict";
+		Game.wave = Game.wave + 1;
+		var row, col;
+		var wave = WAVES[Game.wave].wave;
+		for (row = 0; row < wave.length; row = row + 1) {
+			var aliensRow = wave[row], type = aliensRow[0], offset = (PLAYGROUND_WIDTH - ((aliensRow.length - 1) * 0.5 + aliensRow.length)
+					* ALIENS_WIDTH) / 2;
+			for (col = 0; col < aliensRow.length; col = col + 1) {
+				Game.setAlien(col, row, offset, type, WAVES[Game.wave].move);
+			}
+		}
+
+		SCOREBOARD.init();
+		SCOREBOARD.set_score( Game.score );
+		
+		Game.setShip();
+		
+		Game.running = true;
+	},
+
+	levelComplete : function() {
+		"use strict";
+		Game.running = false;
+
+		setTimeout(function() {
+			Game.init();
+		}, 3000);
+	},
+	
+	hit : function() {
+		"use strict";
+		if( !Game.running ) {
+			return false;
+		}
+		console.log( "Game.hit" );
+		var health = Game.ship.hit();
+		$(".alienShot").remove();
+		$(".shipShot").remove();
+		$("#life" + Game.ship.lives).remove();
+		Game.running = false;
+		Game.ship.lives = Game.ship.lives - 1;
+		$("#hero").children().hide();
+		
+		if( Game.ship.lives > 0 ) {
+			var _this = Game.ship;
+			setTimeout(function() {
+				Game.running = true;
+				$("#hero").children().show();
+				_this.health = 1;
+			}, 2000);
+		}
+		else {
+			GUI.drawText( $("#message"), game_over, true );
+			Game.show_game_over();
+		}
+	},
+	
+	setAlien : function(x, y, offset, type, move) {
+		"use strict";
+		var id = x * ROWS + y;
+		var alien = new Alien("alien" + id, {
+			x : offset + x * ALIENS_WIDTH * 1.5,
+			y : START_Y + (y * 1.25 * ALIENS_HEIGHT)
+		}, move);
+		$("#actors").addSprite("alien" + id, $.extend({posx : alien.x, posy : alien.y}, animations.alien));
+		alien.node = $("#alien" + id);
+		alien.node.addClass("alien");
+		$("#alien" + id)[0].alien = alien;
+		Game.aliens.push(alien);
+	},
+
+	setShip : function() {
+		var type = SHIPS.scout;
+		Game.ship = new Ship("ship", {
+			x : $("#hero").x(),
+			y : $("#hero").y()
+		}, 3, animations.hero.ship.animation);
+		var hero = $("#hero");
+		$.each(animations.hero, 
+			function(id, obj){
+				hero.addSprite(id, obj);
+			});
+		Game.ship.node = $("#hero");
+	},
+	
+	addToScore : function( toAdd ) {
+		Game.score = Game.score + toAdd;
+		SCOREBOARD.add( toAdd );
+	},
+	
+	control : function() {
+		if( !Game.running ) {
+			return false;		
+		}
+
+		$(document).keyup(function(e){
+			switch(e.keyCode) {
+				case 37:
+					e.preventDefault();
+					Game.ship.left(false);
+					break;	
+				case 39:
+					e.preventDefault();
+					Game.ship.right(false);
+					break;
+			} 
+		 });
+		 $(document).keydown(function(e){
+			switch(e.keyCode) {
+				case 37:
+					e.preventDefault();
+					Game.ship.left(true);
+					break;
+				case 39:
+					e.preventDefault();
+					Game.ship.right(true);
+					break;
+				case 32:
+					e.preventDefault();
+					Game.ship.fire($("#shipShots"), "shipShot");
+					return false;
+				} 
+			 });
+	},
+	
+	alienControl : function() {
+		if( !Game.running ) {
+			return false;		
+		}
+
+		$.each(Game.aliens, function(index, alien ) {
+			alien.move();
+			if( alien.health > 0 && Math.random() < alien.aggression ) {
+				alien.fire($("#aliensShots"), "alienShot");
+			}
+		});
+	},
+	
+	heroShotCollision : function() {
+		if( !Game.running ) {
+			return false;		
+		}
+
+		$(".shipShot").each(function(i,e) {
+			var posy = $(this).y();
+			if( posy < -$(this).height() ) {
+				this.remove();
+				return;
+			}
+			var weapon = $(this)[0].weapon;
+			$(this).y(weapon.directionY * weapon.speed, true);
+			$(this).x(weapon.directionX * weapon.speed, true);
+			var collisions = $(this).collision(".alien,."+$.gQ.groupCssClass);
+			collisions.each( function() {
+				var alien = $(this)[0];
+				var aliensNotInArray = $.grep( Game.aliens, function( elementOfArray, index) {
+					return elementOfArray.id == alien.id;
+				}, true);
+				Game.aliens = aliensNotInArray;
+				$(this)[0].alien.hit();
+			})
+			if( collisions.length > 0 ) {
+				this.remove()
+			}
+			
+			if( Game.aliens.length == 0 ) {
+				Game.running = false;
+				Game.levelComplete();
+			}
+		});
+	},
+	
+	alienShotCollision : function() {
+		if( !Game.running ) {
+			return false;		
+		}
+
+		$(".alienShot").each(function(i,e) {
+			var posy = $(this).y();
+			if( posy > PLAYGROUND_HEIGHT ) {
+				this.remove();
+				return;
+			}
+			var weapon = $(this)[0].weapon;
+			$(this).y(weapon.directionY * weapon.speed, true);
+			$(this).x(weapon.directionX * weapon.speed, true);
+			var collisions = $(this).collision("#ship,."+$.gQ.groupCssClass);
+			collisions.each( function() {
+				Game.hit();
+			})
+			if( collisions.length > 0 ) {
+				this.remove()
+			}
+		});
+	}
+};

+ 312 - 0
js/spaceinvaders-models.js

@@ -0,0 +1,312 @@
+/*** Weapons ***/
+
+function Weapon() {
+	"use strict";
+}
+Weapon.prototype = {
+	speed : 5,
+	strength : 10,
+	rof : 300,
+	ror : 1500,
+	load : 3,
+	max_load : 3,
+	width : 5,
+	height : 20,
+	shot_timer : false,
+	reload_timer : false,
+	directionX : 0,
+	directionY : 1,
+	animation : null,
+
+	fire : function() {
+		if (this.shot_timer || this.load <= 0) {
+			return false;
+		}
+		
+		var _this = this;
+		this.load = Math.max(0,this.load - 1);
+		this.shot_timer = setInterval(function() {
+			if (_this.load > 0) {
+				clearTimeout(_this.shot_timer);
+				_this.shot_timer = false;
+			}
+		}, this.rof);
+		
+		if( !this.reload_timer) {
+			this.reload_timer = setInterval( function() {
+				_this.load = Math.min(_this.load + 1, _this.max_load);
+				if( _this.load == _this.max_load ) {
+					clearInterval(_this.reload_timer);
+					_this.reload_timer = false;
+				}
+			}, this.ror);
+		}
+		return true;
+	}
+
+}
+
+function HeroWeapon() {
+	"use strict";
+	this.directionY = -1;
+}
+HeroWeapon.prototype = {
+		
+}
+heriter(HeroWeapon.prototype, Weapon.prototype);
+
+function AlienWeapon() {
+	"use strict";
+	this.directionY = 1;
+	this.width = 5;
+	this.height = 10;
+}
+AlienWeapon.prototype = {
+		
+}
+heriter(AlienWeapon.prototype, Weapon.prototype);
+/*** Weapons -END ***/
+
+
+/*** Actors ***/
+function Actor() {
+	"use strict";
+}
+Actor.prototype = {
+	id : null,
+	node : null,
+	x : null,
+	y : null,
+	originX : 0,
+	originY : 0,
+	speed : null,
+	health : 1,
+	directionX : 0,
+	directionY : 0,
+	fireDirectionX : 0,
+	fireDirectionY : 0,
+	weapon : null,
+	animations : {},
+
+	getX : function() {
+		"use strict";
+		return this.x;
+	},
+
+	getY : function() {
+		"use strict";
+		return this.y;
+	},
+
+	move : function() {
+		"use strict";
+		if (!Game.running) {
+			return;
+		}
+		this.x += this.directionX * this.speed;
+		this.y += this.directionY * this.speed;
+		this.x = Math.min(this.x, PLAYGROUND_WIDTH - this.node.w());
+		this.x = Math.max(this.x, 0);
+		this.node.x(this.x);
+		this.node.y(this.y);
+	},
+
+	getOriginX : function() {
+		return this.originX;
+	},
+	
+	getOriginY : function() {
+		return this.originY;
+	},
+
+	up : function(active) {
+		"use strict";
+		this.directionY = active ? -1 : 0;
+	},
+
+	down : function(active) {
+		"use strict";
+		this.directionY = active ? 1 : 0;
+	},
+
+	left : function(active) {
+		"use strict";
+		this.directionX = active ? -1 : 0;
+	},
+
+	right : function(active) {
+		"use strict";
+		this.directionX = active ? 1 : 0;
+	},
+
+	fire : function(layout, clazz) {
+		var name = "shot" + Math.ceil(Math.random() * 1000);
+		if (this.weapon.fire(layout)) {
+			layout.addSprite(name, $.extend({ posx : this.x + this.node.w() / 2, posy : this.y + this.fireDirectionY * this.node.h()}, this.weapon));
+			$("#" + name).addClass(clazz)
+			$("#" + name)[0].weapon = this.weapon;
+		}
+	},
+	
+	hit : function() {
+		this.health = this.health - 1;
+		if( this.health == 0 ) {
+			this.destroy();
+		}
+		
+		return this.health;
+	},
+
+	destroy : function() {
+		$("#" + this.id).remove();
+	}
+};
+
+/*** Actors - Aliens ***/
+function Alien(id, start, move) {
+	"use strict";
+
+	this.id = id;
+	this.x = start.x;
+	this.y = start.y;
+	this.moveFct = move;
+
+	this.weapon = new AlienWeapon();
+	this.fireDirectionY = 1;
+
+	this.originX = this.x;
+	this.originY = this.y;
+	this.directionX = -1;
+	this.speed = 0.5;
+}
+
+Alien.prototype = {
+	speed : 0,
+	directionX : 0,
+	directionY : 0,
+	moveFct : null,
+	width : ALIENS_WIDTH,
+	height : ALIENS_HEIGHT,
+	aggression : 0.0005,
+
+	init : function() {
+		"use strict";
+		this.speed = 0;
+		this.node.x(this.x);
+		this.node.y(this.y);
+	},
+
+	move : function() {
+		"use strict";
+		this._super("move", arguments);
+		if (typeof this.moveFct !== undefined) {
+			this.moveFct();
+		}
+	},
+	
+	destroy : function() {
+		this._super("destroy", arguments);
+		Game.addToScore( 5 );
+	}
+};
+
+heriter(Alien.prototype, Actor.prototype);
+
+/*** Actors - Aliens - END ***/
+
+/*** Actors - Heroes - END ***/
+function Ship(id, start, speed, animation) {
+	"use strict";
+	this.id = id;
+	this.x = start.x;
+	this.y = start.y;
+
+	this.weapon = new HeroWeapon();
+	this.fireDirectionY = -1;
+
+	this.originX = this.x;
+	this.originY = this.y;
+	this.speed = speed;
+	
+	this.animation = animation;
+	
+	/*this.bigWheel = $("#bigWheel");
+	this.smallWheel = $("#smallWheel");
+	var bigWheelRadius = bigWheel.h() / 2,
+		smallWheelRadius = smallWheel.h() / 2;
+	this.bigWheelAngle = this.speed * 360 / ( 2 * Math.PI * bigWheelRadius );
+	this.smallWheelAngle = this.speed * 360 / ( 2 * Math.PI * smallWheelRadius );*/
+
+}
+
+Ship.prototype = {
+	speed : 0,
+	directionX : 0,
+	directionY : 0,
+	lives : 3,
+	animation : null,
+	/*bigWheel : null,
+	bigWheelAngle : 0,
+	smallWheel : null,
+	smallWheelAngle : 0,*/
+	
+	init : function() {
+		"use strict";
+		this.speed = 0;
+		this.node.x(this.x);
+		this.node.y(this.y);
+	},
+
+	/**
+	 * Arc = (2* Pi * R) / (360) * alpha
+	 * 
+	 */
+	move : function() {
+		"use strict";
+		this._super("move", arguments);
+	},
+
+/*	right : function(active) {
+		if( this.x + this.node.w() > PLAYGROUND_WIDTH ){
+			return false;
+		}
+		this._super("right", arguments);
+		
+		this.bigWheel.rotate(this.bigWheelAngle, true);
+		this.smallWheel.rotate(this.smallWheelAngle, true);
+	},
+	
+	left : function(active) {
+		if( this.x < 0 ){
+			return false;
+		}
+		this._super("left", arguments);
+		
+		this.bigWheel.rotate(this.bigWheelAngle, true);
+		this.smallWheel.rotate(this.smallWheelAngle, true);
+	},
+*/	
+	up : function(active) {
+		if (this.y < (PLAYGROUND_HEIGHT - 2 * HUD_HEIGHT)) {
+			return false;
+		}
+		this._super("up", arguments);
+	},
+
+	destroy : function() {
+		$("#life" + this.lives).remove();
+		this.lives = this.lives - 1;
+		$("#hero").children().hide();
+		var _this = this;
+		setTimeout(function() {
+			$("#hero").children().show();
+			_this.health = 1;
+		}, 2000);
+	}
+
+};
+
+heriter(Ship.prototype, Actor.prototype);
+/*** Actors - Heroes - END ***/
+
+/*** Actors - END ***/

+ 275 - 0
js/spaceinvaders-ui.js

@@ -0,0 +1,275 @@
+/*global jQuery, console */
+
+var PLAYGROUND_WIDTH = 448,
+	PLAYGROUND_HEIGHT = 544,
+	REFRESH_RATE = 15,
+	HUD_HEIGHT = 70,
+
+	ROWS = 5,
+	ALIENS = 11,
+
+	ALIENS_WIDTH = 24,
+	ALIENS_HEIGHT = 17,
+
+	START_Y = 40,
+	IMAGES_PREFIX = "images/";
+
+var SHIPS = {
+  	scout: {width: 30, height: 25},
+  	bomber: {width: 30, height: 25},
+  	cruser: {width: 30, height: 25}
+};
+
+var animations = {
+		hero : {
+			ship : {
+				animation : new $.gameQuery.Animation({
+					imageURL : IMAGES_PREFIX + "farm.png"
+				}),
+				width : 48,
+				height : 24,
+				posx : 0,
+				posy : 17
+			},
+			/*cockpit : {
+				animation : new $.gameQuery.Animation({
+					imageURL : IMAGES_PREFIX + "farm.png",
+					offsety : 24,
+					offsetx : 20
+				}),
+				width : 20,
+				height : 33,
+				posx : 28,
+				posy : 3
+			},*/
+			smallWheel : {
+				animation  : new $.gameQuery.Animation({
+					imageURL : IMAGES_PREFIX + "farm.png",
+					offsetx : 0,
+					offsety : 24
+				}),
+				posx : 4,
+				posy : 30,
+				width: 14,
+				height: 14
+			},
+			bigWheel : {
+				animation  : new $.gameQuery.Animation({
+					imageURL : IMAGES_PREFIX + "farm.png",
+					offsetx : 0,
+					offsety : 38
+				}),
+				width : 19,
+				height : 19,
+				posx : 25,
+				posy : 27
+			}
+		},
+		alien : {
+			animation : new $.gQ.Animation({
+				imageURL : IMAGES_PREFIX + "invader.png",
+				numberOfFrame : 2,
+				delta : ALIENS_WIDTH,
+				rate : 400,
+				type : $.gQ.ANIMATION_HORIZONTAL
+			}),
+			width : ALIENS_WIDTH,
+			height : ALIENS_HEIGHT			
+		},
+		ufo : {
+			animation: new $.gQ.Animation({imageURL: IMAGES_PREFIX + "ufo.png"}), 
+			width: 48, 
+			height: 32,
+			posy: 10
+		},
+		life : {
+			animation : new $.gameQuery.Animation({
+				imageURL : IMAGES_PREFIX + "farm.png",
+				offsetx : 14,
+				offsety : 24
+			}),
+			width: 32,
+			height: 16
+		},
+		weapons : {
+			carot : {
+				animation : new $.gameQuery.Animation({
+					imageURL : IMAGES_PREFIX + "farm.png",
+					offsetx : 19,
+					offsety : 37
+				}),
+				width: 19,
+				height: 19
+			},
+			corn : {
+				animation : new $.gameQuery.Animation({
+					imageURL : IMAGES_PREFIX + "farm.png",
+					offsetx : 38,
+					offsety : 37
+				}),
+				width: 19,
+				height: 19
+			},
+			gun : {
+				animation : new $.gameQuery.Animation({
+					imageURL : IMAGES_PREFIX + "farm.png",
+					offsetx : 57,
+					offsety : 37
+				}),
+				width: 19,
+				height: 19
+			}
+		},
+		backgrounds : {
+			farm: {
+				background1 : {
+					animation : new $.gQ.Animation({
+						imageURL : IMAGES_PREFIX + "background.png"
+					})
+				},
+				background2 : {
+					animation : new $.gQ.Animation({
+						imageURL : IMAGES_PREFIX + "background2.png"
+					})
+				}
+			}
+		}
+};
+
+function updateWeaponLoad( load ) {
+	"use strict";
+	var HTMLDiv = $("#weapon_load");
+	HTMLDiv.removeClass().addClass("weapon_level");
+	if( load > 2/3) {
+		HTMLDiv.addClass("good");
+	} else {
+		if( load > 1/3) {
+			HTMLDiv.addClass("middle");
+		} else {
+			HTMLDiv.addClass("bad");
+		}
+	}
+	HTMLDiv.width( (load * 100) + "%" );
+}
+
+$(function(){
+	"use strict";
+	
+	//Playground Sprites
+	$("#playground").playground({height: PLAYGROUND_HEIGHT, width: PLAYGROUND_WIDTH, keyTracker: true});
+	
+	$.playground({refreshRate: 60})
+		.addGroup("background", {posx: 0, posy: HUD_HEIGHT, width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT - HUD_HEIGHT})
+			.addSprite( "background1", {animation: animations.backgrounds.farm.background1.animation, width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT - HUD_HEIGHT})
+			.addSprite( "background2", {animation: animations.backgrounds.farm.background2.animation, width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT - HUD_HEIGHT})
+			.addSprite("ground", {posx: 0, posy : PLAYGROUND_HEIGHT - HUD_HEIGHT - 20, width: PLAYGROUND_WIDTH, height: 20})
+		.end()
+		.addGroup("actors", {posx: 0, posy: HUD_HEIGHT, width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT - HUD_HEIGHT})
+			.addGroup("hero", {
+				width: 48,
+				height: 60,
+				posx: (PLAYGROUND_WIDTH - 48) / 2,
+				posy: PLAYGROUND_HEIGHT - HUD_HEIGHT - 50
+			})
+			.end()
+		.end()
+		.addGroup( "shipShots", {posx: 0, posy: HUD_HEIGHT, width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT - HUD_HEIGHT})
+		.end()
+		.addGroup( "aliensShots", {posx: 0, posy: HUD_HEIGHT, width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT - HUD_HEIGHT})
+		.end()
+		.addGroup( "hud", {width: PLAYGROUND_WIDTH, height: HUD_HEIGHT, posx: 0, posy: 0})
+			.addSprite( "current_weapon", $.extend({posx: 10, posy: 40}, animations.weapons.gun))
+			.addGroup("weapon_bar", {
+				posx : 50,
+				posy : 40,
+				width : 100,
+				height : 10
+			})
+				.addSprite("weapon_load", {
+					width : 100,
+					height : 10
+				})
+			.end()
+			.addGroup("scoreboard", {
+				posx: 10,
+				posy: 2,
+				width: 6 * 32,
+				height: 32
+			})
+				.addSprite( "subScoreboard", {
+					width: 6 * 32,
+					height: 32
+				})
+			.end()
+			.addGroup("lives", {
+				posx: PLAYGROUND_WIDTH - 100,
+				posy: 32,
+				width: 100,
+				height: 32
+			})
+				.addSprite("life3", animations.life)
+				.addSprite("life2", $.extend({posx: 32}, animations.life))
+				.addSprite("life1", $.extend({posx: 64}, animations.life))
+			.end()
+			.addGroup("levelGrp", {
+				posx: PLAYGROUND_WIDTH - 6 * 32 + 16,
+				posy: 2,
+				width: 32 + 5 * 16,
+				height: 16
+				})
+				.addSprite("levelLbl", {
+					width: 32 + 5 * 16
+				})
+				.addSprite("level", {
+					width : 64,
+					posx: 32 + 7 * 16,
+					posy: 16,
+					height: 16
+				})
+			.end()
+			.addSprite( "carot", $.extend({posx: 210, posy: 40}, animations.weapons.carot))
+			.addSprite( "corn", $.extend({posx: 230, posy: 40}, animations.weapons.corn))
+		.end()
+		;
+
+	$("#ground").css("background-color", "brown");
+	$("#levelLbl").append("Level").lettering();
+	$("#level").append("0").lettering();
+	$("#scoreboard").addClass("scoreboard");
+	$("#subScoreboard").addClass("subScoreboard");
+	var hud = $("#hud");
+
+	/*  
+	hud.append("<div id='scoreMessage'></div>");
+	hud.append("<div id='message'></div>'");
+	hud.append("<div id='level'></div>'");
+	hud.append("<div id='levelNumber'></div>'");*/
+
+	// Controls
+	$.playground().registerCallback(Game.control, REFRESH_RATE);
+	$.playground().registerCallback(Game.alienControl, REFRESH_RATE);
+	
+	// Collisions management
+	$.playground().registerCallback(Game.heroShotCollision, REFRESH_RATE);
+	$.playground().registerCallback(Game.alienShotCollision, REFRESH_RATE);
+	
+	// Refresh playground
+	$.playground().registerCallback(function(){
+		updateWeaponLoad(Game.ship.weapon.load / Game.ship.weapon.max_load);
+		if( Game.running ) {
+			Game.ship.move();
+		}
+	}, REFRESH_RATE);
+});
+
+$(document).ready(function () {
+	Game.init();
+	$.loadCallback(function(percent){
+        	$("#loadingBar").width(400*percent);
+	});
+	$.playground().startGame(
+		function() {
+			$("#welcomeScreen").remove();
+		}
+	);
+});

+ 24 - 0
js/tools.js

@@ -0,0 +1,24 @@
+function heriter(destination, source) { 
+    function initClassIfNecessary(obj) { 
+        if( typeof obj["_super"] == "undefined" ) { 
+            obj["_super"] = function() { 
+                var methodName = arguments[0]; 
+                var parameters = arguments[1]; 
+                this["__parent_methods"][methodName].apply(this, parameters); 
+            } 
+        } 
+     
+        if( typeof obj["__parent_methods"] == "undefined" ) { 
+            obj["__parent_methods"] = {};
+        } 
+    } 
+ 
+    for (var element in source) { 
+        if( typeof destination[element] != "undefined" ) { 
+            initClassIfNecessary(destination); 
+            destination["__parent_methods"][element] = source[element]; 
+        } else { 
+            destination[element] = source[element]; 
+        } 
+    } 
+}

+ 1849 - 0
lib/gamequery-0.7.1.js

@@ -0,0 +1,1849 @@
+/*
+ * gameQuery rev. 0.7.1
+ *
+ * Copyright (c) 2012 Selim Arsever (http://gamequeryjs.com)
+ * licensed under the MIT-License
+ */
+
+// This allows use of the convenient $ notation in a plugin
+(function($) {
+	
+	// CSS Feature detection from: Craig Buckler (http://www.sitepoint.com/detect-css3-property-browser-support/)
+	var cssTransform = false;
+	
+	var detectElement = document.createElement("detect"),  
+    	CSSprefix = "Webkit,Moz,O,ms,Khtml".split(","),
+    	All = ("transform," + CSSprefix.join("Transform,") + "Transform").split(",");  
+	for (var i = 0, l = All.length; i < l; i++) {  
+	    if (detectElement.style[All[i]] === "") {  
+	          cssTransform = All[i];
+	    }  
+	}
+	
+    // This prefix can be use whenever needed to namespace CSS classes, .data() inputs aso.
+    var gQprefix = "gQ_";
+    
+    // Those are the possible states of the engine
+    var STATE_NEW     = 0; // Not yet started for the first time
+    var STATE_RUNNING = 1; // Started and running 
+    var STATE_PAUSED  = 2; // Paused
+    
+    /**
+     * Utility function that returns the radius for a geometry.
+     *
+     * @param {object} elem DOM element
+     * @param {float} angle the angle in degrees
+     * @return {object} .x, .y radius of geometry
+     */
+    var proj = function (elem, angle) {
+        switch (elem.geometry){
+            case $.gameQuery.GEOMETRY_RECTANGLE :
+                var b = angle*Math.PI*2/360;
+                var Rx = Math.abs(Math.cos(b)*elem.width/2*elem.factor)+Math.abs(Math.sin(b)*elem.height/2*elem.factor);
+                var Ry = Math.abs(Math.cos(b)*elem.height/2*elem.factor)+Math.abs(Math.sin(b)*elem.width/2*elem.factor);
+
+                return {x: Rx, y: Ry};
+        }
+    };
+    
+    /**
+     * Utility function that checks for collision between two elements.
+     *
+     * @param {object} elem1 DOM for the first element
+     * @param {float} offset1 offset of the first element
+     * @param {object} elem2 DOM for the second element
+     * @param {float} offset2 offset of the second element
+     * @return {boolean} if the two elements collide or not
+     */
+    var collide = function(elem1, offset1, elem2, offset2) {
+        // test real collision (only for two rectangles...)
+        if((elem1.geometry == $.gameQuery.GEOMETRY_RECTANGLE && elem2.geometry == $.gameQuery.GEOMETRY_RECTANGLE)){
+
+            var dx = offset2.x + elem2.boundingCircle.x - elem1.boundingCircle.x - offset1.x;
+            var dy = offset2.y + elem2.boundingCircle.y - elem1.boundingCircle.y - offset1.y;
+            var a  = Math.atan(dy/dx);
+
+            var Dx = Math.abs(Math.cos(a-elem1.angle*Math.PI*2/360)/Math.cos(a)*dx);
+            var Dy = Math.abs(Math.sin(a-elem1.angle*Math.PI*2/360)/Math.sin(a)*dy);
+
+            var R = proj(elem2, elem2.angle-elem1.angle);
+
+            if((elem1.width/2*elem1.factor+R.x <= Dx) || (elem1.height/2*elem1.factor+R.y <= Dy)) {
+                return false;
+            } else {
+                var Dx = Math.abs(Math.cos(a-elem2.angle*Math.PI*2/360)/Math.cos(a)*-dx);
+                var Dy = Math.abs(Math.sin(a-elem2.angle*Math.PI*2/360)/Math.sin(a)*-dy);
+
+                var R = proj(elem1, elem1.angle-elem2.angle);
+
+                if((elem2.width/2*elem2.factor+R.x <= Dx) || (elem2.height/2*elem2.factor+R.y <= Dy)) {
+                    return false;
+                } else {
+                    return true;
+                }
+            }
+        } else {
+            return false;
+        }
+    };
+    
+    /** 
+     * Utility function computes the offset relative to the playground of a gameQuery element without using DOM's position.
+     * This should be faster than the standand .offset() function.
+     * 
+     * Warning: No non-gameQuery elements should be present between this element and the playground div!
+     * 
+     * @param {jQuery} element the jQuery wrapped DOM element representing the gameQuery object.
+     * @return {object} an object {x:, y: } containing the x and y offset. (Not top and left like jQuery's .offset())  
+     */
+    var offset = function(element) {
+        // Get the tileSet offset (relative to the playground)
+        var offset = {x: 0, y: 0};
+        var parent = element[0];
+        
+        while(parent !== $.gameQuery.playground[0] && parent.gameQuery !== undefined) {
+            offset.x += parent.gameQuery.posx;
+            offset.y += parent.gameQuery.posy;
+            parent = parent.parentNode;
+        }
+        
+        return offset
+    }
+    
+    /**
+     * Utility function computes the index range of the tiles for a tilemap.
+     * 
+     * @param {jQuery} element the jQuery wrapped DOM element representing the tilemap.
+     * @param {object} offset an object holding the x and y offset of the tilemap, this is optional and will be computed if not provided.
+     * @return {object} an object {firstColumn: , lastColumn: , fristRow: , lastRow: } 
+     */
+    var visibleTilemapIndexes = function (element, elementOffset) {
+        if (elementOffset === undefined) {
+            elementOffset = offset(element);   
+        }
+        
+        var gameQuery = element[0].gameQuery;
+        // Activate the visible tiles
+        return {
+            firstRow:    Math.max(Math.min(Math.floor(-elementOffset.y/gameQuery.height), gameQuery.sizey), 0),
+            lastRow:     Math.max(Math.min(Math.ceil(($.gameQuery.playground[0].height-elementOffset.y)/gameQuery.height), gameQuery.sizey), 0),
+            firstColumn: Math.max(Math.min(Math.floor(-elementOffset.x/gameQuery.width), gameQuery.sizex), 0),
+            lastColumn:  Math.max(Math.min(Math.ceil(($.gameQuery.playground[0].width-elementOffset.x)/gameQuery.width), gameQuery.sizex), 0) 
+        }
+    }
+    
+    /**
+     * Utility function thast computes the buffered zone of a tilemap
+     * 
+     * @param {jQuery} element the jQuery wrapped DOM element representing the tilemap.
+     * @param {object} visible an object describing the visible zone
+     * @return {object} an object {firstColumn: , lastColumn: , fristRow: , lastRow: }
+     */
+    var bufferedTilemapIndexes = function (element, visible) {
+        var gameQuery = element[0].gameQuery;
+        
+        return {
+            firstRow:    Math.max(Math.min(visible.firstRow - gameQuery.buffer, gameQuery.sizey), 0),
+            lastRow:     Math.max(Math.min(visible.lastRow + gameQuery.buffer, gameQuery.sizey), 0),
+            firstColumn: Math.max(Math.min(visible.firstColumn - gameQuery.buffer, gameQuery.sizex), 0),
+            lastColumn:  Math.max(Math.min(visible.lastColumn + gameQuery.buffer, gameQuery.sizex), 0) 
+        }
+    }
+    
+    /**
+     * Utility function that creates a tile in the given tilemap
+     * 
+     * @param {jQuery} tileSet the jQuery element representing the tile map
+     * @param {integer} row the row index of the tile in the tile map
+     * @param {integer} column the column index of the tile in the tile map
+     */
+    var addTile = function(tileSet, row, column) {
+        var gameQuery = tileSet[0].gameQuery;
+        var name = tileSet.attr("id");
+        
+        var tileDescription;
+        if(gameQuery.func) {
+            tileDescription = gameQuery.tiles(row,column)-1;
+        } else {
+            tileDescription = gameQuery.tiles[row][column]-1;
+        }
+        
+        var animation;
+        if(gameQuery.multi) {
+            animation = gameQuery.animations;
+        } else {
+            animation = gameQuery.animations[tileDescription];
+        }
+        
+        if(tileDescription >= 0){
+            tileSet.addSprite($.gameQuery.tileIdPrefix+name+"_"+row+"_"+column,
+                                  {width: gameQuery.width,
+                                   height: gameQuery.height,
+                                   posx: column*gameQuery.width,
+                                   posy: row*gameQuery.height,
+                                   animation: animation});
+                                   
+            var newTile = tileSet.find("#"+$.gameQuery.tileIdPrefix+name+"_"+row+"_"+column);
+            if (gameQuery.multi) {
+                newTile.setAnimation(tileDescription);
+            } else {
+                newTile[0].gameQuery.animationNumber = tileDescription;
+            }
+            newTile.removeClass($.gameQuery.spriteCssClass);
+            newTile.addClass($.gameQuery.tileCssClass);
+            newTile.addClass($.gameQuery.tileTypePrefix+tileDescription);
+        }
+    }
+    
+    // Define the list of object/function accessible through $.
+    $.extend({ gameQuery: {
+        /**
+         * This is the Animation Object
+         */
+        Animation: function (options, imediateCallback) {
+            // private default values
+            var defaults = {
+                imageURL:      "",
+                numberOfFrame: 1,
+                delta:         0,
+                rate:          30,
+                type:          0,
+                distance:      0,
+                offsetx:       0,
+                offsety:       0
+            };
+
+            // options extends defaults
+            options = $.extend(defaults, options);
+
+            // "public" attributes:
+            this.imageURL      = options.imageURL;      // The url of the image to be used as an animation or sprite
+            this.numberOfFrame = options.numberOfFrame; // The number of frames to be displayed when playing the animation
+            this.delta         = options.delta;         // The distance in pixels between two frames
+            this.rate          = options.rate;          // The rate at which the frames change in miliseconds
+            this.type          = options.type;          // The type of the animation.This is a bitwise OR of the properties.
+            this.distance      = options.distance;      // The distance in pixels between two animations
+            this.offsetx       = options.offsetx;       // The x coordinate where the first sprite begins
+            this.offsety       = options.offsety;       // The y coordinate where the first sprite begins
+
+            // Whenever a new animation is created we add it to the ResourceManager animation list
+            $.gameQuery.resourceManager.addAnimation(this, imediateCallback);
+
+            return true;
+        },
+
+        /**
+         * "constants" for the different types of an animation
+         */ 
+        ANIMATION_VERTICAL:   1,  // Generated by a vertical offset of the background
+        ANIMATION_HORIZONTAL: 2,  // Generated by a horizontal offset of the background
+        ANIMATION_ONCE:       4,  // Played only once (else looping indefinitely)
+        ANIMATION_CALLBACK:   8,  // A callback is exectued at the end of a cycle
+        ANIMATION_MULTI:      16, // The image file contains many animations
+        ANIMATION_PINGPONG:   32, // At the last frame of the animation it reverses (if used in conjunction with ONCE it will have no effect)
+
+        // "constants" for the different type of geometry for a sprite
+        GEOMETRY_RECTANGLE:   1,
+        GEOMETRY_DISC:        2,
+
+        // basic values
+        refreshRate:          30,
+
+        /**
+         * An object to manage resource loading
+         */
+        resourceManager: {
+            animations: [],    // List of animations / images used in the game
+            sounds:     [],    // List of sounds used in the game
+            callbacks:  [],    // List of the functions called at each refresh
+            loadedAnimationsPointer: 0, // Keep track of the last loaded animation
+            loadedSoundsPointer:    0, // Keep track of the last loaded sound
+
+            /**
+             * Load resources before starting the game.
+             */
+            preload: function() {
+                // Start loading the images
+                for (var i = this.animations.length-1 ; i >= this.loadedAnimationsPointer; i --){
+                    this.animations[i].domO = new Image();
+                    this.animations[i].domO.src = this.animations[i].imageURL;
+                }
+
+                // Start loading the sounds
+                for (var i = this.sounds.length-1 ; i >= this.loadedSoundsPointer; i --){
+                    this.sounds[i].load();
+                }
+
+                $.gameQuery.resourceManager.waitForResources();
+            },
+
+            /**
+             * Wait for all the resources called for in preload() to finish loading.
+             */
+            waitForResources: function() {
+                // Check the images
+                var imageCount = 0;
+                for(var i=this.loadedAnimationsPointer; i < this.animations.length; i++){
+                    if(this.animations[i].domO.complete){
+                        imageCount++;
+                    }
+                }
+                // Check the sounds
+                var soundCount = 0;
+                for(var i=this.loadedSoundsPointer; i < this.sounds.length; i++){
+                    var temp = this.sounds[i].ready();
+                    if(temp){
+                        soundCount++;
+                    }
+                }
+                // Call the load callback with the current progress
+                if($.gameQuery.resourceManager.loadCallback){
+                    var percent = (imageCount + soundCount)/(this.animations.length + this.sounds.length - this.loadedAnimationsPointer - this.loadedSoundsPointer)*100;
+                    $.gameQuery.resourceManager.loadCallback(percent);
+                }
+                if(imageCount + soundCount < (this.animations.length + this.sounds.length  - this.loadedAnimationsPointer - this.loadedSoundsPointer)){
+                    imgWait=setTimeout(function () {
+                        $.gameQuery.resourceManager.waitForResources();
+                    }, 100);
+                } else {
+                    this.loadedAnimationsPointer = this.animations.length;
+                    this.loadedSoundsPointer = this.sounds.length;
+                    
+                    // All the resources are loaded! We can now associate the animation's images to their corresponding sprites
+                    $.gameQuery.scenegraph.children().each(function(){
+                        // recursive call on the children:
+                        $(this).children().each(arguments.callee);
+                        // add the image as a background
+                        if(this.gameQuery && this.gameQuery.animation){
+                            $(this).css("background-image", "url("+this.gameQuery.animation.imageURL+")");
+                            // we set the correct kind of repeat
+                            if(this.gameQuery.animation.type & $.gameQuery.ANIMATION_VERTICAL) {
+                                $(this).css("background-repeat", "repeat-x");
+                            } else if(this.gameQuery.animation.type & $.gameQuery.ANIMATION_HORIZONTAL) {
+                                $(this).css("background-repeat", "repeat-y");
+                            } else {
+                                $(this).css("background-repeat", "no-repeat");
+                            }
+                        }
+                    });
+
+                    // Launch the refresh loop
+                    if($.gameQuery.state === STATE_NEW){
+                        setInterval(function () {
+                            $.gameQuery.resourceManager.refresh();
+                        },($.gameQuery.refreshRate));
+                    }
+                    $.gameQuery.state = STATE_RUNNING;
+                    if($.gameQuery.startCallback){
+                        $.gameQuery.startCallback();
+                    }
+                    // Make the scenegraph visible
+                    $.gameQuery.scenegraph.css("visibility","visible");
+                }
+            },
+
+            /**
+             * This function refresh a unique sprite here 'this' represent a dom object
+             */
+            refreshSprite: function() {
+                // Check if 'this' is a gameQuery element
+                if(this.gameQuery != undefined){
+                    var gameQuery = this.gameQuery;
+                    // Does 'this' has an animation ?
+                    if(gameQuery.animation){
+                        // Do we have anything to do?
+                        if ( (gameQuery.idleCounter == gameQuery.animation.rate-1) && gameQuery.playing){
+
+                            // Does 'this' loops?
+                            if(gameQuery.animation.type & $.gameQuery.ANIMATION_ONCE){
+                                if(gameQuery.currentFrame < gameQuery.animation.numberOfFrame-1){
+                                    gameQuery.currentFrame += gameQuery.frameIncrement;
+                                } else if(gameQuery.currentFrame == gameQuery.animation.numberOfFrame-1) {
+                                    // Does 'this' has a callback ?
+                                    if(gameQuery.animation.type & $.gameQuery.ANIMATION_CALLBACK){
+                                        if($.isFunction(gameQuery.callback)){
+                                            gameQuery.callback(this);
+                                            gameQuery.callback = false;
+                                        }
+                                    }
+                                }
+                            } else {
+                                if(gameQuery.animation.type & $.gameQuery.ANIMATION_PINGPONG){
+                                    if(gameQuery.currentFrame == gameQuery.animation.numberOfFrame-1 && gameQuery.frameIncrement == 1) {
+                                        gameQuery.frameIncrement = -1;
+                                    } else if (gameQuery.currentFrame == 0 && gameQuery.frameIncrement == -1) {
+                                        gameQuery.frameIncrement = 1;
+                                    }
+                                }
+
+                                gameQuery.currentFrame = (gameQuery.currentFrame+gameQuery.frameIncrement)%gameQuery.animation.numberOfFrame;
+                                if(gameQuery.currentFrame == 0){
+                                    // Does 'this' has a callback ?
+                                    if(gameQuery.animation.type & $.gameQuery.ANIMATION_CALLBACK){
+                                        if($.isFunction(gameQuery.callback)){
+                                            gameQuery.callback(this);
+                                        }
+                                    }
+                                }
+                            }
+                            // Update the background
+                            if((gameQuery.animation.type & $.gameQuery.ANIMATION_VERTICAL) && (gameQuery.animation.numberOfFrame > 1)){
+                                if(gameQuery.multi){
+                                    $(this).css("background-position",""+(-gameQuery.animation.offsetx-gameQuery.multi)+"px "+(-gameQuery.animation.offsety-gameQuery.animation.delta*gameQuery.currentFrame)+"px");
+                                } else {
+                                    $(this).css("background-position",""+(-gameQuery.animation.offsetx)+"px "+(-gameQuery.animation.offsety-gameQuery.animation.delta*gameQuery.currentFrame)+"px");
+                                }
+                            } else if((gameQuery.animation.type & $.gameQuery.ANIMATION_HORIZONTAL) && (gameQuery.animation.numberOfFrame > 1)) {
+                                if(gameQuery.multi){
+                                    $(this).css("background-position",""+(-gameQuery.animation.offsetx-gameQuery.animation.delta*gameQuery.currentFrame)+"px "+(-gameQuery.animation.offsety-gameQuery.multi)+"px");
+                                } else {
+                                    $(this).css("background-position",""+(-gameQuery.animation.offsetx-gameQuery.animation.delta*gameQuery.currentFrame)+"px "+(-gameQuery.animation.offsety)+"px");
+                                }
+                            }
+                        }
+                        gameQuery.idleCounter = (gameQuery.idleCounter+1)%gameQuery.animation.rate;
+                    }
+                }
+                return true;
+            },
+
+            /**
+             * This function refresh a unique tile-map, here 'this' represent a dom object
+             */
+            refreshTilemap: function() {
+                // Check if 'this' is a gameQuery element
+                if(this.gameQuery != undefined){
+                    var gameQuery = this.gameQuery;
+                    if($.isArray(gameQuery.frameTracker)){
+                        for(var i=0; i<gameQuery.frameTracker.length; i++){
+                            // Do we have anything to do?
+                            if(gameQuery.idleCounter[i] == gameQuery.animations[i].rate-1){
+                                // Does 'this' loops?
+                                if(gameQuery.animations[i].type & $.gameQuery.ANIMATION_ONCE){
+                                    if(gameQuery.frameTracker[i] < gameQuery.animations[i].numberOfFrame-1){
+                                        gameQuery.frameTracker[i] += gameQuery.frameIncrement[i];
+                                    }
+                                } else {
+                                    if(gameQuery.animations[i].type & $.gameQuery.ANIMATION_PINGPONG){
+                                        if(gameQuery.frameTracker[i] == gameQuery.animations[i].numberOfFrame-1 && gameQuery.frameIncrement[i] == 1) {
+                                            gameQuery.frameIncrement[i] = -1;
+                                        } else if (gameQuery.frameTracker[i] == 0 && gameQuery.frameIncrement[i] == -1) {
+                                            gameQuery.frameIncrement[i] = 1;
+                                        }
+                                    }
+                                    gameQuery.frameTracker[i] = (gameQuery.frameTracker[i]+gameQuery.frameIncrement[i])%gameQuery.animations[i].numberOfFrame;
+                                }
+                            }
+                            gameQuery.idleCounter[i] = (gameQuery.idleCounter[i]+1)%gameQuery.animations[i].rate;
+                        }
+                    } else {
+                        // Do we have anything to do?
+                        if(gameQuery.idleCounter == gameQuery.animations.rate-1){
+                            // Does 'this' loops?
+                            if(gameQuery.animations.type & $.gameQuery.ANIMATION_ONCE){
+                                if(gameQuery.frameTracker < gameQuery.animations.numberOfFrame-1){
+                                    gameQuery.frameTracker += gameQuery.frameIncrement;
+                                }
+                            } else {
+                                if(gameQuery.animations.type & $.gameQuery.ANIMATION_PINGPONG){
+                                    if(gameQuery.frameTracker == gameQuery.animations.numberOfFrame-1 && gameQuery.frameIncrement == 1) {
+                                        gameQuery.frameIncrement = -1;
+                                    } else if (gameQuery.frameTracker == 0 && gameQuery.frameIncrement == -1) {
+                                        gameQuery.frameIncrement = 1;
+                                    }
+                                }
+                                gameQuery.frameTracker = (gameQuery.frameTracker+gameQuery.frameIncrement)%gameQuery.animations.numberOfFrame;
+                            }
+                        }
+                        gameQuery.idleCounter = (gameQuery.idleCounter+1)%gameQuery.animations.rate;
+                    }
+
+
+                    // Update the background of all active tiles
+                    $(this).find("."+$.gameQuery.tileCssClass).each(function(){
+                        if($.isArray(gameQuery.frameTracker)){
+                            var animationNumber = this.gameQuery.animationNumber
+                            if((gameQuery.animations[animationNumber].type & $.gameQuery.ANIMATION_VERTICAL) && (gameQuery.animations[animationNumber].numberOfFrame > 1)){
+                                $(this).css("background-position",""+(-gameQuery.animations[animationNumber].offsetx)+"px "+(-gameQuery.animations[animationNumber].offsety-gameQuery.animations[animationNumber].delta*gameQuery.frameTracker[animationNumber])+"px");
+                            } else if((gameQuery.animations[animationNumber].type & $.gameQuery.ANIMATION_HORIZONTAL) && (gameQuery.animations[animationNumber].numberOfFrame > 1)) {
+                                $(this).css("background-position",""+(-gameQuery.animations[animationNumber].offsetx-gameQuery.animations[animationNumber].delta*gameQuery.frameTracker[animationNumber])+"px "+(-gameQuery.animations[animationNumber].offsety)+"px");
+                            }
+                        } else {
+                            if((gameQuery.animations.type & $.gameQuery.ANIMATION_VERTICAL) && (gameQuery.animations.numberOfFrame > 1)){
+                                $(this).css("background-position",""+(-gameQuery.animations.offsetx-this.gameQuery.multi)+"px "+(-gameQuery.animations.offsety-gameQuery.animations.delta*gameQuery.frameTracker)+"px");
+                            } else if((gameQuery.animations.type & $.gameQuery.ANIMATION_HORIZONTAL)  && (gameQuery.animations.numberOfFrame > 1)) {
+                                $(this).css("background-position",""+(-gameQuery.animations.offsetx-gameQuery.animations.delta*gameQuery.frameTracker)+"px "+(-gameQuery.animations.offsety-this.gameQuery.multi)+"px");
+                            }
+                        }
+                    });
+                }
+                return true;
+            },
+
+            /**
+             * Called periodically to refresh the state of the game.
+             */
+            refresh: function() {
+                if($.gameQuery.state === STATE_RUNNING) {
+                    $.gameQuery.playground.find("."+$.gameQuery.spriteCssClass).each(this.refreshSprite);
+                    $.gameQuery.playground.find("."+$.gameQuery.tilemapCssClass).each(this.refreshTilemap);
+                    var deadCallback= new Array();
+                    for (var i = this.callbacks.length-1; i >= 0; i--){
+                        if(this.callbacks[i].idleCounter == this.callbacks[i].rate-1){
+                            var returnedValue = this.callbacks[i].fn();
+                            if(typeof returnedValue == 'boolean'){
+                                // If we have a boolean: 'true' means 'no more execution', 'false' means 'keep on executing'
+                                if(returnedValue){
+                                    deadCallback.push(i);
+                                }
+                            } else if(typeof returnedValue == 'number') {
+                                // If we have a number it re-defines the time to the next call
+                                this.callbacks[i].rate = Math.round(returnedValue/$.gameQuery.refreshRate);
+                                this.callbacks[i].idleCounter = 0;
+                            }
+                        }
+                        this.callbacks[i].idleCounter = (this.callbacks[i].idleCounter+1)%this.callbacks[i].rate;
+                    }
+                    for(var i = deadCallback.length-1; i >= 0; i--){
+                        this.callbacks.splice(deadCallback[i],1);
+                    }
+                }
+            },
+
+            /**
+             * Add an animation to the resource Manager 
+             */
+            addAnimation: function(animation, callback) {
+                if($.inArray(animation,this.animations)<0){
+                    //normalize the animation rate:
+                    animation.rate = Math.round(animation.rate/$.gameQuery.refreshRate);
+                    if(animation.rate==0){
+                        animation.rate = 1;
+                    }
+                    this.animations.push(animation);
+                    switch ($.gameQuery.state){
+                        case STATE_NEW:
+                        case STATE_PAUSED:
+                            // Nothing to do for now 
+                            break;
+                        case STATE_RUNNING:
+                            // immediatly load the animation and call the callback if any
+                            this.animations[this.loadedAnimationsPointer].domO = new Image();
+                            this.animations[this.loadedAnimationsPointer].domO.src = animation.imageURL;
+                            if (callback !== undefined){
+                                this.animations[this.loadedAnimationsPointer].domO.onload = callback;
+                            }
+                            this.loadedAnimationsPointer++;
+                            break;
+                    }
+                }
+            },
+            
+            /**
+             * Add a sound to the resource Manager 
+             */
+            addSound: function(sound, callback){
+                if($.inArray(sound,this.sounds)<0){
+                    this.sounds.push(sound);
+                    switch ($.gameQuery.state){
+                        case STATE_NEW:
+                        case STATE_PAUSED:
+                            // Nothing to do for now 
+                            break;
+                        case STATE_RUNNING:
+                            // immediatly load the sound and call the callback if any
+                            sound.load();
+                            // TODO callback....
+                            this.loadedSoundsPointer++;
+                            break;
+                    }
+                }
+            },
+
+            /**
+             * Register a callback
+             * 
+             * @param {function} fn the callback
+             * @param {integer} rate the rate in ms at which the callback should be called (should be a multiple of the playground rate or will be rounded) 
+             */
+            registerCallback: function(fn, rate){
+                rate  = Math.round(rate/$.gameQuery.refreshRate);
+                if(rate==0){
+                    rate = 1;
+                }
+                this.callbacks.push({fn: fn, rate: rate, idleCounter: 0});
+            },
+            
+            /**
+             * Clear the animations and sounds 
+             */
+            clear: function(callbacksToo){
+                this.animations  = [];
+                this.loadedAnimationsPointer = 0;
+                this.sounds = [];
+                this.loadedSoundsPointer = 0;
+                if(callbacksToo) {
+                    this.callbacks = [];
+                }
+            }
+        },
+
+        /**
+         * This is a single place to update the underlying data of sprites/groups/tiles after a position or dimesion modification.
+         */ 
+        update: function(descriptor, transformation) {
+            // Did we really receive a descriptor or a jQuery object instead?
+            if(!$.isPlainObject(descriptor)){
+                // Then we must get real descriptor
+                if(descriptor.length > 0){
+                    var gameQuery = descriptor[0].gameQuery;
+                } else {
+                    var gameQuery = descriptor.gameQuery;
+                }
+            } else {
+                var gameQuery = descriptor;
+            }
+            // If we couldn't find one we return
+            if(!gameQuery) return;
+            if(gameQuery.tileSet === true){
+                // We have a tilemap 
+                
+                var visible = visibleTilemapIndexes(descriptor);
+                var buffered = gameQuery.buffered;
+                
+                // Test what kind of transformation we have and react accordingly 
+                for(property in transformation){
+                    switch(property){
+                        case "x":
+                        
+                            if(visible.lastColumn > buffered.lastColumn) {
+                                
+                                // Detach the tilemap
+                                var parent = descriptor[0].parentNode;
+                                var tilemap = descriptor.detach();
+                                
+                                var newBuffered = bufferedTilemapIndexes(descriptor, visible);
+                                for(var i = gameQuery.buffered.firstRow; i < gameQuery.buffered.lastRow; i++){
+                                    // Remove the newly invisible tiles
+                                    for(var j = gameQuery.buffered.firstColumn; j < Math.min(newBuffered.firstColumn, gameQuery.buffered.lastColumn); j++) {
+                                        tilemap.find("#"+$.gameQuery.tileIdPrefix+descriptor.attr("id")+"_"+i+"_"+j).remove();
+                                    }
+                                    // And add the newly visible tiles
+                                    for(var j = Math.max(gameQuery.buffered.lastColumn,newBuffered.firstColumn); j < newBuffered.lastColumn ; j++) {
+                                        addTile(tilemap,i,j);
+                                    }
+                                }
+                                
+                                gameQuery.buffered.firstColumn = newBuffered.firstColumn;
+                                gameQuery.buffered.lastColumn  = newBuffered.lastColumn;
+                                
+                                // Attach the tilemap back
+                                tilemap.appendTo(parent);
+                                
+                            }
+                            
+                            if(visible.firstColumn < buffered.firstColumn) {
+                                
+                                // Detach the tilemap
+                                var parent = descriptor[0].parentNode;
+                                var tilemap = descriptor.detach();
+                                    
+                                var newBuffered = bufferedTilemapIndexes(descriptor, visible);
+                                for(var i = gameQuery.buffered.firstRow; i < gameQuery.buffered.lastRow; i++){
+                                    // Remove the newly invisible tiles
+                                    for(var j = Math.max(newBuffered.lastColumn,gameQuery.buffered.firstColumn); j < gameQuery.buffered.lastColumn ; j++) {
+                                        tilemap.find("#"+$.gameQuery.tileIdPrefix+descriptor.attr("id")+"_"+i+"_"+j).remove();
+                                    }
+                                    // And add the newly visible tiles
+                                    for(var j = newBuffered.firstColumn; j < Math.min(gameQuery.buffered.firstColumn,newBuffered.lastColumn); j++) {
+                                        addTile(tilemap,i,j);
+                                    }
+                                }
+                                
+                                gameQuery.buffered.firstColumn = newBuffered.firstColumn;
+                                gameQuery.buffered.lastColumn  = newBuffered.lastColumn;
+                                
+                                // Attach the tilemap back
+                                tilemap.appendTo(parent);
+                            }
+                            break;
+                            
+                        case "y":
+                        
+                            if(visible.lastRow > buffered.lastRow) {
+                                
+                                // Detach the tilemap
+                                var parent = descriptor[0].parentNode;
+                                var tilemap = descriptor.detach();
+                                
+                                var newBuffered = bufferedTilemapIndexes(descriptor, visible);
+                                for(var j = gameQuery.buffered.firstColumn; j < gameQuery.buffered.lastColumn ; j++) {
+                                    // Remove the newly invisible tiles
+                                    for(var i = gameQuery.buffered.firstRow; i < Math.min(newBuffered.firstRow, gameQuery.buffered.lastRow); i++){
+                                        tilemap.find("#"+$.gameQuery.tileIdPrefix+descriptor.attr("id")+"_"+i+"_"+j).remove();
+                                    }
+                                    // And add the newly visible tiles
+                                    for(var i = Math.max(gameQuery.buffered.lastRow, newBuffered.firstRow); i < newBuffered.lastRow; i++){
+                                        addTile(tilemap,i,j);
+                                    }
+                                }
+                                
+                                gameQuery.buffered.firstRow = newBuffered.firstRow;
+                                gameQuery.buffered.lastRow  = newBuffered.lastRow;
+                                
+                                // Attach the tilemap back
+                                tilemap.appendTo(parent);
+                                
+                            }  
+                            
+                            if(visible.firstRow < buffered.firstRow) {
+                                
+                                // Detach the tilemap
+                                var parent = descriptor[0].parentNode;
+                                var tilemap = descriptor.detach();
+                                
+                                var newBuffered = bufferedTilemapIndexes(descriptor, visible);
+                                for(var j = gameQuery.buffered.firstColumn; j < gameQuery.buffered.lastColumn ; j++) {
+                                    // Remove the newly invisible tiles
+                                    for(var i = Math.max(newBuffered.lastRow, gameQuery.buffered.firstRow); i < gameQuery.buffered.lastRow; i++){
+                                        tilemap.find("#"+$.gameQuery.tileIdPrefix+descriptor.attr("id")+"_"+i+"_"+j).remove();
+                                    }
+                                    // And add the newly visible tiles
+                                    for(var i = newBuffered.firstRow; i < Math.min(gameQuery.buffered.firstRow, newBuffered.lastRow); i++){
+                                        addTile(tilemap,i,j);
+                                    }
+                                }
+                                
+                                gameQuery.buffered.firstRow = newBuffered.firstRow;
+                                gameQuery.buffered.lastRow  = newBuffered.lastRow;
+                                
+                                // Attach the tilemap back
+                                tilemap.appendTo(parent);
+                            }
+                            break;
+                            
+                        case "angle":
+                            //TODO
+                            break;
+                            
+                        case "factor":
+                            //TODO
+                            break;
+                    }
+                }
+
+            } else {
+                var refreshBoundingCircle = $.gameQuery.playground && !$.gameQuery.playground.disableCollision;
+
+                // Update the descriptor
+                for(property in transformation){
+                    switch(property){
+                        case "x":
+                            if(refreshBoundingCircle){
+                                gameQuery.boundingCircle.x = gameQuery.posx+gameQuery.width/2;
+                            }
+                            break;
+                        case "y":
+                            if(refreshBoundingCircle){
+                                gameQuery.boundingCircle.y = gameQuery.posy+gameQuery.height/2;
+                            }
+                            break;
+                        case "w":
+                        case "h":
+                            gameQuery.boundingCircle.originalRadius = Math.sqrt(Math.pow(gameQuery.width,2) + Math.pow(gameQuery.height,2))/2
+                            gameQuery.boundingCircle.radius = gameQuery.factor*gameQuery.boundingCircle.originalRadius;
+                            break;
+                        case "angle": //(in degrees)
+                            gameQuery.angle = parseFloat(transformation.angle);
+                            break;
+                        case "factor":
+                            gameQuery.factor = parseFloat(transformation.factor);
+                            if(refreshBoundingCircle){
+                                gameQuery.boundingCircle.radius = gameQuery.factor*gameQuery.boundingCircle.originalRadius;
+                            }
+                            break;
+                    }
+                }
+            }
+        },
+        // State of the engine
+        state: STATE_NEW,
+        
+        // CSS classes used to mark game element 
+        spriteCssClass:  gQprefix + "sprite",
+        groupCssClass:   gQprefix + "group",
+        tilemapCssClass: gQprefix + "tilemap",
+        tileCssClass:    gQprefix + "tile",
+        // Prefix for CSS Ids or Classes
+        tileTypePrefix:  gQprefix + "tileType_",
+        tileIdPrefix:    gQprefix + "tile_"
+    },
+
+    /** 
+     * Mute (or unmute) all the sounds.
+     */
+    muteSound: function(muted){
+        for (var i = $.gameQuery.resourceManager.sounds.length-1 ; i >= 0; i --) {
+            $.gameQuery.resourceManager.sounds[i].muted(muted);
+        }
+    },
+    
+    /**
+     * Accessor for the currently defined playground as a jQuery object
+     */
+    playground: function() {
+        return $.gameQuery.playground
+    },
+    
+    /**
+     * Define a callback called during the loading of the game's resources.
+     *
+     * The function will recieve as unique parameter
+     * a number representing the progess percentage.
+     */
+    loadCallback: function(callback){
+        $.gameQuery.resourceManager.loadCallback = callback;
+    }
+    }); // end of the extensio of $
+
+
+    // fragments used to create DOM element
+    var spriteFragment  = $("<div class='"+$.gameQuery.spriteCssClass+"'  style='position: absolute; display: block; overflow: hidden' />");
+    var groupFragment   = $("<div class='"+$.gameQuery.groupCssClass+"'  style='position: absolute; display: block; overflow: hidden' />");
+    var tilemapFragment = $("<div class='"+$.gameQuery.tilemapCssClass+"' style='position: absolute; display: block; overflow: hidden;' />");
+
+
+    // Define the list of object/function accessible through $("selector").
+    $.fn.extend({
+        /**
+         * Defines the currently selected div to which contains the game and initialize it.
+         * 
+         * This is a non-destructive call
+         */
+        playground: function(options) {
+            if(this.length == 1){
+                if(this[0] == document){ 
+                    // Old usage detected, this is not supported anymore
+                    throw "Old playground usage, use $.playground() to retreive the playground and $('mydiv').playground(options) to set the div!";
+                }
+                options = $.extend({
+                    height:        320,
+                    width:        480,
+                    refreshRate: 30,
+                    position:    "absolute",
+                    keyTracker:    false,
+                    mouseTracker: false,
+                    disableCollision: false
+                }, options);
+                // We save the playground node and set some variable for this node:
+                $.gameQuery.playground = this;
+                $.gameQuery.refreshRate = options.refreshRate;
+                $.gameQuery.playground[0].height = options.height;
+                $.gameQuery.playground[0].width = options.width;
+
+                // We initialize the display of the div
+                $.gameQuery.playground.css({
+                        position: options.position,
+                        display:  "block",
+                        overflow: "hidden",
+                        height:   options.height+"px",
+                        width:    options.width+"px"
+                    })
+                    .append("<div id='"+gQprefix+"scenegraph' style='visibility: hidden'/>");
+
+                $.gameQuery.scenegraph = $("#"+gQprefix+"scenegraph");
+
+                // Add the keyTracker to the gameQuery object:
+                $.gameQuery.keyTracker = {};
+                // We only enable the real tracking if the users wants it
+                if(options.keyTracker){
+                    $(document).keydown(function(event){
+                        $.gameQuery.keyTracker[event.keyCode] = true;
+                    });
+                    $(document).keyup(function(event){
+                        $.gameQuery.keyTracker[event.keyCode] = false;
+                    });
+                }
+                
+                // Add the mouseTracker to the gameQuery object:
+                 $.gameQuery.mouseTracker = {
+                    x: 0,
+                    y: 0};
+                // We only enable the real tracking if the users wants it
+                var scenegraphOffset = $.gameQuery.playground.offset();
+                if(options.mouseTracker){
+                    $($.gameQuery.playground).mousemove(function(event){
+                        $.gameQuery.mouseTracker.x = event.pageX - scenegraphOffset.left;
+                        $.gameQuery.mouseTracker.y = event.pageY - scenegraphOffset.top;
+                    });
+                    $(document).mousedown(function(event){
+                        $.gameQuery.mouseTracker[event.which] = true;
+                    });
+                    $(document).mouseup(function(event){
+                        $.gameQuery.mouseTracker[event.which] = false;
+                    });
+                }
+            }
+            return this;
+        },
+
+        /**
+         * Starts the game.
+         *
+         * Resources from the resource manager are preloaded if necesary
+         * Works only for the playground node.
+         *
+         * This is a non-destructive call
+         */
+        startGame: function(callback) {
+            $.gameQuery.startCallback = callback;
+            $.gameQuery.resourceManager.preload();
+            return this;
+        },
+        
+        /**
+         * TODO
+         */
+        pauseGame: function() {
+            $.gameQuery.state = STATE_PAUSED;
+            $.gameQuery.scenegraph.css("visibility","hidden");
+            return this;
+        },
+        
+        /**
+         * Resume the game if it was paused and call the callback passed in argument once the newly added ressources are loaded.
+         */
+        resumeGame: function(callback) {
+            if($.gameQuery.state === STATE_PAUSED){
+                $.gameQuery.startCallback = callback;
+                $.gameQuery.resourceManager.preload();
+            }
+            return this;
+        },
+
+        /**
+         * Removes all the sprites, groups and tilemaps present in the scenegraph
+         */
+        clearScenegraph: function() {
+            $.gameQuery.scenegraph.empty()
+            return this;
+        },
+        
+        /**
+         * Removes all the sprites, groups and tilemaps present in the scenegraph as well as all loaded animations and sounds
+         */
+        clearAll: function(callbackToo) {
+            $.gameQuery.scenegraph.empty();
+            $.gameQuery.resourceManager.clear(callbackToo)
+            return this;
+        },
+
+        /**
+         * Add a group to the scene graph. Works only on the scenegraph root or on another group
+         *
+         * This IS a destructive call and should be terminated with end()
+         * to go back one level up in the chaining
+         */
+        addGroup: function(group, options) {
+            options = $.extend({
+                width:      32,
+                height:     32,
+                posx:       0,
+                posy:       0,
+                posz:       0,
+                posOffsetX: 0,
+                posOffsetY: 0,
+                overflow:   "visible",
+                geometry:   $.gameQuery.GEOMETRY_RECTANGLE,
+                angle:      0,
+                factor:     1,
+                factorh:    1,
+                factorv:    1
+            }, options);
+
+            var newGroupElement = groupFragment.clone().attr("id",group).css({
+                    overflow: options.overflow,
+                    height:   options.height,
+                    width:    options.width
+                });
+            
+            if(this == $.gameQuery.playground){
+                $.gameQuery.scenegraph.append(newGroupElement);
+            } else if ((this == $.gameQuery.scenegraph)||(this.hasClass($.gameQuery.groupCssClass))){
+                this.append(newGroupElement);
+            }
+            newGroupElement[0].gameQuery = options;
+            newGroupElement[0].gameQuery.boundingCircle = {x: options.posx + options.width/2,
+                                                    y: options.posy + options.height/0,
+                                                    originalRadius: Math.sqrt(Math.pow(options.width,2) + Math.pow(options.height,2))/2};
+            newGroupElement[0].gameQuery.boundingCircle.radius = newGroupElement[0].gameQuery.boundingCircle.originalRadius;
+            newGroupElement[0].gameQuery.group = true;
+            newGroupElement.transform();
+            return this.pushStack(newGroupElement);
+        },
+
+        /**
+         * Add a sprite to the current node. Works only on the playground or any of its sub-nodes 
+         * 
+         * This is a non-destructive call
+         */
+        addSprite: function(sprite, options) {
+            options = $.extend({
+                width:          32,
+                height:         32,
+                posx:           0,
+                posy:           0,
+                posz:           0,
+                posOffsetX:     0,
+                posOffsetY:     0,
+                idleCounter:    0,
+                currentFrame:   0,
+                frameIncrement: 1,
+                geometry:       $.gameQuery.GEOMETRY_RECTANGLE,
+                angle:          0,
+                factor:         1,
+                playing:        true,
+                factorh:        1,
+                factorv:        1
+            }, options);
+
+            var newSpriteElem = spriteFragment.clone().attr("id",sprite).css({
+                     height: options.height,
+                     width: options.width,
+                     backgroundPosition: ((options.animation)? -options.animation.offsetx : 0)+"px "+((options.animation)? -options.animation.offsety : 0)+"px"
+                });
+                
+            if(this == $.gameQuery.playground){
+                $.gameQuery.scenegraph.append(newSpriteElem);
+            } else {
+                this.append(newSpriteElem);
+            }
+
+            // If the game has already started we want to add the animation's image as a background now
+            if(options.animation){
+                // The second test is a fix for default background    (https://github.com/onaluf/gameQuery/issues/3)
+                if($.gameQuery.state === STATE_RUNNING && options.animation.imageURL !== ''){
+                    newSpriteElem.css("background-image", "url("+options.animation.imageURL+")");
+                }
+                if(options.animation.type & $.gameQuery.ANIMATION_VERTICAL) {
+                    newSpriteElem.css("background-repeat", "repeat-x");
+                } else if(options.animation.type & $.gameQuery.ANIMATION_HORIZONTAL) {
+                    newSpriteElem.css("background-repeat", "repeat-y");
+                } else {
+                    newSpriteElem.css("background-repeat", "no-repeat");
+                }
+            }
+
+
+            var spriteDOMObject = newSpriteElem[0];
+            if(spriteDOMObject != undefined){
+                spriteDOMObject.gameQuery = options;
+                // Compute bounding Circle
+                spriteDOMObject.gameQuery.boundingCircle = {x: options.posx + options.width/2,
+                                                            y: options.posy + options.height/2,
+                                                            originalRadius: Math.sqrt(Math.pow(options.width,2) + Math.pow(options.height,2))/2};
+                spriteDOMObject.gameQuery.boundingCircle.radius = spriteDOMObject.gameQuery.boundingCircle.originalRadius;
+            }
+            newSpriteElem.transform();
+            return this;
+        },
+
+        /**
+         * Add a Tile Map to the selected element.
+         *
+         * This is a non-destructive call. The added sprite is NOT selected after a call to this function!
+         */
+        addTilemap: function(name, tileDescription, animationList, options){
+            options = $.extend({
+                width:          32,
+                height:         32,
+                sizex:          32,
+                sizey:          32,
+                posx:           0,
+                posy:           0,
+                posz:           0,
+                posOffsetX:     0,
+                posOffsetY:     0,
+                angle:          0,
+                factor:         1,
+                factorh:        1,
+                factorv:        1,
+                buffer:         1
+            }, options);
+
+            var tileSet = tilemapFragment.clone().attr("id",name).css({
+                    height: options.height*options.sizey, 
+                    width: options.width*options.sizex
+                });
+            
+            if(this == $.gameQuery.playground){
+                $.gameQuery.scenegraph.append(tileSet);
+            } else {
+                this.append(tileSet);
+            }
+            
+            tileSet[0].gameQuery = options;
+            var gameQuery = tileSet[0].gameQuery;
+            gameQuery.tileSet = true;
+            gameQuery.tiles = tileDescription;
+            gameQuery.func = (typeof tileDescription === "function");
+                
+            if($.isArray(animationList)){
+                var frameTracker = [];
+                var idleCounter = [];
+                var frameIncrement = [];
+                for(var i=0; i<animationList.length; i++){
+                    frameTracker[i] = 0;
+                    idleCounter[i] = 0;
+                    frameIncrement[i] = 1;
+                }
+                gameQuery.frameTracker = frameTracker;
+                gameQuery.animations = animationList;
+                gameQuery.idleCounter =  idleCounter;
+                gameQuery.frameIncrement = frameIncrement;
+                gameQuery.multi = false;
+            } else {
+                gameQuery.frameTracker = 0;
+                gameQuery.frameIncrement = 1;
+                gameQuery.animations = animationList;
+                gameQuery.idleCounter =  0;
+                gameQuery.multi = true;
+                
+            }
+
+            // Get the tileSet offset (relative to the playground)
+            var visible = visibleTilemapIndexes(tileSet);
+            var buffered = bufferedTilemapIndexes(tileSet, visible);
+            gameQuery.buffered = buffered;
+
+            // For many simple animation
+            for(var i = buffered.firstRow; i < buffered.lastRow; i++){
+                for(var j = buffered.firstColumn; j < buffered.lastColumn ; j++) {
+                    addTile(tileSet, i, j);
+                }
+            }
+            tileSet.transform()
+            return this.pushStack(tileSet);
+        },
+        
+        /**
+         * This function imports a JSON file generated by Tiled (http://www.mapeditor.org/). 
+         * All the created tilemaps will be directly under the currently selected element. 
+         * Their name will be made of the provided prefix followed by a number starting at 0. 
+         * 
+         * Only layer of type "tilelayer" are supported for now. Only one single tileset 
+         * per layer is supported.
+         * 
+         * After the call to this function the second argument will hold two new arrays:
+         * - tiles: an arrays of tilemaps wraped in jQuery.
+         * - animations: an arrays of animations 
+         * 
+         * This is a non-destructive call
+         */
+        importTilemaps: function(url, prefix, generatedElements){
+            var animations = [];
+            var tilemaps = [];
+            
+            var that = this;
+            
+            var tilemapJsonLoaded = function(json){
+                var tilesetGID = [];
+                for (var i = 0; i < json.tilesets.length; i++) {
+                    tilesetGID[i] = json.tilesets[i].firstgid;
+                } 
+                
+                var getTilesetIndex = function(index){
+                    var i = 0;
+                    while(index >= tilesetGID[i] && i < tilesetGID.length){
+                        i++;
+                    }
+                    return i-1;
+                }
+        
+                var height = json.height;
+                var width  = json.width;
+                var tileHeight = json.tileheight; 
+                var tileWidth  = json.tilewidth;
+                
+                var layers = json.layers;
+                var usedTiles = [];
+                var animationCounter = 0;
+                var tilemapArrays = [];
+                
+                // Detect which animations we need to generate
+                // and convert the tiles array indexes to the new ones
+                for (var i=0; i < layers.length; i++){
+                    if(layers[i].type === "tilelayer"){
+                        var tilemapArray = new Array(height);
+                        for (var j=0; j<height; j++){
+                            tilemapArray[j] = new Array(width);
+                        }
+                        for (var j=0; j < layers[i].data.length; j++){
+                            var tile = layers[i].data[j];
+                            if(tile === 0){
+                                tilemapArray[Math.floor(j / width)][j % width] = 0;
+                            } else {
+                                if(!usedTiles[tile]){
+                                    animationCounter++;
+                                    usedTiles[tile] = animationCounter;
+                                    animations.push(new $.gameQuery.Animation({
+                                        imageURL: json.tilesets[getTilesetIndex(tile)].image,
+                                        offsetx: ((tile-1) % Math.floor(json.tilesets[getTilesetIndex(tile)].imagewidth / tileWidth)) * tileWidth,
+                                        offsety: Math.floor((tile-1) / Math.floor(json.tilesets[getTilesetIndex(tile)].imagewidth / tileWidth)) * tileHeight
+                                    }));
+                                }
+                                tilemapArray[Math.floor(j / width)][j % width] = usedTiles[tile];
+                            }
+                        }
+                        tilemapArrays.push(tilemapArray);
+                    }
+                }
+                // adding the tilemaps
+                for (var i=0; i<tilemapArrays.length; i++){
+                     tilemaps.push(that.addTilemap(
+                        prefix+i, 
+                        tilemapArrays[i],
+                        animations,
+                        {
+                            sizex:  width,
+                            sizey:  height,
+                            width:  tileWidth,
+                            height: tileHeight
+                    }));
+                }
+            };
+    
+            $.ajax({
+                url: url,
+                async: false,
+                dataType: 'json',
+                success: tilemapJsonLoaded
+            });
+            
+            if(generatedElements !== undefined){
+                generatedElements.animations = animations;
+                generatedElements.tilemaps = tilemaps;
+            }
+        
+            return this;
+        },
+
+        /**
+         * Stop the animation at the current frame
+         * 
+         * This is a non-destructive call.
+         */
+        pauseAnimation: function() {
+            this[0].gameQuery.playing = false;
+            return this;
+        },
+
+        /**
+         * Resume the animation (if paused)
+         * 
+         * This is a non-destructive call.
+         */
+        resumeAnimation: function() {
+            this[0].gameQuery.playing = true;
+            return this;
+        },
+
+        /**
+         * Changes the animation associated with a sprite.
+         *
+         * WARNING: no checks are made to ensure that the object is really a sprite
+         *
+         * This is a non-destructive call.
+         */
+        setAnimation: function(animation, callback) {
+            var gameQuery = this[0].gameQuery;
+            if(typeof animation == "number"){
+                if(gameQuery.animation.type & $.gameQuery.ANIMATION_MULTI){
+                    var distance = gameQuery.animation.distance * animation;
+                    gameQuery.multi = distance;
+                    gameQuery.frameIncrement = 1;
+                    gameQuery.currentFrame = 0;
+                    
+                    if(gameQuery.animation.type & $.gameQuery.ANIMATION_VERTICAL) {
+                        this.css("background-position",""+(-distance-gameQuery.animation.offsetx)+"px "+(-gameQuery.animation.offsety)+"px");
+                    } else if(gameQuery.animation.type & $.gameQuery.ANIMATION_HORIZONTAL) {
+                        this.css("background-position",""+(-gameQuery.animation.offsetx)+"px "+(-distance-gameQuery.animation.offsety)+"px");
+                    }
+                }
+            } else {
+                if(animation){
+                    gameQuery.animation = animation;
+                    gameQuery.currentFrame = 0;
+                    gameQuery.frameIncrement = 1;
+
+                    if (animation.imageURL !== '') {this.css("backgroundImage", "url('"+animation.imageURL+"')");}
+                    this.css({"background-position": ""+(-animation.offsetx)+"px "+(-animation.offsety)+"px"});
+
+                    if(gameQuery.animation.type & $.gameQuery.ANIMATION_VERTICAL) {
+                        this.css("background-repeat", "repeat-x");
+                    } else if(gameQuery.animation.type & $.gameQuery.ANIMATION_HORIZONTAL) {
+                        this.css("background-repeat", "repeat-y");
+                    } else {
+                        this.css("background-repeat", "no-repeat");
+                    }
+                } else {
+                    this.css("background-image", "");
+                }
+            }
+
+            if(callback != undefined){
+                this[0].gameQuery.callback = callback;
+            }
+
+            return this;
+        },
+
+        /**
+         * Register a callback funnction
+         *
+         * This is a non-destructive call
+         *
+         * @param {Function} fn the callback function.
+         * @param {Number} rate time in milliseconds between calls.
+         */
+        registerCallback: function(fn, rate) {
+            $.gameQuery.resourceManager.registerCallback(fn, rate);
+            return this;
+        },
+
+        /**
+         * Retrieve a list of objects in collision with the subject.
+         *
+         * If 'this' is a sprite or a group, the function will retrieve the list of sprites (not groups!!!) that touch it. For now all abject are considered to be boxes.
+         *
+         * This IS a destructive call and should be terminated with end() to go back one level up in the chaining.
+         */
+        collision: function(arg1, arg2){
+            var filter, override;
+            if ($.isPlainObject(arg1)){
+                override = arg1;
+            } else if (typeof arg1 === "string") {
+                filter = arg1;
+            }
+            if ($.isPlainObject(arg2)){
+                override = arg2;
+            } else if (typeof arg2 === "string") {
+                filter = arg2;
+            }
+            
+            var resultList = [];
+
+            // Retrieve 'this' offset by looking at the parents
+            var itsParent = this[0].parentNode, offsetX = 0, offsetY = 0;
+            while (itsParent != $.gameQuery.playground[0]){
+                    if(itsParent.gameQuery){
+                    offsetX += itsParent.gameQuery.posx;
+                    offsetY += itsParent.gameQuery.posy;
+                }
+                itsParent = itsParent.parentNode;
+            }
+
+            // Retrieve the playground's absolute position and size information
+            var pgdGeom = {top: 0, left: 0, bottom: $.playground().height(), right: $.playground().width()};
+
+            // Retrieve the gameQuery object and correct it with the override
+            var gameQuery = jQuery.extend(true, {}, this[0].gameQuery);
+
+            // Retrieve the BoundingCircle and correct it with the override
+            var boundingCircle = jQuery.extend(true, {}, gameQuery.boundingCircle);
+            if(override && override.w){
+                gameQuery.width = override.w;
+            }
+            if(override && override.h){
+                gameQuery.height = override.h;
+            }
+            boundingCircle.originalRadius = Math.sqrt(Math.pow(gameQuery.width,2) + Math.pow(gameQuery.height,2))/2
+            boundingCircle.radius = gameQuery.factor*boundingCircle.originalRadius;
+            
+            if(override && override.x){
+                boundingCircle.x = override.x + gameQuery.width/2.0;
+            }
+            if(override && override.y){
+                boundingCircle.y = override.y + gameQuery.height/2.0;
+            }
+            
+            gameQuery.boundingCircle = boundingCircle;
+            
+
+            // Is 'this' inside the playground ?
+            if( (gameQuery.boundingCircle.y + gameQuery.boundingCircle.radius + offsetY < pgdGeom.top)    ||
+                (gameQuery.boundingCircle.x + gameQuery.boundingCircle.radius + offsetX < pgdGeom.left)   ||
+                (gameQuery.boundingCircle.y - gameQuery.boundingCircle.radius + offsetY > pgdGeom.bottom) ||
+                (gameQuery.boundingCircle.x - gameQuery.boundingCircle.radius + offsetX > pgdGeom.right)){
+                return this.pushStack(new $([]));
+            }
+
+            if(this !== $.gameQuery.playground){
+                // We must find all the elements that touche 'this'
+                var elementsToCheck = new Array();
+                elementsToCheck.push($.gameQuery.scenegraph.children(filter).get());
+                elementsToCheck[0].offsetX = 0;
+                elementsToCheck[0].offsetY = 0;
+
+                for(var i = 0, len = elementsToCheck.length; i < len; i++) {
+                    var subLen = elementsToCheck[i].length;
+                    while(subLen--){
+                        var elementToCheck = elementsToCheck[i][subLen];
+                        // Is it a gameQuery generated element?
+                        if(elementToCheck.gameQuery){
+                            // We don't want to check groups
+                            if(!elementToCheck.gameQuery.group && !elementToCheck.gameQuery.tileSet){
+                                // Does it touche the selection?
+                                if(this[0]!=elementToCheck){
+                                    // Check bounding circle collision
+                                    var distance = Math.sqrt(Math.pow(offsetY + gameQuery.boundingCircle.y - elementsToCheck[i].offsetY - elementToCheck.gameQuery.boundingCircle.y, 2) + Math.pow(offsetX + gameQuery.boundingCircle.x - elementsToCheck[i].offsetX - elementToCheck.gameQuery.boundingCircle.x, 2));
+                                    if(distance - gameQuery.boundingCircle.radius - elementToCheck.gameQuery.boundingCircle.radius <= 0){
+                                        // Check real collision
+                                        if(collide(gameQuery, {x: offsetX, y: offsetY}, elementToCheck.gameQuery, {x: elementsToCheck[i].offsetX, y: elementsToCheck[i].offsetY})) {
+                                            // Add to the result list if collision detected
+                                            resultList.push(elementsToCheck[i][subLen]);
+                                        }
+                                    }
+                                }
+                            }
+                            // Add the children nodes to the list
+                            var eleChildren = $(elementToCheck).children(filter);
+                            if(eleChildren.length){
+                                elementsToCheck.push(eleChildren.get());
+                                elementsToCheck[len].offsetX = elementToCheck.gameQuery.posx + elementsToCheck[i].offsetX;
+                                elementsToCheck[len].offsetY = elementToCheck.gameQuery.posy + elementsToCheck[i].offsetY;
+                                len++;
+                            }
+                        }
+                    }
+                }
+                return this.pushStack($(resultList));
+            }
+        },
+
+/** ---------------------------------------------------------------------------------------------------------------------------------------------------------------- **/
+/** --          Sound related functions           ------------------------------------------------------------------------------------------------------------------ **/
+/** ---------------------------------------------------------------------------------------------------------------------------------------------------------------- **/
+
+        /**
+         * Add the sound to the resourceManager for later use and
+         * associates it to the selected dom element(s).
+         * 
+         * This is a non-destructive call
+         */
+        addSound: function(sound, add) {
+            // Does a SoundWrapper exist?
+            if($.gameQuery.SoundWrapper) {
+                var gameQuery = this[0].gameQuery;
+                // Should we add to existing sounds?
+                if(add) {
+                    // Do we have some sound associated with 'this'?
+                    var sounds = gameQuery.sounds;
+                    if(sounds) {
+                        // Yes, we add it
+                        sounds.push(sound);
+                    } else {
+                        // No, we create a new sound array
+                        gameQuery.sounds = [sound];
+                    }
+                } else {
+                    // No, we replace all sounds with this one
+                    gameQuery.sounds = [sound];
+                }
+            }
+            return this;
+        },
+
+        /**
+         * Play the sound(s) associated with the selected dom element(s).
+         * 
+         * This is a non-destructive call.
+         */
+        playSound: function() {
+            $(this).each(function(){
+                var gameQuery = this.gameQuery;
+                if(gameQuery.sounds) {
+                    for(var i = gameQuery.sounds.length-1 ; i >= 0; i --) {
+                        gameQuery.sounds[i].play();
+                    }
+                }
+            });
+
+            return this;
+        },
+
+        /**
+         * Stops the sound(s) associated with the selected dom element(s) and rewinds them.
+         * 
+         * This is a non-destructive call.
+         */
+        stopSound: function() {
+            $(this).each(function(){
+                var gameQuery = this.gameQuery;
+                if(gameQuery.sounds) {
+                    for(var i = gameQuery.sounds.length-1 ; i >= 0; i --) {
+                        gameQuery.sounds[i].stop();
+                    }
+                }
+            });
+            return this;
+        },
+
+
+        /**
+         * Pauses the sound(s) associated with the selected dom element(s).
+         * 
+         * This is a non-destructive call.
+         */
+
+        pauseSound: function() {
+            $(this).each(function(){
+                var gameQuery = this.gameQuery;
+                if(gameQuery.sounds) {
+                    for(var i = gameQuery.sounds.length-1 ; i >= 0; i --) {
+                        gameQuery.sounds[i].pause();
+                    }
+                }
+            });
+            return this;
+        },
+
+
+        /**
+         * Mute or unmute the selected sound or all the sounds if none is specified.
+         * 
+         * This is a non-destructive call.
+         */
+
+        muteSound: function(muted) {
+            $(this).each(function(){
+                var gameQuery = this.gameQuery;
+                if(gameQuery.sounds) {
+                    for(var i = gameQuery.sounds.length-1 ; i >= 0; i --) {
+                        gameQuery.sounds[i].muted(muted);
+                    }
+                }
+            });
+            return this;
+        },
+
+
+/** ---------------------------------------------------------------------------------------------------------------------------------------------------------------- **/
+/** --          Transformation functions           ----------------------------------------------------------------------------------------------------------------- **/
+/** ---------------------------------------------------------------------------------------------------------------------------------------------------------------- **/
+
+        /**
+         * Internal function doing the combined actions of rotate and scale. 
+         * 
+         * Please use .rotate() or .scale() instead since they are part of the supported API!
+         * 
+         * This is a non-destructive call.
+         */
+        transform: function() {
+            var gameQuery = this[0].gameQuery;
+
+			if(cssTransform){
+				var transform = "translate("+gameQuery.posx+"px, "+gameQuery.posy+"px) rotate("+gameQuery.angle+"deg) scale("+(gameQuery.factor*gameQuery.factorh)+","+(gameQuery.factor*gameQuery.factorv)+")";
+				this.css(cssTransform,transform);
+			} else {
+				var angle_rad = Math.PI * 2 / 360 * gameQuery.angle;
+				// try filter for IE 
+				// For ie from 5.5
+                var cos = Math.cos(angle_rad) * gameQuery.factor;
+                var sin = Math.sin(angle_rad) * gameQuery.factor;
+                this.css("filter","progid:DXImageTransform.Microsoft.Matrix(M11="+(cos*gameQuery.factorh)+",M12="+(-sin*gameQuery.factorv)+",M21="+(sin*gameQuery.factorh)+",M22="+(cos*gameQuery.factorv)+",SizingMethod='auto expand',FilterType='nearest neighbor')");
+                var newWidth = this.width();
+                var newHeight = this.height();
+                gameQuery.posOffsetX = (newWidth-gameQuery.width)/2;
+                gameQuery.posOffsetY = (newHeight-gameQuery.height)/2;
+
+                this.css("left", ""+(gameQuery.posx-gameQuery.posOffsetX)+"px");
+                this.css("top", ""+(gameQuery.posy-gameQuery.posOffsetY)+"px");
+			}
+			
+            return this;
+        },
+
+        /**
+         * Rotate the element(s) clock-wise.
+         *
+         * @param {Number} angle the angle in degrees
+         * @param {Boolean} relative or not
+         *
+         * This is a non-destructive call when called with a parameter. Without parameter it IS a destructive call since the return value is the current rotation angle!
+         */
+        rotate: function(angle, relative){
+             var gameQuery = this[0].gameQuery;
+ 
+             if(angle !== undefined) {
+             	 if(relative === true){
+                    angle += gameQuery.angle;
+                    angle %= 360;
+            	 }
+                 $.gameQuery.update(gameQuery,{angle: angle});
+                 return this.transform();
+             } else {
+                 var ang = gameQuery.angle;
+                 return ang;
+             }
+        },
+
+        /**
+         * Change the scale of the selected element(s). The passed argument is a ratio:
+         *
+         * @param {Number} factor a ratio: 1.0 = original size, 0.5 = half the original size etc.
+         * @param {Boolean} relative or not
+         * 
+         * This is a non-destructive call when called with a parameter. Without parameter it IS a destructive call since the return value is the current scale factor!
+         */
+        scale: function(factor, relative){
+             var gameQuery = this[0].gameQuery;
+ 
+             if(factor !== undefined) {
+             	if(relative === true){
+                    factor *= gameQuery.factor;
+            	 }
+                 $.gameQuery.update(gameQuery,{factor: factor});
+                 return this.transform();
+             } else {
+                 var fac = gameQuery.factor;
+                 return fac;
+             }
+        },
+
+        /**
+         * Flips the element(s) horizontally.
+         * 
+         * This is a non-destructive call when called with a parameter. Without parameter it IS a destructive call since the return value is the current horizontal flipping status!
+         */
+        fliph: function(flip){
+            var gameQuery = this[0].gameQuery;
+
+            if (flip === undefined) {
+                return (gameQuery.factorh !== undefined) ? (gameQuery.factorh === -1) : false;
+            } else if (flip) {
+                gameQuery.factorh = -1;
+            } else {
+                gameQuery.factorh = 1;
+            }
+
+            return this.transform();
+        },
+
+        /**
+         * Flips the element(s) vertically.
+         * 
+         * This is a non-destructive call when called with a parameter. Without parameter it IS a destructive call since the return value is the current vertical flipping status!
+         */
+        flipv: function(flip){
+            var gameQuery = this[0].gameQuery;
+
+            if (flip === undefined) {
+                return (gameQuery.factorv !== undefined) ? (gameQuery.factorv === -1) : false;;
+            } else if (flip) {
+                gameQuery.factorv = -1;
+            } else {
+                gameQuery.factorv = 1;
+            }
+
+            return this.transform();
+        },
+
+/** ---------------------------------------------------------------------------------------------------------------------------------------------------------------- **/
+/** --          Position getter/setter functions           --------------------------------------------------------------------------------------------------------- **/
+/** ---------------------------------------------------------------------------------------------------------------------------------------------------------------- **/
+
+        /**
+         * Main function to change the sprite/group/tilemap position on screen.
+         * The three first agruments are the coordiate (double) and the last one is a flag
+         * to specify if the coordinate given are absolute or relative.
+         *
+         * If no argument is specified then the functions act as a getter and return a
+         * object {x,y,z}
+         *
+         * Please note that the z coordinate is just the z-index.
+         * 
+         * This is a non-destructive call when called with a parameter. Without parameter it IS a destructive call.
+         */
+        xyz: function(x, y, z, relative) {
+             if (x === undefined) {
+                 return this.getxyz();
+             } else {
+                 return this.setxyz({x: x, y: y, z: z}, relative);
+             }
+        },
+
+        /**
+         * The following functions are all all shortcuts for the .xyz(...) function.
+         *
+         * @see xyz for detailed documentation.
+         * 
+         * This is a non-destructive call when called with a parameter. Without parameter it IS a destructive call.
+         */
+        x: function(value, relative) {
+             if (value === undefined) {
+                 return this.getxyz().x;
+             } else {
+                 return this.setxyz({x: value}, relative);
+             }
+        },
+
+        y: function(value, relative) {
+             if (value === undefined) {
+                 return this.getxyz().y;
+             } else {
+                 return this.setxyz({y: value}, relative);
+             }
+        },
+
+        z: function(value, relative) {
+             if (value === undefined) {
+                 return this.getxyz().z;
+             } else {
+                 return this.setxyz({z: value}, relative);
+             }
+        },
+
+        xy: function(x, y, relative) {
+             if (x === undefined) {
+                 // we return the z too since it doesn't cost anything
+                 return this.getxyz();
+             } else {
+                 return this.setxyz({x: x, y: y}, relative);
+             }
+        },
+
+        /**
+         * Main function to change the sprite/group/tilemap dimension on screen.
+         * The two first arguments are the width and height (double) and the last one is a
+         * flag to specify if the dimensions given are absolute or relative.
+         *
+         * If no argument is specified then the functions act as a getter and
+         *
+         * return an object {w,h}
+         * 
+         * This is a non-destructive call when called with a parameter. Without parameter it IS a destructive call.
+         */
+        wh: function(w, h, relative) {
+            if (w === undefined) {
+                 return this.getwh();
+             } else {
+                 return this.setwh({w: w, h: h}, relative);
+             }
+        },
+
+        /**
+         * The following functions are all all shortcuts for the .wh(...) function.
+         *
+         * @see wh for detailed documentation.
+         * 
+         * This is a non-destructive call when called with a parameter. Without parameter it IS a destructive call.
+         */
+        w: function(value, relative) {
+            if (value === undefined) {
+                 return this.getwh().w;
+             } else {
+                 return this.setwh({w: value}, relative);
+             }
+        },
+
+        h: function(value, relative) {
+            if (value === undefined) {
+                 return this.getwh().h;
+             } else {
+                 return this.setwh({h: value}, relative);
+             }
+        },
+
+        /**
+         * The following four functions are 'private', and are not supposed to
+         * be used outside of the library.
+         * They are NOT part of the API and so are not guaranteed to remain unchanged.
+         * You should really use .xyz() and .wh() instead.
+         */
+        getxyz: function() {
+            var gameQuery = this[0].gameQuery;
+            return {x: gameQuery.posx, y: gameQuery.posy, z: gameQuery.posz};
+        },
+
+        setxyz: function(option, relative) {
+            var gameQuery = this[0].gameQuery;
+
+            for (coordinate in option) {
+                // Update the gameQuery object
+                switch (coordinate) {
+                    case 'x':
+                        if(relative) {
+                            option.x += gameQuery.posx;
+                        }
+                        gameQuery.posx = option.x;
+                        this.transform();
+                        
+                        //update the sub tile maps (if any), this forces to recompute which tiles are visible
+                        this.find("."+$.gameQuery.tilemapCssClass).each(function(){
+                            $(this).x(0, true);
+                        });
+                        break;
+
+                    case 'y':
+                        if(relative) {
+                            option.y += gameQuery.posy;
+                        }
+                        gameQuery.posy = option.y;
+                        this.transform();
+                        
+                        //update the sub tile maps (if any), this forces to recompute which tiles are visible
+                        this.find("."+$.gameQuery.tilemapCssClass).each(function(){
+                            $(this).y(0, true);
+                        });
+                        break;
+
+                    case 'z':
+                        if(relative) {
+                            option.z += gameQuery.posz;
+                        }
+                        gameQuery.posz = option.z;
+                        this.css("z-index",gameQuery.posz);
+                        break;
+                }
+            }
+            $.gameQuery.update(this, option);
+            return this;
+        },
+
+        getwh: function() {
+            var gameQuery = this[0].gameQuery;
+            return {w: gameQuery.width, h: gameQuery.height};
+        },
+
+        setwh: function(option, relative) {
+            var gameQuery = this[0].gameQuery;
+
+            for (coordinate in option) {
+                // Update the gameQuery object
+                switch (coordinate) {
+                    case 'w':
+                        if(relative) {
+                            option.w += gameQuery.width;
+                        }
+                        gameQuery.width = option.w;
+                        this.css("width","" + gameQuery.width + "px");
+                        break;
+
+                    case 'h':
+                        if(relative) {
+                            option.h += gameQuery.height;
+                        }
+                        gameQuery.height = option.h;
+                        this.css("height","" + gameQuery.height + "px");
+                        break;
+                }
+            }
+            $.gameQuery.update(this, option);
+            return this;
+        }
+    }); // end of the extensio of $.fn
+
+    // alias gameQuery to gQ for easier access
+    $.extend({ gQ: $.gameQuery}); 
+})(jQuery);

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1 - 0
lib/jquery-1.7.1.min.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 4 - 0
lib/jquery-ui-1.8.23.custom.min.js


이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.