Selaa lähdekoodia

Initial commit

ECAILLE Fabrice (externe) 8 vuotta sitten
commit
2e64cb961e
100 muutettua tiedostoa jossa 9885 lisäystä ja 0 poistoa
  1. 4 0
      demo/filebrowser-durandal-widget/.bowerrc
  2. 2 0
      demo/filebrowser-durandal-widget/.gitignore
  3. 7 0
      demo/filebrowser-durandal-widget/MIT-LICENCE.txt
  4. 19 0
      demo/filebrowser-durandal-widget/README.md
  5. 44 0
      demo/filebrowser-durandal-widget/app/main.js
  6. 34 0
      demo/filebrowser-durandal-widget/app/shell.html
  7. 26 0
      demo/filebrowser-durandal-widget/app/shell.js
  8. 31 0
      demo/filebrowser-durandal-widget/app/widgets/filebrowser/newItemModal.html
  9. 34 0
      demo/filebrowser-durandal-widget/app/widgets/filebrowser/newItemModal.js
  10. 18 0
      demo/filebrowser-durandal-widget/app/widgets/filebrowser/renameModal.html
  11. 34 0
      demo/filebrowser-durandal-widget/app/widgets/filebrowser/renameModal.js
  12. 56 0
      demo/filebrowser-durandal-widget/app/widgets/filebrowser/view.html
  13. 147 0
      demo/filebrowser-durandal-widget/app/widgets/filebrowser/viewmodel.js
  14. 15 0
      demo/filebrowser-durandal-widget/bower.json
  15. 154 0
      demo/filebrowser-durandal-widget/data/filesystem.json
  16. 45 0
      demo/filebrowser-durandal-widget/index.html
  17. 56 0
      demo/filebrowser-durandal-widget/style/filebrowser.less
  18. 9 0
      demo/filebrowser-durandal-widget/style/global.less
  19. 52 0
      demo/filebrowser-durandal-widget/style/starterkit.css
  20. 7 0
      demo/pacman/MIT-LICENCE.txt
  21. 31 0
      demo/pacman/README.md
  22. 10 0
      demo/pacman/css/hack/csshover3.htc
  23. 267 0
      demo/pacman/css/pacman.css
  24. BIN
      demo/pacman/img/banner.png
  25. BIN
      demo/pacman/img/font-red.png
  26. BIN
      demo/pacman/img/font-yellow.png
  27. BIN
      demo/pacman/img/font.png
  28. BIN
      demo/pacman/img/sprite.png
  29. 44 0
      demo/pacman/index.html
  30. 1009 0
      demo/pacman/js/pacman-core.js
  31. 11 0
      demo/pacman/js/pacman-data.js
  32. 255 0
      demo/pacman/js/pacman-ui.js
  33. 54 0
      demo/pacman/js/scoreboard.js
  34. 55 0
      demo/pacman/js/utils.js
  35. 1738 0
      demo/pacman/lib/gamequery-0.7.0.js
  36. 80 0
      demo/pacman/lib/gamequery-soundwrapper-soundmanager.js
  37. 1 0
      demo/pacman/lib/jquery-1.8.3.min.js
  38. 4 0
      demo/pacman/lib/jquery-ui-1.8.23.custom.min.js
  39. 106 0
      demo/pacman/lib/soundmanager2.min.js
  40. BIN
      demo/pacman/sound/dies.mp3
  41. BIN
      demo/pacman/sound/eatingfruit.mp3
  42. BIN
      demo/pacman/sound/eatingghost.mp3
  43. BIN
      demo/pacman/sound/opening.mp3
  44. BIN
      demo/pacman/sound/siren.mp3
  45. BIN
      demo/pacman/sound/wakawaka.mp3
  46. BIN
      demo/pacman/swf/soundmanager2.swf
  47. BIN
      demo/pacman/swf/soundmanager2_debug.swf
  48. BIN
      demo/pacman/swf/soundmanager2_flash9.swf
  49. BIN
      demo/pacman/swf/soundmanager2_flash9_debug.swf
  50. 7 0
      demo/pyramid/MIT-LICENCE.txt
  51. 23 0
      demo/pyramid/README.md
  52. 184 0
      demo/pyramid/css/cards.css
  53. BIN
      demo/pyramid/images/cards.png
  54. 35 0
      demo/pyramid/index.html
  55. 59 0
      demo/pyramid/js/cards.js
  56. 185 0
      demo/pyramid/js/pyramid.js
  57. 1 0
      demo/pyramid/lib/jquery-1.7.1.min.js
  58. 1 0
      demo/sis/.gitignore
  59. 7 0
      demo/sis/MIT-LICENCE.txt
  60. 63 0
      demo/sis/css/countdown.css
  61. 64 0
      demo/sis/css/scoreboard.css
  62. 129 0
      demo/sis/css/spaceinvaders.css
  63. BIN
      demo/sis/images/aliensprite.png
  64. BIN
      demo/sis/images/background.png
  65. BIN
      demo/sis/images/background2.png
  66. BIN
      demo/sis/images/explosion_big.png
  67. BIN
      demo/sis/images/explosion_small.png
  68. BIN
      demo/sis/images/farm.png
  69. BIN
      demo/sis/images/font.png
  70. BIN
      demo/sis/images/invader.png
  71. BIN
      demo/sis/images/ufo.png
  72. 60 0
      demo/sis/index.html
  73. 55 0
      demo/sis/js/animations/aliens.js
  74. 11 0
      demo/sis/js/animations/animations.js
  75. 127 0
      demo/sis/js/animations/explosions.js
  76. 131 0
      demo/sis/js/animations/worlds.js
  77. 94 0
      demo/sis/js/countdown.js
  78. 78 0
      demo/sis/js/lettering.js
  79. 91 0
      demo/sis/js/models/aliens.js
  80. 234 0
      demo/sis/js/models/models.js
  81. 183 0
      demo/sis/js/models/waves.js
  82. 140 0
      demo/sis/js/models/weapons.js
  83. 54 0
      demo/sis/js/scoreboard.js
  84. 318 0
      demo/sis/js/spaceinvaders-core.js
  85. 226 0
      demo/sis/js/spaceinvaders-ui.js
  86. 83 0
      demo/sis/js/tools.js
  87. 1849 0
      demo/sis/lib/gamequery-0.7.1.js
  88. 1 0
      demo/sis/lib/jquery-1.7.1.min.js
  89. 4 0
      demo/sis/lib/jquery-ui-1.8.23.custom.min.js
  90. 7 0
      demo/yahtzee/LICENSE.md
  91. 31 0
      demo/yahtzee/README.md
  92. 65 0
      demo/yahtzee/css/yahtzee.css
  93. BIN
      demo/yahtzee/images/dices.png
  94. BIN
      demo/yahtzee/images/red_dices.png
  95. 394 0
      demo/yahtzee/index.html
  96. 310 0
      demo/yahtzee/js/yahtzee.js
  97. 1 0
      demo/yahtzee/lib/jquery-1.7.1.min.js
  98. 53 0
      docs/development/cloudbudget.md
  99. 35 0
      docs/development/cloudbudget_angularjs.md
  100. 33 0
      docs/development/filebrowser_durandal_widget.md

+ 4 - 0
demo/filebrowser-durandal-widget/.bowerrc

@@ -0,0 +1,4 @@
+{
+	"directory" : "lib",
+	"json" : "bower.json"
+}

+ 2 - 0
demo/filebrowser-durandal-widget/.gitignore

@@ -0,0 +1,2 @@
+lib
+.codenvy

+ 7 - 0
demo/filebrowser-durandal-widget/MIT-LICENCE.txt

@@ -0,0 +1,7 @@
+Copyright (c) 2015 Fabrice ECAILLE aka Febbweiss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 19 - 0
demo/filebrowser-durandal-widget/README.md

@@ -0,0 +1,19 @@
+# Durandal Filebrowser widget
+
+## What's this widget ?
+
+This [Durandal](http://durandaljs.com/) widget allows to display a folder tree and add some actions to manipulate this items :
+* A clic on a file or folder selects it
+* A double-clic opens / closes a folder
+* A double-clic displays the file content in the editor
+* A right-clic opens a context menu with different options :
+ * Rename the item
+ * Copy the selected item(s)
+ * Paste the selected item(s)
+ * Create an item (in a folder)
+ * Delete an item (and its components)
+	
+   
+#Licence
+
+Source code is under [MIT Licence](http://opensource.org/licenses/mit-license.php) 

+ 44 - 0
demo/filebrowser-durandal-widget/app/main.js

@@ -0,0 +1,44 @@
+requirejs.config({
+	paths: {
+		'text'						: '../lib/requirejs-text/text',
+		'durandal'					: '../lib/durandal/js',
+		'plugins'					: '../lib/durandal/js/plugins',
+		'transitions'				: '../lib/durandal/js/transitions',
+		'knockout'					: '../lib/knockout.js/knockout',
+		'knockout.mapping'		: '../lib/bower-knockout-mapping/dist/knockout.mapping.min',
+		'knockout.validation'	: '../lib/knockout-validation/dist/knockout.validation.min',
+		'jquery'						: '../lib/jquery/jquery.min',
+		'perfect.scrollbar'		: '../lib/perfect-scrollbar/js/perfect-scrollbar.jquery',
+		'highlightjs'				: '../lib/highlightjs/highlight.pack'
+	},
+	shim: {
+		'knockout.mapping': {
+			deps: ['knockout'],
+			exports: 'knockout.mapping'
+		},
+		'knockout.validation': {
+			deps: ['knockout'],
+			exports: 'knockout.validation'
+		}
+	}
+});
+
+define(['durandal/system', 'durandal/app'], function (system, app) {
+	system.debug(true);
+
+	app.title = 'File browser Durandal Widget';
+
+	app.configurePlugins({
+		router	: true,
+		dialog	: true,
+		widget	: {
+			kinds: [
+				'filebrowser'
+			]
+		}
+	});
+
+	app.start().then(function() {
+		app.setRoot('shell');
+	});
+});

+ 34 - 0
demo/filebrowser-durandal-widget/app/shell.html

@@ -0,0 +1,34 @@
+<div class="row-fluid">
+	<aside class="col-sm-2 col-md-2">
+		<div data-bind="widget: {kind:'filebrowser'}"></div>
+	</aside>
+	
+	<main class="col-sm-10 col-md-6" id="main">
+		<pre data-bind="visible: content">
+			<code data-bind="attr: {css: type}, text: content" id="editor"></code>
+		</pre>
+		<span class="text-center" data-bind="visible: !content()">Open file to view it.</span>
+	</main> 
+	
+	<aside class="col-sd-12 col-md-4" id="helpPane">
+		<h2>Welcome to the filebrowser Durandal widget demo</h2>
+		<p>
+			This <a href="http://durandaljs.com/" target="_blank">Durandal</a> widget allows to display a folder tree and add some actions to manipulate this items.
+			<ul>
+				<li>A clic on a file or folder selects it.
+				<li>A double-clic opens / closes a folder</li>
+				<li>A double-clic displays the file content in the editor</li>
+				<li>
+					A right-clic opens a context menu with different options :
+					<ul>
+						<li>Rename the item</li>
+						<li>Copy the selected item(s)</li>
+						<li>Paste the selected item(s)</li>
+						<li>Create an item (in a folder)</li>
+						<li>Delete an item (and its components)</li>
+					</ul>
+				</li>
+			</ul>
+		</p>
+	</aside>
+</div>

+ 26 - 0
demo/filebrowser-durandal-widget/app/shell.js

@@ -0,0 +1,26 @@
+define(['durandal/app', 'knockout', 'highlightjs'], function (app, ko) {
+	var type = ko.observable(),
+		content = ko.observable();
+	
+	var sub = app.on('filebrowser:open_file').then(function(message) {
+		type(message.type);
+		if( message.type === "json" ) {
+			content(ko.utils.stringifyJson(message.content));
+		} else {
+        	content(message.content);
+        }
+        hljs.highlightBlock($('#editor')[0]);
+    }, this);
+
+
+	return {
+		attached: function () {
+			hljs.configure({
+			  tabReplace: '  '
+			});
+			hljs.initHighlighting();
+		},
+		type: type,
+		content: content
+	};
+});

+ 31 - 0
demo/filebrowser-durandal-widget/app/widgets/filebrowser/newItemModal.html

@@ -0,0 +1,31 @@
+<div class="modal-content">
+    <div class="modal-header">
+        <button type="button" class="close" data-dismiss="modal" aria-label="Close" data-bind="click: close"><span aria-hidden="true">&times;</span></button>
+        <h4 class="modal-title">New item</h4>
+    </div>
+    <div class="modal-body">
+        <form data-bind="submit: ok" class="form-horizontal">
+             <div class="row form-group">
+                <label class="col-sm-2">Type</label>
+                <div class="col-sm-10">
+                    <label class="radio-inline">
+                      <input type="radio" name="typeItem" id="typeItemFile" value="file" data-bind="checked: typeItem"> File
+                    </label>
+                    <label class="radio-inline">
+                      <input type="radio" name="typeItem" id="typeItemFolder" value="folder" data-bind="checked: typeItem"> Folder
+                    </label>
+                </div>
+              </div>
+             <div class="row form-group">
+                <label for="nameInput" class="col-sm-2 control-label">Name</label>
+                <div class="col-sm-10">
+                    <input data-bind="value: input, valueUpdate: 'afterkeydown'" name="nameInput" class="form-control autofocus"/>
+                </div>
+              </div>
+        </form>
+    </div>
+    <div class="modal-footer">
+        <button class="btn btn-primary" data-bind="click: ok, visible: isValid">Create</button>
+        <button class="btn btn-default" data-bind="click: close">Cancel</button>
+    </div>
+</div>

+ 34 - 0
demo/filebrowser-durandal-widget/app/widgets/filebrowser/newItemModal.js

@@ -0,0 +1,34 @@
+define(['plugins/dialog', 'knockout', 'knockout.validation'], function (dialog, ko, ko_validation) {
+
+    ko.validation = ko_validation;
+
+    var NewItemModal = function() {
+        var self = this;
+        self.input = ko.observable('').extend({
+            required: true,
+            pattern: {
+                message : 'The name must not contain a \'/\'',
+                params  : '^[^/]+$'
+            }
+        });
+        self.typeItem = ko.observable('file');
+        self.form = ko.validatedObservable( {input: self.input} );
+        self.isValid = ko.computed(function() {
+            return self.form.isValid();
+        });
+    };
+
+    NewItemModal.prototype.ok = function() {
+        dialog.close(this, { name: this.input(), type: this.typeItem()});
+    };
+
+    NewItemModal.prototype.close = function() {
+        dialog.close(this);
+    };
+
+    NewItemModal.show = function(defaultValue){
+        return dialog.show(new NewItemModal(defaultValue));
+    };
+
+    return NewItemModal;
+});

+ 18 - 0
demo/filebrowser-durandal-widget/app/widgets/filebrowser/renameModal.html

@@ -0,0 +1,18 @@
+<div class="modal-content">
+    <div class="modal-header">
+        <button type="button" class="close" data-dismiss="modal" aria-label="Close" data-bind="click: close"><span aria-hidden="true">&times;</span></button>
+        <h4 class="modal-title">Rename</h4>
+    </div>
+    <div class="modal-body">
+        <form data-bind="submit: ok" class="form-inline">
+             <div class="form-group">
+                <label for="renameInput">New name</label>
+                <input data-bind="value: input, valueUpdate: 'afterkeydown'" name="renameInput" class="form-control autofocus"/>
+              </div>
+        </form>
+    </div>
+    <div class="modal-footer">
+        <button class="btn btn-primary" data-bind="click: ok, visible: isValid">Ok</button>
+        <button class="btn btn-default" data-bind="click: close">Cancel</button>
+    </div>
+</div>

+ 34 - 0
demo/filebrowser-durandal-widget/app/widgets/filebrowser/renameModal.js

@@ -0,0 +1,34 @@
+define(['plugins/dialog', 'knockout', , 'knockout.validation'], function (dialog, ko, ko_validation) {
+
+    ko.validation = ko_validation;
+
+    var RenameModal = function(defaultValue) {
+        var self = this;
+        self.previousName = defaultValue;
+        self.input = ko.observable(defaultValue).extend({
+            required: true,
+            pattern: {
+                message : 'The name must not contain a \'/\'',
+                params  : '^[^/]+$'
+            }
+        });
+        self.form = ko.validatedObservable( {input: self.input} );
+        self.isValid = ko.computed(function() {
+            return self.form.isValid() && self.input() != self.previousName;
+        });
+    };
+
+    RenameModal.prototype.ok = function() {
+        dialog.close(this, this.input());
+    };
+
+    RenameModal.prototype.close = function() {
+        dialog.close(this);
+    };
+
+    RenameModal.show = function(defaultValue){
+        return dialog.show(new RenameModal(defaultValue));
+    };
+
+    return RenameModal;
+});

+ 56 - 0
demo/filebrowser-durandal-widget/app/widgets/filebrowser/view.html

@@ -0,0 +1,56 @@
+<div id="filebrowser">
+  <!-- ko if: folder() -->
+		<!-- ko let: { loopRoot: $data } -->
+			<ul data-bind="template: { name: 'tree-template', foreach: folder().children() }" class="tree-file"></ul>
+		<!-- /ko -->
+	<!-- /ko -->
+
+	<script id="tree-template" type="text/html">
+		<!-- ko if: $data.type() === "folder" -->
+			<li class="folder">
+				<span data-bind="event: { contextmenu: loopRoot.openContextMenu, dblclick: loopRoot.open, click: loopRoot.select }">
+					<i class="fa fa-folder-o" data-bind="attr: {id: 'icon_folder_' + $data.uuid()}"></i>
+					<!-- ko text: $data.name --><!-- /ko --> 
+				</span>
+				<input type="checkbox" data-bind="attr: {id: $data.uuid}" />
+				<ul data-bind="template: { name: 'tree-template', foreach: $data.children }"></ul>
+			</li>
+		<!-- /ko -->
+		<!--ko if: $data.type() !== "folder"-->
+			<li data-bind="attr: {'data-id': $data.uuid, 'data-filetype': $data.type()}, 
+         					event: { contextmenu: loopRoot.openContextMenu, dblclick: loopRoot.open, click: loopRoot.select }" 
+         	 class="file">
+				<!--ko ifnot: $data.type -->
+					<i data-bind="attr: {class: 'fa fa-file-o'}"></i>
+				<!-- /ko -->
+				<!--ko if: $data.type -->
+					<i data-bind="attr: {class: 'fa fa-file-' + $data.type() + '-o', title: $data.type()}"></i>
+				<!-- /ko -->
+				<span data-bind="text: $data.name, attr: {'data-extra': $data.extra ? $data.extra : ''}"></span>
+			</li>
+		<!-- /ko -->
+	</script>    
+	
+	<!-- Context menu -->
+	<div id="fileBrowserContextMenu" class="dropdown open" data-bind="visible: showContextMenu" tabindex="1">
+		<ul class="dropdown-menu" role="menu" aria-labelledby="contextMenu">
+			<li data-bind="visible: hasSelectedFolder()" >
+				<a role="menuitem" tabindex="-1" href="#" data-bind="click: newItem">New ...</a>
+			</li>
+			<li>
+				<a role="menuitem" tabindex="-1" href="#" data-bind="click: openRenamePopup">Rename</a>
+			</li>
+			<li>
+				<a role="menuitem" tabindex="-1" href="#" data-bind="click: copy">Copy</a>
+			</li>
+			<li data-bind="css: { 'disabled': !hasCopied()}, visible: hasSelectedFolder()">
+				<a role="menuitem" tabindex="-1" href="#" data-bind="click: paste">Paste</a>
+			</li>
+			<li>
+				<a role="menuitem" tabindex="-1" href="#" data-bind="click: openDeletePopup">Delete</a>
+			</li>
+		</ul>
+	</div>
+  
+	<!-- End of Context menu -->
+</div>

+ 147 - 0
demo/filebrowser-durandal-widget/app/widgets/filebrowser/viewmodel.js

@@ -0,0 +1,147 @@
+define(['durandal/app', 'durandal/composition', 'plugins/http',
+        'jquery', 'knockout', 'knockout.mapping',
+        'perfect.scrollbar',
+        './renameModal', './newItemModal'],
+        function(app, composition,http, $, ko, ko_mapping, perfectScrollbar, RenameModal, NewItemModal) {
+
+	ko.mapping = ko_mapping;
+	
+	ko.bindingHandlers['let'] = {
+	    'init': function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
+	        // Make a modified binding context, with extra properties, and apply it to descendant elements
+	        var innerContext = bindingContext.extend(valueAccessor());
+	        ko.applyBindingsToDescendants(innerContext, element);
+	
+	        return { controlsDescendantBindings: true };
+	    }
+	};
+	ko.virtualElements.allowedBindings['let'] = true;
+    
+    var ctor = function() {},
+        selected = ko.observable(),
+        showContextMenu = ko.observable(),
+        copied = ko.observable(undefined),
+        folder = ko.observable(ko.mapping.fromJS({children: []})),
+        scrollable = $('#filebrowser');
+
+	ctor.prototype.attached = function() {
+     showContextMenu(false);
+		$('#filebrowser').perfectScrollbar();
+	};
+	
+    ctor.prototype.activate = function(settings) {
+        this.settings = settings;
+		this.selected = selected;
+		this.folder = folder;
+		this.showContextMenu = showContextMenu;
+      this.hasSelectedFolder = ko.computed(function() {
+        return selected() != undefined && selected().type === 'folder';
+      });
+      this.hasCopied = ko.computed( function() {
+        return copied() !== undefined;
+      });
+    };
+
+    ctor.prototype.open = function(object,event) {
+    	console.log('Opening', object);
+    	if( object.type() === 'folder' ) {
+	        var id = object.uuid(),
+	        	checkbox = $('input[type=checkbox][id=' + id + ']');
+	        	
+	        checkbox.prop('checked', !checkbox.prop('checked'));
+	        $('#icon_folder_' + id).toggleClass('fa-folder-o').toggleClass('fa-folder-open-o');
+	        
+	        $('#filebrowser').perfectScrollbar('update');
+		} else {
+ 			var type = object.extra();
+	        http.get(object.path()).then(function(response) {
+		        app.trigger('filebrowser:open_file', {
+		        	type: type,
+		        	content: response
+		        });
+		   });
+		}
+    };
+    
+    ctor.prototype.select = function(object, event) {
+        $('li > span.select').removeClass('select');
+        $(event.target).toggleClass('select');
+        selected( ko.mapping.fromJS(object) );
+    };
+    
+    /** Context Menu **/
+
+    ctor.prototype.openContextMenu = function(object, event) {
+        // Position du menu, calculer la pos pour eviter sortie du viewport
+        var posX = event.pageX,
+            posY = event.pageY,
+            windowWidth = $(window).width(),
+            windowHeight = $(window).height(),
+            contextMenu = $('#fileBrowserContextMenu'),
+            menuWidth = contextMenu.width(),
+            menuHeight = contextMenu.height();
+
+			posX = Math.min(posX, windowWidth - menuWidth - 15);
+			posY = Math.min(posY, windowHeight - menuHeight - 10);
+
+        // affichage
+        contextMenu.css({
+            left : posX + 'px',
+            top : posY + 'px'
+        });
+
+      
+       showContextMenu(true);
+    };
+	
+    ctor.prototype.openRenamePopup = function(ctor, event) {
+        RenameModal.show(ctor.selected().name()).then(function(response) {
+            if( response !== undefined ) {
+                ctor.selected().name(response);
+            }
+        });
+    };
+
+    ctor.prototype.openDeletePopup = function(ctor, event) {
+        app.showMessage(
+            'Are you sure you want to delete this ' +
+                (ctor.selected().is_folder ? ' folder and all its content' : 'file') + '?',
+            'Delete ' + ctor.selected().name(), [ { text: "Yes", value: "yes" }, { text: "No", value: "no" }]).then( function( dialogResult ) {
+                if( dialogResult === 'yes' ) {
+                    console.log('Deleting', ctor.selected().name());
+                }
+            });
+    };
+
+    ctor.prototype.copy = function(ctor, event) {
+        console.log('Copied', ctor.selected().name());
+        copied( ctor.selected() );
+    };
+
+    ctor.prototype.paste = function(ctor, event) {
+        if( copied() !== undefined ) {
+            console.log('Paste', copied().name(), 'into', ctor.selected().name());
+            copied( undefined );
+        }
+    };
+
+    ctor.prototype.newItem = function(ctor, event) {
+        NewItemModal.show().then(function(response) {
+            if( response !== undefined ) {
+                console.log('New item : ' + response.type + ' - ' + response.name );
+            }
+        });
+    };
+
+   $(document).on('click', function() {
+        showContextMenu(false);
+    });
+    /** End of Context Menu */
+
+  	http.get('/data/filesystem.json').then(function(response) {
+        folder(ko.mapping.fromJS(response));
+        $('#filebrowser').perfectScrollbar('update');
+   });
+
+    return ctor;
+});

+ 15 - 0
demo/filebrowser-durandal-widget/bower.json

@@ -0,0 +1,15 @@
+{
+  "name": "filebrowser-durandal-widget",
+  "version": "0.0.1-SNAPSHOT",
+  "dependencies" : {
+    "durandal" 					: "~2.1.0",
+    "knockout-validation"		: "~2.0.2",
+    "bower-knockout-mapping"	: "~2.5.0",
+    "perfect-scrollbar"			: "~0.6.0",
+    "bootstrap"					: "~3.3.4",
+    "fontawesome"					: "~4.3.0",
+    "less.js"						: "~2.4.0",
+    "lesshat"						: "~3.0.2",
+    "highlightjs"					: "~8.4.0"
+  }
+}

+ 154 - 0
demo/filebrowser-durandal-widget/data/filesystem.json

@@ -0,0 +1,154 @@
+{
+   "uuid":"workspace",
+   "name":"workspace",
+   "type":"folder",
+   "children":[
+      {
+         "uuid":"app",
+         "name":"app",
+         "type":"folder",
+         "children":[
+		      {
+		         "uuid":"widgets",
+		         "name":"widgets",
+		         "type":"folder",
+		         "children":[
+		         	{
+		         		"uuid":"filebrowser",
+		         		"name":"filebrowser",
+		         		"type":"folder",
+		         		"children": [
+			         		{
+				               "name":"newItemModal.html",
+				               "uuid":"newItemModalhtml",
+				               "type":"code",
+				               "extra":"html",
+				               "path": "/app/widgets/filebrowser/newItemModal.html"
+				            },
+			         		{
+				               "name":"newItemModal.js",
+				               "uuid":"newItemModaljs",
+				               "type":"code",
+				               "extra":"javascript",
+				               "path": "/app/widgets/filebrowser/newItemModal.js"
+				            },
+			         		{
+				               "name":"renameModal.html",
+				               "uuid":"renameModalhtml",
+				               "type":"code",
+				               "extra":"html",
+				               "path": "/app/widgets/filebrowser/renameModal.html"
+				            },
+			         		{
+				               "name":"renameModal.js",
+				               "uuid":"renameModaljs",
+				               "type":"code",
+				               "extra":"javascript",
+				               "path": "/app/widgets/filebrowser/renameModal.js"
+				            },
+			         		{
+				               "name":"view.html",
+				               "uuid":"viewhtml",
+				               "type":"code",
+				               "extra":"html",
+				               "path": "/app/widgets/filebrowser/view.html"
+				            },
+			         		{
+				               "name":"viewmodel.js",
+				               "uuid":"viewmodeljs",
+				               "type":"code",
+				               "extra":"javascript",
+				               "path": "/app/widgets/filebrowser/viewmodel.js"
+				            }
+		         		]
+		         	}
+		         ]
+		      },
+		      {
+	               "name":"main.js",
+	               "uuid":"mainjs",
+	               "type":"code",
+	               "extra":"javascript",
+				   "path": "/app/main.js"
+	            },
+	            {
+	               "name":"shell.html",
+	               "uuid":"shellhtml",
+	               "type":"code",
+	               "extra":"html",
+				   "path": "/app/shell.html"
+	            },
+	            {
+	               "name":"shell.js",
+	               "uuid":"shelljs",
+	               "type":"code",
+	               "extra":"javascript",
+				   "path": "/app/shell.js"
+	            }
+         ]
+      },
+      {
+         "uuid":"data",
+         "name":"data",
+         "type":"folder",
+         "children":[
+            {
+               "name":"filesystem.json",
+               "uuid":"filesystemjson",
+               "type":"code",
+               "extra":"json",
+				"path": "/data/filesystem.json"
+            }
+         ]
+      },
+      {
+      	"uuid": "style",
+      	"name": "style",
+      	"type": "folder",
+      	"children": [
+      		{
+      			"name": "filebrowser.less",
+      			"uuid": "filebrowserless",
+      			"type": "code",
+      			"extra": "less",
+				"path": "/style/filebrowser.less"
+      		},
+      		{
+      			"name": "global.less",
+      			"uuid": "globalless",
+      			"type": "code",
+      			"extra": "less",
+				"path": "/style/global.less"
+      		},
+      		{
+      			"name": "starterkit.css",
+      			"uuid": "starterkitcss",
+      			"type": "code",
+      			"extra": "less",
+				"path": "/style/starterkit.css"
+      		}
+      	]
+      },
+      {
+	      "uuid": "bowerrc",
+	      "name": ".bowerrc",
+	      "type": "code",
+	      "extra": "json",
+	      "path": "/.bowerrc"
+	  },
+      {
+	      "uuid": "bowerjson",
+	      "name": "bower.json",
+	      "type": "code",
+	      "extra": "json",
+	      "path": "/bower.json"
+	  },
+      {
+	      "uuid": "indexhtml",
+	      "name": "index.html",
+	      "type": "code",
+	      "extra": "html",
+	      "path": "/index.html"
+	  }
+   ]
+}

+ 45 - 0
demo/filebrowser-durandal-widget/index.html

@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.css"/>
+		<link rel="stylesheet" href="lib/fontawesome/css/font-awesome.css"/>
+		<link rel="stylesheet" href="lib/durandal/css/durandal.css"/>
+		<link rel="stylesheet" href="lib/perfect-scrollbar/css/perfect-scrollbar.css" />
+		<link rel="stylesheet" href="lib/highlightjs/styles/github.css" />
+		<link rel="stylesheet" href="style/starterkit.css" />
+		<link rel="stylesheet/less" href="style/global.less" />
+		<link rel="stylesheet/less" href="style/filebrowser.less" />
+	 
+		<script>
+			less = {
+				env: "development"
+			};
+		</script>
+		<script src="lib/less.js/dist/less.min.js"></script>
+	</head>
+	<body>
+		<nav class="navbar navbar-default">
+			<div class="container-fluid">
+				<div class="navbar-header">
+					<a class="navbar-brand" href="#">Filebrowser Durandal Widget</a>             	
+				</div>
+			</div>
+		</nav>
+		
+		<div id="applicationHost">
+			<div class="splash">
+				<h1 class="message">Filebrowser Durandal widget</h1>
+				<i class="fa fa-spinner fa-2x fa-spin active"></i>
+			</div>
+		</div>
+		
+		<nav class="navbar navbar-default navbar-fixed-bottom">
+			<div class="container">
+				Footer
+			</div>
+		</nav>
+		
+		<script src="lib/requirejs/require.js" data-main="app/main"></script>
+		
+	</body>
+</html>

+ 56 - 0
demo/filebrowser-durandal-widget/style/filebrowser.less

@@ -0,0 +1,56 @@
+@import "../lib/lesshat/build/lesshat.less";
+
+#filebrowser {
+	max-height: 500px;
+	
+	position: relative;
+	
+	overflow: hidden;
+	white-space: nowrap;
+}
+
+.folder {
+	
+	& > label {
+		cursor: pointer;
+	}
+
+	& input[type=checkbox] {
+		opacity: 0;
+
+		& + ul > li {
+			display: none;
+		}
+
+		&:checked + ul > li {
+			display: block;
+		}
+	}
+	
+} 
+
+ul {
+	&.tree-file {
+		list-style-type: none;
+		padding-left: 0;
+	}
+};
+
+.folder > span,
+.file > span {
+
+	.user-select(none);
+
+	&.select {
+		font-weight: bold;	
+	}
+};
+
+#fileBrowserContextMenu {
+	position: fixed;
+  z-index: 9999;
+}
+
+.validationMessage {
+	color: red;
+}

+ 9 - 0
demo/filebrowser-durandal-widget/style/global.less

@@ -0,0 +1,9 @@
+body { 
+	padding-bottom: 100px; 
+}
+
+#editor {
+	position: relative;
+	
+	overflow: hidden;
+}

+ 52 - 0
demo/filebrowser-durandal-widget/style/starterkit.css

@@ -0,0 +1,52 @@
+/*!
+ * Durandal 2.1.0 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved.
+ * Available via the MIT license.
+ * see: http://durandaljs.com or https://github.com/BlueSpire/Durandal for details
+ */
+
+.splash {
+    text-align: center;
+    margin: 10% 0 0 0;
+}
+
+.splash .message {
+    font-size: 3em;
+    line-height: 1.5em;
+    -webkit-text-shadow: rgba(0, 0, 0, 0.5) 0 0 15px;
+    text-shadow: rgba(0, 0, 0, 0.5) 0 0 15px;
+    text-transform: uppercase;
+}
+
+.splash .fa-spinner {
+    text-align: center;
+    display: inline-block;
+    font-size: 3em;
+    margin-top: 50px;
+}
+
+.page-host {
+    position: absolute;
+    left: 0;
+    right: 0;
+    top: 50px;
+    bottom: 0;
+    overflow-x: hidden;
+    overflow-y: auto;
+}
+
+section {
+    margin: 0 20px;
+}
+
+.navbar-nav li.loader {
+    margin: 12px 6px 0 6px;
+    visibility: hidden;
+}
+
+.navbar-nav li.loader.active {
+    visibility: visible;
+}
+
+.pictureDetail {
+    max-width: 425px;
+}

+ 7 - 0
demo/pacman/MIT-LICENCE.txt

@@ -0,0 +1,7 @@
+Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 31 - 0
demo/pacman/README.md

@@ -0,0 +1,31 @@
+gq-pacman is a jQuery implementation of the famous Namco's Pacman.
+
+
+How to :
+--------
+
+Use your keyboard arrows to move Pacman to eat all energizer to complete the level.
+Ghosts become frightened when Pacman eats a big energizer. Eat them !!!
+                                        
+Beware of ghost who haunt the maze !!! Each one has his own personality :
+ * Blinky tracks Pacman as his shadow.
+ * Pinky perfoms ambushes to Pacman.
+ * Inky is the least predictable.
+ * Clyde pretends ignorance and is one who lags behind.
+
+A live demo is avalaible [here](http://www.pavnay.fr/gq-pacman/)
+
+Credits :
+---------
+
+* Graphics : Fabrice Ecaille aka Febbweiss
+* Code : Fabrice Ecaille aka Febbweiss
+* Algorithm : Based on the ["Pacman Dossier"](http://home.comcast.net/~jpittman2/pacman/pacmandossier.html)
+* Tools : [gameQuery](http://gamequeryjs.com/)
+* Sounds : [Sound FX Center](http://soundfxcenter.com/sound_effect/search.php?sfx=Pacman)
+
+Licences :
+----------
+
+Source code is under [MIT Licence](http://opensource.org/licenses/mit-license.php)
+Sprite is under [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/legalcode)

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 10 - 0
demo/pacman/css/hack/csshover3.htc


+ 267 - 0
demo/pacman/css/pacman.css

@@ -0,0 +1,267 @@
+#playgroundContainer {
+	width: 448px; 
+	height: 512px; 
+	background-color: black; 
+	margin-left: auto; 
+	margin-right: auto;
+}
+
+.actor {
+	width: 32px;
+	height: 32px;
+	position: relative;
+	margin-top: -16px;
+	margin-left: -16px;
+}
+
+/** TILES **/
+
+.tile {
+	width: 16px;
+	height: 16px;
+	float: left;
+	margin: 0px;
+	padding: 0px;
+}
+
+.corner1 {
+	background-image: url('../img/sprite.png');
+	background-position: -0px -0px;
+}
+
+.corner2 {
+	background-image: url('../img/sprite.png');
+	background-position: -16px -0px;
+}
+
+.corner3 {
+	background-image: url('../img/sprite.png');
+	background-position: -32px 0px;
+}
+
+.corner4 {
+	background-image: url('../img/sprite.png');
+	background-position: -48px 0px;
+}
+
+.horizontalMidUp {
+	background-image: url('../img/sprite.png');
+	background-position: -96px -0px;
+}
+
+.horizontalMidDown {
+	background-image: url('../img/sprite.png');
+	background-position: -112px -0px;
+}
+
+.verticalMidLeft {
+	background-image: url('../img/sprite.png');
+	background-position: -64px -0px;
+}
+
+.verticalMidRight {
+	background-image: url('../img/sprite.png');
+	background-position: -80px -0px;
+}
+
+.squareCornerTopLeft {
+	background-image: url('../img/sprite.png');
+	background-position: -128px -0px;
+}
+
+.squareCornerTopRight {
+	background-image: url('../img/sprite.png');
+	background-position: -144px -0px;
+}
+
+.squareCornerBottomLeft {
+	background-image: url('../img/sprite.png');
+	background-position: -160px -0px;
+}
+
+.squareCornerBottomRight {
+	background-image: url('../img/sprite.png');
+	background-position: -176px -0px;
+}
+
+.gate {
+	background-image: url('../img/sprite.png');
+	background-position: -192px -0px;
+}
+
+.dot {
+	background-image: url('../img/sprite.png');
+	background-position: -208px -0px;
+}
+
+.bigDot {
+	background-image: url('../img/sprite.png');
+	background-position: -208px -16px;
+}
+
+.ghost {
+	width: 32px;
+	height: 32px;
+	float: left;	
+}
+
+.blinky {
+	background-image: url('../img/sprite.png');
+	background-position: -160px -48px;
+}
+.pinky {
+	background-image: url('../img/sprite.png');
+	background-position: -160px -80px;
+}
+.inky {
+	background-image: url('../img/sprite.png');
+	background-position: -160px -112px;
+}
+.clyde {
+	background-image: url('../img/sprite.png');
+	background-position: -160px -144px;
+}
+
+.cherries {
+	background-image: url('../img/sprite.png');
+	background-position: -208px -32px;
+}
+
+.strawberry {
+	background-image: url('../img/sprite.png');
+	background-position: -208px -48px;
+}
+
+.peach {
+	background-image: url('../img/sprite.png');
+	background-position: -208px -64px;
+}
+
+.apple {
+	background-image: url('../img/sprite.png');
+	background-position: -208px -80px;
+}
+
+.grapes {
+	background-image: url('../img/sprite.png');
+	background-position: -208px -96px;
+}
+
+.galaxian {
+	background-image: url('../img/sprite.png');
+	background-position: -208px -112px;
+}
+
+.bell {
+	background-image: url('../img/sprite.png');
+	background-position: -208px -128px;
+}
+
+.key {
+	background-image: url('../img/sprite.png');
+	background-position: -208px -144px;
+}
+
+.description {
+	padding-left: 40px;
+	line-height: 3em;
+	vertical-align:  middle;
+}
+/** HUD **/
+
+#message {
+	position : absolute;
+	left : 50%;
+	top : 50%;
+}
+
+#level {
+	position : relative;
+	top: -10px;
+	left: 5px;
+}
+
+#levelNumber {
+	position : relative;
+	top: -14px;
+	float: left;
+	margin-left: 100px;
+}
+
+#lives {
+	position: absolute;
+	top: -1px;
+	right: 10px;
+}
+
+#scoreMessage {
+	position: absolute;
+	right: 0px;
+}
+
+.life {
+	width: 32px;
+	height: 32px;
+	float: left;
+	background-image: url('../img/sprite.png');
+	background-position: 0px -16px;
+}
+
+/** SCOREBOARD**/
+
+.clock { 
+	background : transparent url("../img/font.png") no-repeat top left; 
+	height:32px; 
+	width:32px; 
+	float:left;
+}
+
+.clock.red {
+	background : transparent url("../img/font-red.png") no-repeat top left; 
+}
+
+.clock.yellow {
+	background : transparent url("../img/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;
+}
+
+.hiddenDot {
+	visibility: hidden;
+}

BIN
demo/pacman/img/banner.png


BIN
demo/pacman/img/font-red.png


BIN
demo/pacman/img/font-yellow.png


BIN
demo/pacman/img/font.png


BIN
demo/pacman/img/sprite.png


+ 44 - 0
demo/pacman/index.html

@@ -0,0 +1,44 @@
+
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Pacman</title>
+        <!--[if IE]>
+        <style type="text/css" media="screen">
+        	body {
+ 				behavior: url("css/hack/csshover3.htc");
+			}
+        </style>
+        <![endif]-->
+	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+		<script src="lib/jquery-1.8.3.min.js" type="text/javascript" ></script>
+        <script src="lib/jquery-ui-1.8.23.custom.min.js" type="text/javascript" ></script>
+		<script src="lib/soundmanager2.min.js" type="text/javascript"></script>
+		<script src="lib/gamequery-0.7.0.js" type="text/javascript" ></script>
+		<script src="lib/gamequery-soundwrapper-soundmanager.js" type="text/javascript"></script>
+		<script src="js/utils.js" type="text/javascript"></script>
+        <script src="js/scoreboard.js" type="text/javascript" ></script>
+        <script src="js/pacman-data.js" type="text/javascript" ></script>
+        <script src="js/pacman-ui.js" type="text/javascript" ></script>
+        <script src="js/pacman-core.js" type="text/javascript" ></script>
+		<link href="css/pacman.css" type="text/css" rel="stylesheet" media="screen, projection" />
+		
+		<link href="../../extra/css/extra.css" type="text/css" rel="stylesheet" media="screen, projection" />
+		<script src="../../extra/js/pacman.js" type="text/javascript" ></script>
+    </head>
+	<div>
+		<div id="playgroundContainer">
+			<div id="playground"></div>
+		</div>
+	</div>
+	<div style="text-align: center;margin-top: 00px;">
+		<button id="startBtn" class="push--skeuo">Start</button>
+	</div>
+
+	<script type="text/javascript">
+		$(document).ready(function () {
+			GUI.drawText( $("#ranking"), 'Howto');
+		});
+	</script>	
+    </body>
+</html>

+ 1009 - 0
demo/pacman/js/pacman-core.js

@@ -0,0 +1,1009 @@
+/*
+Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+var Game = {
+	id : null,
+	type : "offline",
+	player : 1,
+	
+	PACMAN_START_X : 14 * TILE_SIZE,
+	PACMAN_START_Y : 24 * TILE_SIZE,
+
+	GHOST_STATE_CHASE : 1,
+	GHOST_STATE_SCATTER : 2,
+	GHOST_STATE_FRIGHTENED : 3,
+	GHOST_STATE_IN_JAIL : 4,
+	GHOST_STATE_EATEN : 5,
+	GHOST_STATE_FRIGHTENED_BLINK : 6,
+
+	
+	GHOST_EVENT_CHASE : "ghost_event_chase",
+	GHOST_EVENT_SCATTER : "ghost_event_scatter",
+	GHOST_EVENT_DOT_EATEN : "ghost_event_dot_eaten",
+	
+	DOT_POINTS : 10,
+	BIG_DOT_POINTS : 50,
+	totalDots : 0,
+		
+	dots : {},
+	timer : null,
+	frightTimer : null,
+	bonusTimer : null,
+	level : -1,
+	levelData : null,
+	step : 0,
+	score : 0,
+	eatenDots : 0,
+	lives : 3,
+	running : false,
+	mode : 2, // Game.GHOST_STATE_SCATTER
+	frightMode : false,
+	eaten: 0,
+	
+	pacman : null,
+	miss : null,
+	hero : null,
+	blinky : null,
+	pinky : null,
+	inky : null,
+	clyde : null,
+	ghosts : new Array(),
+	actors : {},
+	heroes : new Array(),
+	maze : MAZE,
+
+	init : function() {
+		
+		GUI.updateMessage("READY");
+		
+		$(".dot.hiddenDot").each( function(incr, elt) {
+			Game.dots[elt.id] = "dot";
+		});
+		$(".dot.hiddenDot").removeClass("hiddenDot");
+		
+		$(".bigDot.hiddenDot").each( function(incr, elt) {
+			Game.dots[elt.id] = "bigDot";
+		});
+		$(".bigDot.hiddenDot").removeClass("hiddenDot")
+		
+		Game.totalDots = $(".dot").length + $(".bigDot").length;
+		
+		SCOREBOARD.init();
+		SCOREBOARD.set_score( Game.score );
+		
+		Game.level++;
+		Game.step = 0;
+		Game.eatenDots = 0;
+		
+		GUI.updateLevelNumber( Game.level + 1 );
+		
+		Game.build(LEVELS[Math.min(Game.level, LEVELS.length)]);
+	},
+	
+	build : function(data) {
+		Game.levelData = data;
+		Game.addPacman();
+		Game.addGhosts();
+		Sound.play("opening");
+		setTimeout("Game.start();", 4500);
+	},
+	
+	start : function() {
+		//if( $.browser.webkit )
+		$(document).keydown( function( event ) {
+			if( event.which > 36 && event.which < 41 )
+				 return false;
+		} );
+		//	$(document).keypress(scrollPreventFct );
+		
+		GUI.updateMessage("");
+		Game.timer = new PausableTimer(Game.timerManager, Game.levelData.mode[Game.step] * 1000);
+		Game.running = true;
+	},
+	
+	levelComplete : function() {
+		Game.running = false;
+		Game.timer.stop();
+		Game.timer = null;
+		
+		setTimeout("Game.init();", 3000);
+	},
+
+	eat : function(type) {
+		Game.eatenDots++;
+		if( type === "bigDot" ) {
+			Game.score += Game.BIG_DOT_POINTS;
+//			console.log( "Eating big dot " + Game.score );
+			SCOREBOARD.add( Game.BIG_DOT_POINTS );
+		} else {
+			Game.score += Game.DOT_POINTS;
+//			console.log( "Eating dot " + Game.score );
+			SCOREBOARD.add( Game.DOT_POINTS );
+		}
+		
+		if( Game.eatenDots == 70 || Game.eatenDots == 170 ) {
+			Game.bonusTimer = setTimeout("Game.hideBonus();", ( 9 + Math.random() ) * 1000 );
+			$("#" + Game.maze.bonus_target).addClass( Game.levelData.bonus.type);
+		}
+		
+		if( Game.eatenDots === Game.totalDots )
+			Game.levelComplete();
+	},
+	
+	eatGhost : function(ghost) {
+		Sound.play("ghost");
+		Game.eaten++;
+		var points = Game.eaten * 200;
+		Game.score += points;
+//		console.log(new Date() + " Eating " + ghost.id + " " + (Game.eaten * 200) + " "+ Game.score );
+		SCOREBOARD.add( points );
+	},
+	
+	hideBonus : function() {
+		$("#" + Game.maze.bonus_target).removeClass( Game.levelData.bonus.type);
+		Game.bonusTimer = null;
+	},
+	
+	die : function() {
+		Game.running = false;
+		$.each( Game.actors, function(index, actor) { 
+			actor.speed = 0;
+		})
+		Game.pacman.die();
+		Game.timer.stop();
+		Game.step = 0;
+		Game.timer = null;
+		$("#life" + Game.lives).effect( "pulsate", {times:3, mode:"hide"}, 500 );
+		Game.lives--;
+		if( Game.lives > 0 )
+			setTimeout( "Game.startAfterDie();", 3000);
+		else {
+			GUI.drawText( $("#message"), "GAME OVER", true );
+			Game.show_game_over();
+		}
+	},
+	
+	show_game_over: function() {
+	},
+	
+	startAfterDie : function() {
+		var dotsCounters = new Array();
+		$.each(Game.ghosts, function(index, ghost ) {
+			dotsCounters[index] = ghost.dotsCounter;
+		});
+		
+		Game.addGhosts();
+		Game.addPacman();
+		//Game.addMissPacman();
+
+		$.each(Game.ghosts, function(index, ghost ) {
+			ghost.dotsCounter = dotsCounters[index];
+			if( ghost.dotsCounter >= ghost.dotsLimits[Math.min(Game.level, ghost.dotsLimits.length - 1)] ) {
+				ghost.speed = ghost.initialSpeed;
+				ghost.state_to(Game.GHOST_STATE_SCATTER);
+			}
+		});
+		
+		Game.running = true;
+		Game.step = 0;
+		Game.timer = new PausableTimer(Game.timerManager, Game.levelData.mode[Game.step] * 1000);
+	},
+
+	timerManager : function() {
+		Game.step++;
+		if( Game.step % 2 == 1 ) {
+			$(".actor").trigger(Game.GHOST_EVENT_CHASE);
+			Game.mode = Game.GHOST_STATE_CHASE;
+		} else {
+			$(".actor").trigger(Game.GHOST_EVENT_SCATTER);
+			Game.mode = Game.GHOST_STATE_SCATTER;
+		}
+		if( Game.step < Game.levelData.mode.length - 1 && Game.levelData.mode[Game.step] != INFINITY )
+			Game.timer = new PausableTimer(Game.timerManager, Game.levelData.mode[Game.step] * 1000);
+	},
+	
+	addPacman : function() {
+		if( $("#pacman").length == 0) {
+			Game.pacman = new Pacman();
+			$("#actors").addSprite("pacman", {animation: Game.pacman.animations["right"], posx:Game.pacman.x, posy: Game.pacman.y, width: ACTOR_SIZE, height: ACTOR_SIZE});
+			Game.pacman.node = $("#pacman");
+			Game.pacman.node.addClass( "actor" );
+			Game.actors[ "pacman" ] = Game.pacman;
+			Game.heroes[ "pacman" ] = Game.pacman;
+			
+			Game.hero = Game.pacman;
+		}
+		Game.pacman.init();
+		Game.pacman.speed = Game.levelData.pacman.speed;
+		Game.pacman.left();
+	},
+	
+	addMissPacman : function() {
+		if( $("#miss_pacman").length == 0) {
+			Game.miss = new Pacman();
+			Game.miss.animations["right"] = new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 3, offsety: 272, delta: ACTOR_SIZE, rate: 120, type: $.gameQuery.ANIMATION_HORIZONTAL });
+			Game.miss.animations["up"] = new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 3, offsetx: 96, offsety: 272, delta: ACTOR_SIZE, rate: 120, type: $.gameQuery.ANIMATION_HORIZONTAL });
+			
+			$("#actors").addSprite("miss_pacman", {animation: Game.miss.animations["right"], posx:Game.miss.x, posy: Game.miss.y, width: ACTOR_SIZE, height: ACTOR_SIZE});
+			Game.miss.node = $("#miss_pacman");
+			Game.miss.node.addClass( "actor" );
+			Game.actors[ "miss_pacman" ] = Game.miss;
+			Game.heroes[ "miss_pacman" ] = Game.miss;
+		}
+		Game.miss.init();
+		Game.miss.x = Game.MISS_PACMAN_START_X; 
+		Game.miss.y = Game.MISS_PACMAN_START_Y;
+		Game.miss.speed = Game.levelData.pacman.speed;
+		Game.miss.right(true);
+		Game.miss.left(true);
+		Game.miss.node.x(Game.miss.x);
+		Game.miss.node.y(Game.miss.y);
+		Game.miss.right();
+	},
+	
+	addGhosts : function() {
+		Game.addBlinky();
+		Game.addPinky();
+		Game.addInky();
+		Game.addClyde();
+	},
+	
+	addBlinky : function() {
+		if( $("#blinky").length == 0 ) {
+			Game.blinky = new Ghost("blinky", 0, {x: 14 * TILE_SIZE, y: 14 * TILE_SIZE}, {x: 25, y: 0 }, function() {
+				var prey = Game.actors[ "blinky" ].prey;
+				return {x: prey.getTileX(), y: prey.getTileY()};
+			}, [0,0,0], Game.GHOST_STATE_SCATTER);
+			Game.blinky.center();
+			$("#actors").addSprite("blinky", {animation: Game.blinky.animations["right"], posx:Game.blinky.x, posy: Game.blinky.y, width: ACTOR_SIZE, height: ACTOR_SIZE});
+			Game.blinky.node = $("#blinky");
+			Game.blinky.node.addClass( "actor" );
+			Game.actors[ "blinky" ] = Game.blinky;
+			Game.blinky.loadBindings();
+			
+			Game.blinky.originalTarget = Game.blinky.target;
+			Game.blinky.target = function() {
+				var remainingDots = Game.totalDots - Game.eatenDots;
+				var elroySpecs = Game.levelData.ghost;
+				if( ( Game.blinky.state == Game.GHOST_STATE_SCATTER || Game.blinky.state == Game.GHOST_STATE_CHASE ) && remainingDots <= elroySpecs.elroy1Dots ) {
+					if( remainingDots <= elroySpecs.elroy2Dots ) {
+						Game.blinky.speed = elroySpecs.elroy2Speed;
+					}
+					else {
+						Game.blinky.speed = elroySpecs.elroy1Speed;
+					}
+					
+					return Game.blinky.personnalTarget();
+				}
+				return Game.blinky.originalTarget();
+			};
+			
+			Game.ghosts.push( Game.blinky );
+		} else {
+			Game.blinky.init();
+		}
+		Game.blinky.state = Game.GHOST_STATE_SCATTER;
+		Game.blinky.left();
+		Game.blinky.initialSpeed = Game.levelData.ghost.speed;
+		Game.blinky.speed = Game.blinky.initialSpeed;
+	},
+	
+	addPinky : function() {
+		if( $("#pinky").length == 0 ) {
+			Game.pinky = new Ghost("pinky", 1, {x: 14 * TILE_SIZE, y: 16 * TILE_SIZE}, {x: 2, y: 0 }, function() {
+				var prey = Game.actors[ "pinky" ].prey;
+				var direction = this.prey.direction;
+				if( direction % 2 == 0 )
+					return  {x: prey.getTileX() + (direction == LEFT ? -4 : 4), y: prey.getTileY()};
+					else
+						return {x: prey.getTileX(), y: prey.getTileY() + (direction == UP ? -4 : 4) };
+			}, [0,0,0], Game.GHOST_STATE_IN_JAIL);
+			Game.pinky.center();
+			$("#actors").addSprite("pinky", {animation: Game.pinky.animations["right"], posx: Game.pinky.x, posy: Game.pinky.y, width: ACTOR_SIZE, height: ACTOR_SIZE});
+			Game.pinky.node = $("#pinky");
+			Game.pinky.node.addClass( "actor" );
+			Game.actors[ "pinky" ] = Game.pinky;
+			Game.pinky.loadBindings();
+			
+			Game.ghosts.push( Game.pinky );
+		} else {
+			Game.pinky.init();
+		}
+		Game.pinky.initialSpeed = Game.levelData.ghost.speed;
+		Game.pinky.left();
+	},
+	
+	addInky : function() {
+		if( $("#inky").length == 0 ) {
+			Game.inky = new Ghost("inky", 2, {x: 12 * TILE_SIZE, y: 16 * TILE_SIZE}, {x: 27, y: 34 }, function() {
+				var prey = Game.actors[ "inky" ].prey;
+				var direction = prey.direction;
+				if( direction % 2 == 0 )
+					direction = {x: prey.getTileX() + (direction == LEFT ? -2 : 2) - Game.blinky.getTileX(), y: prey.getTileY() - Game.blinky.getTileY()};
+				else
+					direction = {x: prey.getTileX() - Game.blinky.getTileX(), y: prey.getTileY() + (direction == UP ? -2 : 2) - Game.blinky.getTileY()};
+				return {x: direction.x * 2, y: direction.y * 2};
+			}, [30,0,0], Game.GHOST_STATE_IN_JAIL);
+			Game.inky.center();
+			$("#actors").addSprite("inky", {animation: Game.inky.animations["right"], posx:Game.inky.x, posy: Game.inky.y, width: ACTOR_SIZE, height: ACTOR_SIZE});
+			Game.inky.node = $("#inky");
+			Game.inky.node.addClass( "actor" );
+			Game.actors[ "inky" ] = Game.inky;
+			Game.inky.loadBindings();
+			
+			Game.ghosts.push( Game.inky );
+		} else {
+			Game.inky.init();
+		}
+		Game.inky.initialSpeed = Game.levelData.ghost.speed;
+		Game.inky.right();
+	},
+	
+	addClyde : function() {
+		if( $("#clyde").length == 0 ) {
+			Game.clyde = new Ghost("clyde", 3, {x: 16 * TILE_SIZE, y: 16 * TILE_SIZE}, {x: 0, y: 34 }, function() {
+				var prey = Game.actors[ "clyde" ].prey;
+				return distance( {x: this.getTileX(), y: this.getTileY()} , {x: prey.getTileX(), y: prey.getTileY()}) < 8 ? 
+						this.scatterTarget : {x: prey.getTileX(), y: prey.getTileY()};
+			}, [60,50,0], Game.GHOST_STATE_IN_JAIL);
+			Game.clyde.center();
+			$("#actors").addSprite("clyde", {animation: Game.clyde.animations["right"], posx:Game.clyde.x, posy: Game.clyde.y, width: ACTOR_SIZE, height: ACTOR_SIZE});
+			Game.clyde.node = $("#clyde");
+			Game.clyde.node.addClass( "actor" );
+			Game.actors[ "clyde" ] = Game.clyde;
+			Game.clyde.loadBindings();
+			
+			Game.ghosts.push( Game.clyde );
+		} else {
+			Game.clyde.init();
+		}
+		Game.clyde.initialSpeed = Game.levelData.ghost.speed;
+		Game.clyde.left();
+	},
+	
+	moveGhosts : function() {
+		$.each(Game.ghosts, function(index, ghost ) {
+			ghost.move();
+		});
+	},
+	
+	nearEndFright : function() {
+		$.each(Game.ghosts, function(index, ghost ) {
+			if( ghost.state != Game.GHOST_STATE_IN_JAIL && ghost.state != Game.GHOST_STATE_EATEN )
+				ghost.state_to(Game.GHOST_STATE_FRIGHTENED_BLINK);
+		});
+
+		setTimeout( 'Game.endFright();', 160 * 4 * Game.levelData.frightFlashesCount);
+	},
+	
+	endFright : function() {
+		if( Game.timer )
+			Game.timer.resume();
+		Game.frightTimer = null;
+		Game.eaten = 0;
+		$('.actor').trigger( Game.mode == Game.GHOST_STATE_CHASE ? Game.GHOST_EVENT_CHASE : Game.GHOST_EVENT_SCATTER );
+	}
+}
+
+function distance(currentTile, target) {
+	return Math.sqrt( (target.x - currentTile.x) * (target.x - currentTile.x) + (target.y - currentTile.y)*(target.y - currentTile.y));
+};
+
+//Game objects:
+function Actor(){}
+Actor.prototype = {
+	node : null,
+	animations : null,
+	x : null,
+	y : null,
+	speed : null,
+	direction : null, // 1: up, 2: left, 3:down, 4: right
+	directionX : 0,
+	directionY : 0,
+
+	getX : function() {
+		return x;
+	},
+	
+	getY : function() {
+		return y;
+	},
+	
+	getTileX : function() {
+		return Math.floor(this.x / TILE_SIZE);
+	},
+	
+	getTileY : function() {
+		return Math.floor(this.y / TILE_SIZE);
+	},
+	
+	getTile : function() {
+		return this.getTileX() + this.getTileY() * WIDTH_TILE_COUNT;
+	},
+	
+	getInsideTileX : function() {
+		return this.x % TILE_SIZE;
+	},
+	
+	getInsideTileY : function() {
+		return this.y % TILE_SIZE;
+	},
+	
+	move : function() {
+		if( !Game.running )
+			return;
+		this.x += this.directionX * this.speed * ACTOR_SPEED;
+		this.y += this.directionY * this.speed * ACTOR_SPEED;
+		this.node.x(this.x );
+		this.node.y(this.y );
+	},
+	
+	up : function( force ) {
+		if( force || this.direction != UP ) {
+			this.directionX = 0;
+			this.directionY = -1;
+			this.direction = UP;
+			this.node.setAnimation(this.animations["up"]);
+			this.node.flipv(false);
+			this.node.fliph(false);
+			this.center();
+		}
+	},
+	
+	down : function( force ) {
+		if( force || this.direction != DOWN ) {
+			this.directionX = 0;
+			this.directionY = 1;
+			this.direction = DOWN;
+			if( this.animations["down"] ) {
+				this.node.setAnimation(this.animations["down"]);
+				this.node.fliph( false );
+			} else {
+				this.node.setAnimation(this.animations["up"]);
+				this.node.flipv( true );
+				this.node.fliph( false );
+			}
+			this.center();
+		}
+	},
+	
+	left : function( force ) {
+		if( force || this.direction != LEFT ) {
+			this.directionX = -1;
+			this.directionY = 0;
+			this.direction = LEFT;
+			this.node.flipv( false );
+			if( this.animations["left"] ) {
+				this.node.setAnimation(this.animations["left"]);
+			} else {
+				this.node.setAnimation(this.animations["right"]);
+				this.node.fliph( true );
+			}
+			this.center();
+		}
+	},
+	
+	right : function( force ) {
+		if( force || this.direction != RIGHT ) {
+			this.directionX = 1;
+			this.directionY = 0;
+			this.direction = RIGHT;
+			this.node.setAnimation(this.animations["right"]);
+			this.node.fliph( false );
+			this.node.flipv( false );
+			this.center();
+		}
+	},
+
+	canLeft : function() {
+		return Game.maze.structure[this.getTileX() + this.getTileY() * WIDTH_TILE_COUNT - 1] <= 0;
+	},
+	
+	canRight : function() {
+		return Game.maze.structure[this.getTileX() + this.getTileY() * WIDTH_TILE_COUNT + 1] <= 0;
+	},
+	
+	canUp : function() {
+		return Game.maze.structure[this.getTileX() + (this.getTileY() - 1 ) * WIDTH_TILE_COUNT ] <= 0;
+	},
+	
+	canDown : function() {
+		return Game.maze.structure[this.getTileX() + (this.getTileY() + 1 ) * WIDTH_TILE_COUNT ] <= 0;
+	},
+
+	isNearMiddleTile : function() {
+		return Math.abs( HALF_TILE_SIZE - this.getInsideTileX() ) < 4 && Math.abs( HALF_TILE_SIZE - this.getInsideTileY() ) < 4; 
+	},
+	
+	center : function() {
+		this.x = this.getTileX() * TILE_SIZE + HALF_TILE_SIZE;
+		this.y = this.getTileY() * TILE_SIZE + HALF_TILE_SIZE;
+	},
+	
+	isInTunnel : function() {
+		var tile = this.getTile();
+		return $.inArray(tile, Game.maze.tunnel) > -1;
+	}
+};
+
+/*********************************************/
+/****************** PACMAN *******************/
+/*********************************************/
+function Pacman() {
+	this.animations = {
+			"right": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 3, offsety: 16, delta: ACTOR_SIZE, rate: 120, type: $.gameQuery.ANIMATION_HORIZONTAL }),
+			"up": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 3, offsetx: 64, offsety: 16, delta: ACTOR_SIZE, rate: 120, type: $.gameQuery.ANIMATION_HORIZONTAL }),
+			"die": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 7, offsety: 208, delta: ACTOR_SIZE, rate: 120, type: $.gameQuery.ANIMATION_HORIZONTAL | $.gameQuery.ANIMATION_ONCE | $.gameQuery.ANIMATION_CALLBACK }),
+			"die2": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 4, offsety: 240, delta: ACTOR_SIZE, rate: 120, type: $.gameQuery.ANIMATION_HORIZONTAL | $.gameQuery.ANIMATION_ONCE })
+	}
+};
+Pacman.prototype = {
+	x : Game.PACMAN_START_X,
+	y : Game.PACMAN_START_Y,
+	speed : null,
+	directionX : 0,
+	directionY : 0,
+	lastEatenGhost : null,
+	
+	stop : false,
+	previousTile : null,
+	
+	init : function() {
+		this.x = Game.PACMAN_START_X; 
+		this.y = Game.PACMAN_START_Y;
+		this.speed = Game.levelData.pacman.speed;
+		this.right(true);
+		this.left(true);
+		this.node.x(this.x);
+		this.node.y(this.y);
+	},
+	
+	left : function() {
+		if( this.direction != LEFT && this.canLeft() ) {
+			this.stop = false;
+			this._super("left", arguments);
+		}
+	},
+	
+	right : function() {
+		if( this.direction != RIGHT && this.canRight() ) {
+			this.stop = false;
+			this._super("right", arguments);
+		}
+	},
+	
+	up : function() {
+		if( this.direction != UP && this.canUp() ) {
+			this.stop = false;
+			this._super("up", arguments);
+		}
+	},
+	
+	down : function() {
+		if( this.direction != DOWN && this.canDown() ) {
+			this.stop = false;
+			this._super("down", arguments);
+		}
+	},
+	
+	move : function() {
+		if( !this.stop ) {
+			this.previousTile = {x: this.getTileX(), y: this.getTileY()};
+			this._super("move", arguments);
+			var currentTile = {x: this.getTileX(), y: this.getTileY()};
+			if( this.previousTile.x !== currentTile.x || this.previousTile.y !== currentTile.y ) {
+				var id = this.getTile();
+				if( Game.dots[ id ] )
+					this.eatDot( id );
+				if( id == Game.maze.bonus_target )
+					this.eatBonus();
+				this.eatGhosts();
+			}
+
+			var inTunnel = this.isInTunnel();
+			if( this.x < 0 )
+				this.x += PLAYGROUND_WIDTH;
+			if( this.x > PLAYGROUND_WIDTH )
+				this.x -= PLAYGROUND_WIDTH;
+			switch( this.direction ) {
+				case LEFT :
+					if( !inTunnel && !this.canLeft() ) 
+						this.stop = true;
+					break;
+				case RIGHT :
+					if( !inTunnel && !this.canRight() )
+						this.stop = true;
+					break;
+				case UP :
+					if( !this.canUp() )
+						this.stop = true;
+					break;
+				case DOWN :
+					if( !this.canDown() )
+						this.stop = true;
+					break;
+			}
+		}
+	},
+	
+	eatDot : function(id) {
+		Game.eat(Game.dots[id]);
+		$('.actor').trigger(Game.GHOST_EVENT_DOT_EATEN);
+		if( Game.dots[id] === "bigDot" ) {
+			$.each(Game.ghosts, function(index, ghost ) {
+				if( ghost.state != Game.GHOST_STATE_IN_JAIL && ghost.state != Game.GHOST_STATE_EATEN )
+					ghost.state_to(Game.GHOST_STATE_FRIGHTENED)
+			});
+				
+			Game.timer.pause();
+			if( Game.frightTimer )
+				clearTimeout( Game.frightTimer );
+			Game.frightTimer = setTimeout( 'Game.nearEndFright();', Game.levelData.frightTime * 1000 - 160 * 4 * Game.levelData.frightFlashesCount);
+		}
+		
+		Game.dots[id] = null;
+		$("#" + id ).addClass("hiddenDot");
+	},
+	
+	eatGhosts : function() {
+		var tile = this.getTile();
+		$.each(Game.ghosts, function(index, ghost ) {
+			if( tile == ghost.getTile() ) {
+				Game.pacman.eatGhost( ghost );
+			}
+		});
+	},
+	
+	eatGhost : function( ghost ) {
+		if( ghost.state == Game.GHOST_STATE_EATEN ) {
+//			console.log( ghost.id + " already eaten" );
+			return;
+		}
+		if( ghost.state != Game.GHOST_STATE_FRIGHTENED && ghost.state != Game.GHOST_STATE_FRIGHTENED_BLINK ) {
+			Game.die();
+		} else if( Game.pacman.lastEatenGhost !== ghost.id ){
+			ghost.state_to(Game.GHOST_STATE_EATEN);
+//			console.log( "Eating " + ghost.id + " " + ghost.state );
+			Game.eatGhost(ghost);
+		}
+	},
+	
+	eatBonus : function() {
+		if( !$("#" + Game.maze.bonus_target).hasClass( Game.levelData.bonus.type) && Game.bonusTimer == null )
+			return;
+		
+		Sound.play("fruit");
+		
+		eatenBonus.push(Game.levelData.bonus.type);
+		Game.score += Game.levelData.bonus.points;
+//		console.log( "Eating bonus " + Game.levelData.bonus.points + " " + Game.score );
+		SCOREBOARD.add( Game.levelData.bonus.points );
+		Game.hideBonus();
+	},
+	
+	die : function() {
+		Sound.play("dies");
+		this.node.setAnimation(this.animations["die"], function(node) {
+			Game.pacman.node.setAnimation(Game.pacman.animations["die2"]);
+		});
+	}
+};
+
+// Overriding Actor.methods() method 
+heriter(Pacman.prototype, Actor.prototype); 
+
+function Ghost(id, ghostIndex, start, scatterTarget, personnalTarget, dotsLimits, state ) {
+	this.animations = {
+		"normal_up": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 2, offsety: 48 + ghostIndex * 32, delta: ACTOR_SIZE, rate: 160, type: $.gameQuery.ANIMATION_HORIZONTAL }),
+		"normal_right": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 2, offsetx: 128, offsety: 48 + ghostIndex * 32, delta: ACTOR_SIZE, rate: 160, type: $.gameQuery.ANIMATION_HORIZONTAL }),
+		"normal_down": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 2, offsetx: 64, offsety: 48 + ghostIndex * 32, delta: ACTOR_SIZE, rate: 160, type: $.gameQuery.ANIMATION_HORIZONTAL }),
+		"frightened": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 2, offsetx: 0, offsety: 176, delta: ACTOR_SIZE, rate: 160, type: $.gameQuery.ANIMATION_HORIZONTAL }),
+		"frightened_blink": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 4, offsetx: 0, offsety: 176, delta: ACTOR_SIZE, rate: 160, type: $.gameQuery.ANIMATION_HORIZONTAL }),
+		"eaten_up": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 1, offsetx: 128, offsety: 176, delta: ACTOR_SIZE, rate: 160, type: $.gameQuery.ANIMATION_HORIZONTAL }),
+		"eaten_down": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 1, offsetx: 160, offsety: 176, delta: ACTOR_SIZE, rate: 160, type: $.gameQuery.ANIMATION_HORIZONTAL }),
+		"eaten_right": new $.gameQuery.Animation({imageURL: "img/sprite.png", numberOfFrame: 1, offsetx: 192, offsety: 176, delta: ACTOR_SIZE, rate: 160, type: $.gameQuery.ANIMATION_HORIZONTAL })
+	}
+	this.animations["up"] = this.animations["normal_up"];
+	this.animations["down"] = this.animations["normal_down"];
+	this.animations["right"] = this.animations["normal_right"];
+	
+	this.id = id;
+	this.scatterTarget = scatterTarget;
+	this.personnalTarget = personnalTarget;
+	this.x = start.x;
+	this.y = start.y;
+	this.startingTileX = start.x;
+	this.startingTileY = start.y;
+	
+	this.state = state;
+	
+	this.dotsLimits = dotsLimits;
+	
+	this.prey = Game.pacman;
+};
+
+Ghost.prototype = {
+	id : null,
+	startingTileX : 0,
+	startingTileY : 0,
+	initialSpeed : 0,
+	speed : 0,
+	directionX : 0,
+	directionY : 0,
+
+	state: null,
+	scatterTarget : null,
+	lastDirectionTile : null,
+	
+	prey : null,
+	
+	dotsCounter : 0,
+	dotsLimits : [],
+	
+	init : function() {
+		this.dotsCounter = 0;
+		this.speed = 0;
+		this.x = this.startingTileX; 
+		this.y = this.startingTileY;
+		this.right(true);
+		this.left(true);
+		this.state = Game.GHOST_STATE_IN_JAIL;
+		this.node.x(this.x);
+		this.node.y(this.y);
+	},
+	
+	target : function() {
+		switch( this.state ) {
+			case Game.GHOST_STATE_CHASE :
+				return this.personnalTarget();
+			case Game.GHOST_STATE_SCATTER :
+				return this.scatterTarget;
+			case Game.GHOST_STATE_FRIGHTENED :
+				var currentTile = {x: this.getTileX(), y: this.getTileY()};
+				var targets = new Array();
+				if( this.canUp() && this.direction != DOWN )
+					targets.push( {x:currentTile.x, y:currentTile.y - 1} );
+				if( this.canDown() && this.direction != UP )
+					targets.push( {x:currentTile.x, y:currentTile.y + 1} );
+				if( this.canLeft() && this.direction != RIGHT )
+					targets.push( {x:currentTile.x - 1, y:currentTile.y} );
+				if( this.canRight() && this.direction != LEFT )
+					targets.push( {x:currentTile.x + 1, y:currentTile.y} );
+				return targets[ parseInt(Math.random() * targets.length ) ];
+			case Game.GHOST_STATE_IN_JAIL :
+			case Game.GHOST_STATE_EATEN :
+				return {x: 13, y: 14};
+		}
+	},
+	
+	loadBindings : function() {
+		this.node.bind(Game.GHOST_EVENT_CHASE, {ghost: this}, function(evt) { 
+			var ghost = evt.data.ghost;
+			if( ghost.state != Game.GHOST_STATE_IN_JAIL && ghost.state != Game.GHOST_STATE_EATEN )
+				ghost.state_to(Game.GHOST_STATE_CHASE);
+		});
+		this.node.bind(Game.GHOST_EVENT_SCATTER, {ghost: this}, function(evt) { 
+			var ghost = evt.data.ghost;
+			if( ghost.state != Game.GHOST_STATE_IN_JAIL && ghost.state != Game.GHOST_STATE_EATEN )
+				ghost.state_to(Game.GHOST_STATE_SCATTER);
+		});
+		this.node.bind(Game.GHOST_EVENT_DOT_EATEN, {ghost: this}, function(evt) {  
+			var ghost = evt.data.ghost;
+			if( ghost.state == Game.GHOST_STATE_IN_JAIL && ghost.dotsCounter++ >= ghost.dotsLimits[Math.min(Game.level, ghost.dotsLimits.length - 1)] ) {
+				ghost.speed = ghost.initialSpeed;
+				ghost.state_to(Game.mode);
+			}
+		});
+	},
+	
+	personnalTarget : function() {
+	},
+	
+	state_to : function( state ) {
+		var up;
+		var down;
+		var right;
+		var reverse = this.state != Game.GHOST_STATE_FRIGHTENED && this.state != Game.GHOST_STATE_IN_JAIL; // previous state
+		this.state = state;
+		switch( state ) {
+			case Game.GHOST_STATE_CHASE :
+				this.speed = Game.levelData.ghost.speed;
+			case Game.GHOST_STATE_SCATTER :
+				this.speed = Game.levelData.ghost.speed;
+			case Game.GHOST_STATE_IN_JAIL :
+				up = this.animations["normal_up"];
+				down = this.animations["normal_down"];
+				right = this.animations["normal_right"];
+				break;
+			case Game.GHOST_STATE_FRIGHTENED :
+				up = down = right = this.animations["frightened"];
+				this.speed = Game.levelData.ghost.frightSpeed;
+				break;
+			case Game.GHOST_STATE_FRIGHTENED_BLINK :
+				up = down = right = this.animations["frightened_blink"];
+				this.state = Game.GHOST_STATE_FRIGHTENED;
+				break;
+			case Game.GHOST_STATE_EATEN :
+				up = this.animations["eaten_up"];
+				down = this.animations["eaten_down"];
+				right = this.animations["eaten_right"];
+				this.speed = 1;
+				break;
+		}
+		
+		
+		this.animations["up"] = up;
+		this.animations["down"] = down;
+		this.animations["right"] = right;
+
+		if( reverse )
+			switch( this.direction ) {
+			case UP:
+				this.direction = DOWN;
+				break;
+			case LEFT:
+				this.direction = RIGHT;
+				break;
+			case DOWN:
+				this.direction = UP;
+				break;
+			case RIGHT:
+				this.direction = LEFT;
+				break;
+			}
+			
+		var inTunnel = this.isInTunnel();
+		var distances = [
+             {direction: UP, distance: this.canUp() && this.direction != DOWN ? 1 : INFINITY},
+             {direction: LEFT, distance: (inTunnel && this.direction == LEFT ) || (this.canLeft() && this.direction != RIGHT) ? 1 : INFINITY},
+             {direction: DOWN, distance: this.canDown() && this.direction != UP ? 1 : INFINITY},
+             {direction: RIGHT, distance: (inTunnel && this.direction == RIGHT ) || (this.canRight() && this.direction != LEFT) ? 1 : INFINITY},
+         ];
+		distances.sort( function(a, b) {
+			if( a.distance == b.distance )
+				return a.direction - b.direction;
+			return a.distance - b.distance;
+		})
+		var selected = distances[0];
+
+		switch( selected.direction ) {
+		case UP:
+			this.up(true);
+			break;
+		case LEFT:
+			this.left(true);
+			break;
+		case DOWN:
+			this.down(true);
+			break;
+		case RIGHT:
+			this.right(true);
+			break;
+		}
+		
+	},
+	
+	canUp : function() {
+		switch( this.getTile() ) {
+			case 404:
+			case 407:
+			case 684:
+			case 687:
+				return false;
+			case 461:
+			case 462:
+				return true;
+			default:
+				return Game.maze.structure[ this.getTileX() + (this.getTileY() - 1 ) * WIDTH_TILE_COUNT ] <= 0;
+		} 
+	},
+	
+	canDown : function() {
+		switch( this.getTile() ) {
+			case 405:
+			case 406:
+				return false;
+			default:
+				return Game.maze.structure[ this.getTileX() + (this.getTileY() + 1 ) * WIDTH_TILE_COUNT ] <= 0;
+		} 
+	},
+	
+	move : function() {
+		this._super("move", arguments);
+		var currentTile = {x: this.getTileX(), y: this.getTileY()};
+		var id = this.getTile();;
+		if( this.lastDirectionTile != id && this.isNearMiddleTile()) {
+			this.lastDirectionTile = id;
+			this.eaten();
+			
+			var distances = null;
+			var target = this.target();
+			if( this.state == Game.GHOST_STATE_EATEN && id == Game.maze.ghost_frightened_target ) {
+				this.state_to(Game.mode);
+			}
+			
+			var inTunnel = this.isInTunnel();
+			if( inTunnel )
+				this.speed = Game.levelData.ghost.tunnelSpeed;
+			else if( this.state != Game.GHOST_STATE_IN_JAIL )
+				this.speed = this.state == Game.GHOST_STATE_FRIGHTENED ? Game.levelData.ghost.frightSpeed : Game.levelData.ghost.speed;
+			
+			if( this.x < 0 )
+				this.x += PLAYGROUND_WIDTH;
+			if( this.x > PLAYGROUND_WIDTH )
+				this.x -= PLAYGROUND_WIDTH;
+			
+			if( Game.maze.choice_tiles.indexOf( id ) != -1 ) {
+				distances = [
+	                 {direction: UP, distance: this.canUp() && this.direction != DOWN ? distance({x:currentTile.x, y:currentTile.y - 1}, target ) : INFINITY},
+	                 {direction: LEFT, distance: this.canLeft() && this.direction != RIGHT ? distance( {x:currentTile.x - 1, y:currentTile.y }, target ) : INFINITY},
+	                 {direction: DOWN, distance: this.canDown() && this.direction != UP ? distance({x:currentTile.x, y:currentTile.y + 1}, target ) : INFINITY},
+	                 {direction: RIGHT, distance: this.canRight() && this.direction != LEFT ? distance({x:currentTile.x + 1, y:currentTile.y}, target ) : INFINITY},
+                 ];
+			} else {
+				distances = [
+		             {direction: UP, distance: this.canUp() && this.direction != DOWN ? 1 : INFINITY},
+		             {direction: LEFT, distance: (inTunnel && this.direction == LEFT ) || (this.canLeft() && this.direction != RIGHT) ? 1 : INFINITY},
+		             {direction: DOWN, distance: this.canDown() && this.direction != UP ? 1 : INFINITY},
+		             {direction: RIGHT, distance: (inTunnel && this.direction == RIGHT ) || (this.canRight() && this.direction != LEFT) ? 1 : INFINITY},
+	             ];
+			}
+			distances.sort( function(a, b) {
+				if( a.distance == b.distance )
+					return a.direction - b.direction;
+				return a.distance - b.distance;
+			})
+			var selected = distances[0];
+			
+			switch( selected.direction ) {
+			case LEFT :
+				if( this.direction != LEFT ) 
+					this.left();
+				break;
+			case RIGHT :
+				if( this.direction != RIGHT )
+					this.right();
+				break;
+			case UP :
+				if( this.direction != UP )
+					this.up();
+				break;
+			case DOWN :
+				if( this.direction != DOWN )
+					this.down();
+				break;
+			}
+		}
+
+		var inTunnel = this.isInTunnel();
+		if( this.x < 0 )
+			this.x += PLAYGROUND_WIDTH;
+		if( this.x > PLAYGROUND_WIDTH )
+			this.x -= PLAYGROUND_WIDTH;
+		
+	},
+	
+	eaten : function(target) {
+		if( typeof target === "undefined" )
+			target = this;
+		if( target.getTile() == Game.pacman.getTile() ) {
+//			console.log(" Eaten from ghost" );
+			Game.pacman.eatGhost(target);
+//			if( target.state != Game.GHOST_STATE_FRIGHTENED && target.state != Game.GHOST_STATE_EATEN ) {
+//				Game.die();
+//			} else {
+//				target.state_to(Game.GHOST_STATE_EATEN);
+//				Game.eatGhost(this);
+//			}
+		}
+	}
+};
+
+heriter(Ghost.prototype, Actor.prototype); 

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 11 - 0
demo/pacman/js/pacman-data.js


+ 255 - 0
demo/pacman/js/pacman-ui.js

@@ -0,0 +1,255 @@
+/*
+Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+var SOUND_ACTIVATED = false;
+
+var WIDTH_TILE_COUNT = 28;
+var HEIGHT_TILE_COUNT = 34;
+var TILE_SIZE = 16;
+var HALF_TILE_SIZE = 8;
+var ACTOR_SIZE = 32;
+var PLAYGROUND_WIDTH = WIDTH_TILE_COUNT * TILE_SIZE;
+var PLAYGROUND_HEIGHT = HEIGHT_TILE_COUNT * TILE_SIZE;
+var ACTOR_SPEED = 4;
+var LOOP_COUNT_REFRESH = 66;
+var loopCount = 0;
+var REFRESH_RATE		= 15;
+//1: up, 2: left, 3:down, 4: right
+var UP = 1;
+var LEFT = 2;
+var DOWN = 3;
+var RIGHT = 4;
+
+var BONUS_TILE = 77;
+
+var eatenBonus = new Array();
+
+var INFINITY = 9999999999;
+
+$(function(){
+	
+	//Playground Sprites
+	$("#playground").playground({height: PLAYGROUND_HEIGHT, width: PLAYGROUND_WIDTH, keyTracker: true});
+
+	$.playground({refreshRate: 60}).addGroup("background", {posx: 0, posy: 0, width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT})
+					.end()
+					.addGroup("dots", {width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT})
+					.end()
+					.addGroup("actors", {width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT})
+					.end()
+					.addGroup( "hud", {width: PLAYGROUND_WIDTH, height: PLAYGROUND_HEIGHT})
+					.end();
+	
+	var hud = $("#hud");
+	hud.append("<div id='scoreboard' class='scoreboard'><div class='subScoreboard'></div></div>");
+	hud.append("<div id='lives' ><div id='life3' class='life'></div><div id='life2' class='life'></div><div id='life1' class='life'></div></div>");
+	hud.append("<div id='scoreMessage'></div>");
+	hud.append("<div id='message'></div>'");
+	hud.append("<div id='level'></div>'");
+	hud.append("<div id='levelNumber'></div>'");
+	
+	GUI.updateLevel( "Level" );
+			
+	var background = $("#background");
+	var dotsGroup = $("#dots");
+	var maze = Game.maze.structure;
+	
+	for( var i = 0; i < maze.length; i++ ) {
+		var clazz = "";
+		switch( maze[i]) {
+			case -2:
+				clazz = "bigDot";
+				Game.dots[i] = "bigDot";
+				break;
+			case -1:
+				clazz = "dot";
+				Game.dots[i] = "dot";
+				break;
+			case 1:
+				clazz = "corner1";
+				break;
+			case 2:
+				clazz = "corner2";
+				break;
+			case 3:
+				clazz = "corner3";
+				break;
+			case 4:
+				clazz = "corner4";
+				break;
+			case 5:
+				clazz = "squareCornerTopLeft";
+				break;
+			case 6:
+				clazz = "squareCornerTopRight";
+				break;
+			case 7:
+				clazz = "squareCornerBottomLeft";
+				break;
+			case 8:
+				clazz = "squareCornerBottomRight";
+				break;
+			case 9:
+				clazz = "horizontalMidDown";
+				break;
+			case 10:
+				clazz = "verticalMidLeft";
+				break;
+			case 11:
+				clazz = "verticalMidRight";
+				break;
+			case 12:
+				clazz = "gate";
+				break;
+		}
+		background.append('<div id="'+ i + '" class="tile ' + clazz +'"></div>');
+		
+		if(i % 28 == 27 ) {
+			background.append('<div class="clear"></div>');
+		}
+	}
+
+	// this is the function that control most of the game logic 
+	$.playground().registerCallback(function(){
+		if(jQuery.gameQuery.keyTracker[37]){ //this is left! (a)
+			Game.hero.left();
+		}
+		if(jQuery.gameQuery.keyTracker[38]){ //this is up! (w)
+			Game.hero.up();
+		}
+		if(jQuery.gameQuery.keyTracker[39]){ //this is right! (d)
+			Game.hero.right();
+		}
+		if(jQuery.gameQuery.keyTracker[40]){ //this is down! (s)
+			Game.hero.down();
+		}
+		
+		$.each(Game.actors, function(index, actor ) {
+			actor.move();
+		});
+		
+		for( var i = Math.max(0, eatenBonus.length - 6), j = 0; i < eatenBonus.length; i++, j++) {
+			$("#" +( BONUS_TILE + j)).removeClass().addClass("tile").addClass( eatenBonus[i] );
+		}
+		
+	}, REFRESH_RATE);
+
+	Sound.init(function(){
+		$.playground().startGame( function() {
+		//	Game.init();
+		});
+	});
+	
+});
+
+var Sound = {
+		soundList : [],
+		
+		init : function(callback) {
+			if( SOUND_ACTIVATED ) {
+				soundManager.setup({
+				  	url: 'swf/'
+				  });
+					  
+				Sound.soundList = {
+						opening : new $.gameQuery.SoundWrapper('sound/opening.mp3', false),
+						waka : new $.gameQuery.SoundWrapper('sound/wakawaka.mp3', false),
+						fruit : new $.gameQuery.SoundWrapper('sound/eatingfruit.mp3', false),
+						ghost : new $.gameQuery.SoundWrapper('sound/eatingghost.mp3', false),
+						dies : new $.gameQuery.SoundWrapper('sound/dies.mp3', false)
+					};
+				soundManager.onready( callback );
+			} else
+				callback();
+		},
+
+		play: function( sound ) {
+			if( SOUND_ACTIVATED )
+				Sound.soundList[sound].play();
+		},
+
+		stop: function( sound ) {
+			if( SOUND_ACTIVATED )
+				Sound.soundList[sound].stop();
+		},
+}
+
+var GUI = {
+	updateMessage : function( message ) {
+		GUI.drawText( $("#message"), message, true );
+	},
+	
+	updateScoreMessage : function( message ) {
+		GUI.drawText( $("#scoreMessage"), message, false, "red" );
+	},
+
+	updateLevel : function( message ) {
+		GUI.drawText( $("#level"), message, false );
+	},
+
+	updateLevelNumber: function( message ) {
+		GUI.drawText( $("#levelNumber"), message + "", false, "", true );
+	},
+
+	drawText : function( divHTML, message, center, customClazz, forceSmall) {
+		var html = "";
+		var clazz = "clock";
+		var letterSize = 32;
+		if( typeof customClazz !== "undefined" ) {
+			clazz = " clock " + customClazz;
+		}
+			
+		
+		var count = 0;
+		var width = 0;
+		var height = 0;
+		for( var i = 0; i < message.length; i++ ) {
+			var letter = message[i];
+			var iLetter = (message.charCodeAt(i) - 97);
+			if( letter == " " ) {
+				html += "<div class='blank'></div>";
+				width += 16;
+				count++;
+			} else if( letter.charCodeAt(0) > 47 && letter.charCodeAt(0) < 58 ) {
+				var letterSize = 32;
+				if( forceSmall ) {
+					letterSize = 16;
+				}
+				html += "<div class='" + clazz + (forceSmall ? "small" : "") + "' style='top: -50%;background-position: -" + ( parseInt( letter ) * letterSize) + "px -" + (forceSmall > -1 ? 128 : 0) +"px'></div>";
+				count++;
+			} else if( ( letter.charCodeAt(0) >= 'a'.charCodeAt(0) && letter.charCodeAt(0) <= 'z'.charCodeAt(0)) ) {
+				if( height < 16 )
+					height = 16;
+				width += 16;
+				var lineSize = 20;
+				var x = (iLetter % lineSize) * 16;
+				var y = Math.floor(iLetter / lineSize) * 16 + 144;
+				html += "<div class='" + clazz + " small' style='background-position: -" + x + "px -" + y + "px'></div>";
+				count++;
+			} 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;
+				var lineSize = 10;
+				var x = (iLetter % lineSize) * 32;
+				var y = Math.floor(iLetter / lineSize) * 32 + 32;
+				html += "<div class='" + clazz + "' style='background-position: -" + x + "px -" + y + "px'></div>";
+				count++;
+			}
+		}
+		
+		divHTML.empty();
+		divHTML.css( "width", width + "px");
+		divHTML.css( "height", height + "px");
+		if( center )
+			divHTML.css( "margin-left", "-" + (message.length * letterSize / 2) + "px");
+		divHTML.append( html );
+	}
+}

+ 54 - 0
demo/pacman/js/scoreboard.js

@@ -0,0 +1,54 @@
+/*
+Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+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" );
+	}
+};

+ 55 - 0
demo/pacman/js/utils.js

@@ -0,0 +1,55 @@
+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]; 
+        } 
+    } 
+}
+
+
+/** PausableTimer **/
+
+function PausableTimer(func, millisec) {
+	this.func = func;
+	this.stTime = new Date().valueOf();
+	this.timeout = setTimeout(func, millisec);
+	this.timeLeft = millisec;
+}
+
+PausableTimer.prototype.stop = function() {
+	clearTimeout(this.timeout);
+};
+
+PausableTimer.prototype.pause = function() {
+	clearTimeout(this.timeout);
+	var timeRan = new Date().valueOf()-this.stTime;
+	this.timeLeft -= timeRan;
+};
+
+PausableTimer.prototype.resume = function() {
+	this.timeout = setTimeout(this.func, this.timeLeft);
+	this.stTime = new Date().valueOf();
+};
+
+//Usage:
+//var myTimer = new PausableTimer(function(){alert("It works!");}, 2000);
+//myTimer.pause();
+//myTimer.unpause();
+

+ 1738 - 0
demo/pacman/lib/gamequery-0.7.0.js

@@ -0,0 +1,1738 @@
+/*
+ * gameQuery rev. 0.7.0
+ *
+ * Copyright (c) 2012 Selim Arsever (http://gamequeryjs.com)
+ * licensed under the MIT-License
+ */
+
+// This allows use of the convenient $ notation in a plugin
+(function($) {
+    
+    // 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);
+                                        }
+                                    }
+                                }
+                            } 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,
+                    top:      options.posy,
+                    left:     options.posx,
+                    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;
+            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,
+                     left: options.posx,
+                     top: options.posy,
+                     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;
+            }
+            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,
+                factorh:        1,
+                factorv:        1,
+                buffer:         1
+            }, options);
+
+            var tileSet = tilemapFragment.clone().attr("id",name).css({
+                    top: options.posy, 
+                    left: options.posx, 
+                    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);
+                }
+            }
+            
+            return this.pushStack(tileSet);
+        },
+
+        /**
+         * 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(angle, factor) {
+            var gameQuery = this[0].gameQuery;
+            // Mark transformed and compute bounding box
+            $.gameQuery.update(gameQuery,{angle: angle, factor: factor});
+
+            if(this.css("MozTransform")) {
+                // For firefox from 3.5
+                var transform = "rotate("+angle+"deg) scale("+(factor*gameQuery.factorh)+","+(factor*gameQuery.factorv)+")";
+                this.css("MozTransform",transform);
+            } else if(this.css("-o-transform")) {
+                // For opera from 10.50
+                var transform = "rotate("+angle+"deg) scale("+(factor*gameQuery.factorh)+","+(factor*gameQuery.factorv)+")";
+                this.css("-o-transform",transform);
+            } else if(this.css("msTransform")!==null && this.css("msTransform")!==undefined) {
+                // For ie from 9
+                var transform = "rotate("+angle+"deg) scale("+(factor*gameQuery.factorh)+","+(factor*gameQuery.factorv)+")";
+                this.css("msTransform",transform);
+            } else if(this.css("WebkitTransform")!==null && this.css("WebkitTransform")!==undefined) {
+                // For safari from 3.1 (and chrome)
+                var transform = "rotate("+angle+"deg) scale("+(factor*gameQuery.factorh)+","+(factor*gameQuery.factorv)+")";
+                this.css("WebkitTransform",transform);
+            } else if(this.css("filter")!==undefined){
+                var angle_rad = Math.PI * 2 / 360 * angle;
+                // For ie from 5.5
+                var cos = Math.cos(angle_rad) * factor;
+                var sin = Math.sin(angle_rad) * 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
+         * 
+         * 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){
+            var gameQuery = this[0].gameQuery;
+
+            if(angle !== undefined) {
+                return this.transform(angle % 360, this.scale());
+            } else {
+                var ang = gameQuery.angle;
+                return ang ? ang : 0;
+            }
+        },
+
+        /**
+         * 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.
+         * 
+         * 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){
+            var gameQuery = this[0].gameQuery;
+
+            if(factor !== undefined) {
+                return this.transform(this.rotate(), factor);
+            } else {
+                var fac = gameQuery.factor;
+                return fac ? fac : 1;
+            }
+        },
+
+        /**
+         * 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(this.rotate(), this.scale());
+        },
+
+        /**
+         * 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(this.rotate(), this.scale());
+        },
+
+/** ---------------------------------------------------------------------------------------------------------------------------------------------------------------- **/
+/** --          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.css("left",""+(gameQuery.posx + gameQuery.posOffsetX)+"px");
+                        
+                        //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.css("top",""+(gameQuery.posy + gameQuery.posOffsetY)+"px");
+                        
+                        //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);

+ 80 - 0
demo/pacman/lib/gamequery-soundwrapper-soundmanager.js

@@ -0,0 +1,80 @@
+/**
+ * gameQuery rev. $Revision$
+ *
+ * Copyright (c) 2008 Selim Arsever (gamequery.onaluf.org)
+ * licensed under the MIT (MIT-LICENSE.txt)
+ */
+
+ /**
+  * To use this wrapper you will need:
+  * 1) Include SoundManager2.js before this script
+  * 2) Give SoundManager2 the position of the swf ie. : soundManager.url = './path/to/swf'
+  * 3) Optionally deactivate the debug mode from SoundManager2
+  */
+
+// this allows use of the convenient $ notation in a plugin
+(function($) {
+    soundManager.url = './'
+    // Here is a bogus soundWrapper written as an example
+    $.extend($.gameQuery, {
+        SoundWrapper: function(url, loop) {
+
+            // start loading the sound. Should turn this.ready to true once done.
+            this.load  = function(){
+            	try{
+	                this.sound = soundManager.createSound({
+	                    id: this.id,
+	                    url: url,
+	                    autoplay: false,
+	                    autoLoad: true
+	                });
+              } catch (err) {
+              	// if something failed we generate a fake sound object
+              		this.sound = {readyState: 3, play: function(){}};
+              }
+            };
+
+            // plays the sound if this.ready == true
+            this.play  = function(){
+                if(loop){
+                    this.sound.play({
+                        onfinish: function() {
+                            this.play();
+                        }
+                    });
+                } else {
+                    this.sound.play();
+                }
+            };
+
+            // pauses the sound if it is playing
+            this.pause = function(){
+                this.sound.pause();
+            };
+
+            // stops the sound if it is playing, rewind (even if paused)
+            this.stop  = function(){
+                this.sound.stop();
+            };
+
+            // mutes the sound without stopping it
+            this.muted = function(mute){
+                if(mute){
+                    this.sound.mute()
+                } else {
+                    this.sound.unmute();
+                }
+            }
+
+            // returns true if the sound is ready to be played
+            this.ready = function(){
+                return this.sound.readyState==3
+            };
+
+            // add the sound to the manager
+            this.id = 'sound_'+$.gameQuery.resourceManager.sounds.length;
+            $.gameQuery.resourceManager.addSound(this);
+
+            return true;
+        }});
+})(jQuery);

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
demo/pacman/lib/jquery-1.8.3.min.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 4 - 0
demo/pacman/lib/jquery-ui-1.8.23.custom.min.js


+ 106 - 0
demo/pacman/lib/soundmanager2.min.js

@@ -0,0 +1,106 @@
+/** @license
+
+
+ SoundManager 2: JavaScript Sound for the Web
+ ----------------------------------------------
+ http://schillmania.com/projects/soundmanager2/
+
+ Copyright (c) 2007, Scott Schiller. All rights reserved.
+ Code provided under the BSD License:
+ http://schillmania.com/projects/soundmanager2/license.txt
+
+ V2.97a.20130101
+*/
+(function(j,g){function aa(aa,pa){function ba(a){return c.preferFlash&&z&&!c.ignoreFlash&&c.flash[a]!==g&&c.flash[a]}function q(a){return function(d){var e=this._s;!e||!e._a?(e&&e.id?c._wD(e.id+": Ignoring "+d.type):c._wD(pb+"Ignoring "+d.type),d=null):d=a.call(this,d);return d}}this.setupOptions={url:aa||null,flashVersion:8,debugMode:!0,debugFlash:!1,useConsole:!0,consoleOnly:!0,waitForWindowLoad:!1,bgColor:"#ffffff",useHighPerformance:!1,flashPollingInterval:null,html5PollingInterval:null,flashLoadTimeout:1E3,
+wmode:null,allowScriptAccess:"always",useFlashBlock:!1,useHTML5Audio:!0,html5Test:/^(probably|maybe)$/i,preferFlash:!0,noSWFCache:!1};this.defaultOptions={autoLoad:!1,autoPlay:!1,from:null,loops:1,onid3:null,onload:null,whileloading:null,onplay:null,onpause:null,onresume:null,whileplaying:null,onposition:null,onstop:null,onfailure:null,onfinish:null,multiShot:!0,multiShotEvents:!1,position:null,pan:0,stream:!0,to:null,type:null,usePolicyFile:!1,volume:100};this.flash9Options={isMovieStar:null,usePeakData:!1,
+useWaveformData:!1,useEQData:!1,onbufferchange:null,ondataerror:null};this.movieStarOptions={bufferTime:3,serverURL:null,onconnect:null,duration:null};this.audioFormats={mp3:{type:['audio/mpeg; codecs="mp3"',"audio/mpeg","audio/mp3","audio/MPA","audio/mpa-robust"],required:!0},mp4:{related:["aac","m4a","m4b"],type:['audio/mp4; codecs="mp4a.40.2"',"audio/aac","audio/x-m4a","audio/MP4A-LATM","audio/mpeg4-generic"],required:!1},ogg:{type:["audio/ogg; codecs=vorbis"],required:!1},wav:{type:['audio/wav; codecs="1"',
+"audio/wav","audio/wave","audio/x-wav"],required:!1}};this.movieID="sm2-container";this.id=pa||"sm2movie";this.debugID="soundmanager-debug";this.debugURLParam=/([#?&])debug=1/i;this.versionNumber="V2.97a.20130101";this.altURL=this.movieURL=this.version=null;this.enabled=this.swfLoaded=!1;this.oMC=null;this.sounds={};this.soundIDs=[];this.didFlashBlock=this.muted=!1;this.filePattern=null;this.filePatterns={flash8:/\.mp3(\?.*)?$/i,flash9:/\.mp3(\?.*)?$/i};this.features={buffering:!1,peakData:!1,waveformData:!1,
+eqData:!1,movieStar:!1};this.sandbox={type:null,types:{remote:"remote (domain-based) rules",localWithFile:"local with file access (no internet access)",localWithNetwork:"local with network (internet access only, no local access)",localTrusted:"local, trusted (local+internet access)"},description:null,noRemote:null,noLocal:null};this.html5={usingFlash:null};this.flash={};this.ignoreFlash=this.html5Only=!1;var Pa,c=this,Qa=null,i=null,pb="HTML5::",A,s=navigator.userAgent,R=j.location.href.toString(),
+h=document,qa,Ra,ra,l,C=[],sa=!0,x,S=!1,T=!1,n=!1,r=!1,ca=!1,k,qb=0,U,v,ta,K,ua,I,L,M,Sa,va,da,F,ea,wa,N,xa,V,fa,ga,O,Ta,ya,Ua=["log","info","warn","error"],Va,za,Wa,W=null,Aa=null,p,Ba,P,Xa,ha,ia,Q,t,X=!1,Ca=!1,Ya,Za,$a,ja=0,Y=null,ka,J=[],B=null,ab,la,Z,G,Da,Ea,bb,u,cb=Array.prototype.slice,D=!1,Fa,z,Ga,db,E,eb,Ha,ma=s.match(/(ipad|iphone|ipod)/i),fb=s.match(/android/i),H=s.match(/msie/i),rb=s.match(/webkit/i),Ia=s.match(/safari/i)&&!s.match(/chrome/i),Ja=s.match(/opera/i),Ka=s.match(/(mobile|pre\/|xoom)/i)||
+ma||fb,La=!R.match(/usehtml5audio/i)&&!R.match(/sm2\-ignorebadua/i)&&Ia&&!s.match(/silk/i)&&s.match(/OS X 10_6_([3-7])/i),gb=j.console!==g&&console.log!==g,Ma=h.hasFocus!==g?h.hasFocus():null,na=Ia&&(h.hasFocus===g||!h.hasFocus()),hb=!na,ib=/(mp3|mp4|mpa|m4a|m4b)/i,$=h.location?h.location.protocol.match(/http/i):null,jb=!$?"http://":"",kb=/^\s*audio\/(?:x-)?(?:mpeg4|aac|flv|mov|mp4||m4v|m4a|m4b|mp4v|3gp|3g2)\s*(?:$|;)/i,lb="mpeg4 aac flv mov mp4 m4v f4v m4a m4b mp4v 3gp 3g2".split(" "),sb=RegExp("\\.("+
+lb.join("|")+")(\\?.*)?$","i");this.mimePattern=/^\s*audio\/(?:x-)?(?:mp(?:eg|3))\s*(?:$|;)/i;this.useAltURL=!$;var Na;try{Na=Audio!==g&&(Ja&&opera!==g&&10>opera.version()?new Audio(null):new Audio).canPlayType!==g}catch(ub){Na=!1}this.hasHTML5=Na;this.setup=function(a){var d=!c.url;a!==g&&(n&&B&&c.ok()&&(a.flashVersion!==g||a.url!==g||a.html5Test!==g))&&Q(p("setupLate"));ta(a);d&&(V&&a.url!==g)&&c.beginDelayedInit();!V&&(a.url!==g&&"complete"===h.readyState)&&setTimeout(N,1);return c};this.supported=
+this.ok=function(){return B?n&&!r:c.useHTML5Audio&&c.hasHTML5};this.getMovie=function(c){return A(c)||h[c]||j[c]};this.createSound=function(a,d){function e(){f=ha(f);c.sounds[f.id]=new Pa(f);c.soundIDs.push(f.id);return c.sounds[f.id]}var b,f;b=null;b="soundManager.createSound(): "+p(!n?"notReady":"notOK");if(!n||!c.ok())return Q(b),!1;d!==g&&(a={id:a,url:d});f=v(a);f.url=ka(f.url);f.id.toString().charAt(0).match(/^[0-9]$/)&&c._wD("soundManager.createSound(): "+p("badID",f.id),2);c._wD("soundManager.createSound(): "+
+f.id+" ("+f.url+")",1);if(t(f.id,!0))return c._wD("soundManager.createSound(): "+f.id+" exists",1),c.sounds[f.id];la(f)?(b=e(),c._wD(f.id+": Using HTML5"),b._setup_html5(f)):(8<l&&(null===f.isMovieStar&&(f.isMovieStar=!(!f.serverURL&&!(f.type&&f.type.match(kb)||f.url.match(sb)))),f.isMovieStar&&(c._wD("soundManager.createSound(): using MovieStar handling"),1<f.loops&&k("noNSLoop"))),f=ia(f,"soundManager.createSound(): "),b=e(),8===l?i._createSound(f.id,f.loops||1,f.usePolicyFile):(i._createSound(f.id,
+f.url,f.usePeakData,f.useWaveformData,f.useEQData,f.isMovieStar,f.isMovieStar?f.bufferTime:!1,f.loops||1,f.serverURL,f.duration||null,f.autoPlay,!0,f.autoLoad,f.usePolicyFile),f.serverURL||(b.connected=!0,f.onconnect&&f.onconnect.apply(b))),!f.serverURL&&(f.autoLoad||f.autoPlay)&&b.load(f));!f.serverURL&&f.autoPlay&&b.play();return b};this.destroySound=function(a,d){if(!t(a))return!1;var e=c.sounds[a],b;e._iO={};e.stop();e.unload();for(b=0;b<c.soundIDs.length;b++)if(c.soundIDs[b]===a){c.soundIDs.splice(b,
+1);break}d||e.destruct(!0);delete c.sounds[a];return!0};this.load=function(a,d){return!t(a)?!1:c.sounds[a].load(d)};this.unload=function(a){return!t(a)?!1:c.sounds[a].unload()};this.onposition=this.onPosition=function(a,d,e,b){return!t(a)?!1:c.sounds[a].onposition(d,e,b)};this.clearOnPosition=function(a,d,e){return!t(a)?!1:c.sounds[a].clearOnPosition(d,e)};this.start=this.play=function(a,d){var e=!1;return!n||!c.ok()?(Q("soundManager.play(): "+p(!n?"notReady":"notOK")),e):!t(a)?(d instanceof Object||
+(d={url:d}),d&&d.url&&(c._wD('soundManager.play(): attempting to create "'+a+'"',1),d.id=a,e=c.createSound(d).play()),e):c.sounds[a].play(d)};this.setPosition=function(a,d){return!t(a)?!1:c.sounds[a].setPosition(d)};this.stop=function(a){if(!t(a))return!1;c._wD("soundManager.stop("+a+")",1);return c.sounds[a].stop()};this.stopAll=function(){var a;c._wD("soundManager.stopAll()",1);for(a in c.sounds)c.sounds.hasOwnProperty(a)&&c.sounds[a].stop()};this.pause=function(a){return!t(a)?!1:c.sounds[a].pause()};
+this.pauseAll=function(){var a;for(a=c.soundIDs.length-1;0<=a;a--)c.sounds[c.soundIDs[a]].pause()};this.resume=function(a){return!t(a)?!1:c.sounds[a].resume()};this.resumeAll=function(){var a;for(a=c.soundIDs.length-1;0<=a;a--)c.sounds[c.soundIDs[a]].resume()};this.togglePause=function(a){return!t(a)?!1:c.sounds[a].togglePause()};this.setPan=function(a,d){return!t(a)?!1:c.sounds[a].setPan(d)};this.setVolume=function(a,d){return!t(a)?!1:c.sounds[a].setVolume(d)};this.mute=function(a){var d=0;a instanceof
+String&&(a=null);if(a){if(!t(a))return!1;c._wD('soundManager.mute(): Muting "'+a+'"');return c.sounds[a].mute()}c._wD("soundManager.mute(): Muting all sounds");for(d=c.soundIDs.length-1;0<=d;d--)c.sounds[c.soundIDs[d]].mute();return c.muted=!0};this.muteAll=function(){c.mute()};this.unmute=function(a){a instanceof String&&(a=null);if(a){if(!t(a))return!1;c._wD('soundManager.unmute(): Unmuting "'+a+'"');return c.sounds[a].unmute()}c._wD("soundManager.unmute(): Unmuting all sounds");for(a=c.soundIDs.length-
+1;0<=a;a--)c.sounds[c.soundIDs[a]].unmute();c.muted=!1;return!0};this.unmuteAll=function(){c.unmute()};this.toggleMute=function(a){return!t(a)?!1:c.sounds[a].toggleMute()};this.getMemoryUse=function(){var c=0;i&&8!==l&&(c=parseInt(i._getMemoryUse(),10));return c};this.disable=function(a){var d;a===g&&(a=!1);if(r)return!1;r=!0;k("shutdown",1);for(d=c.soundIDs.length-1;0<=d;d--)Va(c.sounds[c.soundIDs[d]]);U(a);u.remove(j,"load",L);return!0};this.canPlayMIME=function(a){var d;c.hasHTML5&&(d=Z({type:a}));
+!d&&B&&(d=a&&c.ok()?!!(8<l&&a.match(kb)||a.match(c.mimePattern)):null);return d};this.canPlayURL=function(a){var d;c.hasHTML5&&(d=Z({url:a}));!d&&B&&(d=a&&c.ok()?!!a.match(c.filePattern):null);return d};this.canPlayLink=function(a){return a.type!==g&&a.type&&c.canPlayMIME(a.type)?!0:c.canPlayURL(a.href)};this.getSoundById=function(a,d){if(!a)throw Error("soundManager.getSoundById(): sID is null/_undefined");var e=c.sounds[a];!e&&!d&&c._wD('"'+a+'" is an invalid sound ID.',2);return e};this.onready=
+function(a,d){if("function"===typeof a)n&&c._wD(p("queue","onready")),d||(d=j),ua("onready",a,d),I();else throw p("needFunction","onready");return!0};this.ontimeout=function(a,d){if("function"===typeof a)n&&c._wD(p("queue","ontimeout")),d||(d=j),ua("ontimeout",a,d),I({type:"ontimeout"});else throw p("needFunction","ontimeout");return!0};this._writeDebug=function(a,d){var e,b;if(!c.debugMode)return!1;if(gb&&c.useConsole){if(d&&"object"===typeof d)console.log(a,d);else if(Ua[d]!==g)console[Ua[d]](a);
+else console.log(a);if(c.consoleOnly)return!0}e=A("soundmanager-debug");if(!e)return!1;b=h.createElement("div");0===++qb%2&&(b.className="sm2-alt");d=d===g?0:parseInt(d,10);b.appendChild(h.createTextNode(a));d&&(2<=d&&(b.style.fontWeight="bold"),3===d&&(b.style.color="#ff3333"));e.insertBefore(b,e.firstChild);return!0};-1!==R.indexOf("sm2-debug=alert")&&(this._writeDebug=function(c){j.alert(c)});this._wD=this._writeDebug;this._debug=function(){var a,d;k("currentObj",1);a=0;for(d=c.soundIDs.length;a<
+d;a++)c.sounds[c.soundIDs[a]]._debug()};this.reboot=function(a,d){c.soundIDs.length&&c._wD("Destroying "+c.soundIDs.length+" SMSound objects...");var e,b,f;for(e=c.soundIDs.length-1;0<=e;e--)c.sounds[c.soundIDs[e]].destruct();if(i)try{H&&(Aa=i.innerHTML),W=i.parentNode.removeChild(i),k("flRemoved")}catch(g){k("badRemove",2)}Aa=W=B=i=null;c.enabled=V=n=X=Ca=S=T=r=D=c.swfLoaded=!1;c.soundIDs=[];c.sounds={};if(a)C=[];else for(e in C)if(C.hasOwnProperty(e)){b=0;for(f=C[e].length;b<f;b++)C[e][b].fired=
+!1}d||c._wD("soundManager: Rebooting...");c.html5={usingFlash:null};c.flash={};c.html5Only=!1;c.ignoreFlash=!1;j.setTimeout(function(){wa();d||c.beginDelayedInit()},20);return c};this.reset=function(){k("reset");return c.reboot(!0,!0)};this.getMoviePercent=function(){return i&&"PercentLoaded"in i?i.PercentLoaded():null};this.beginDelayedInit=function(){ca=!0;N();setTimeout(function(){if(Ca)return!1;ga();ea();return Ca=!0},20);M()};this.destruct=function(){c._wD("soundManager.destruct()");c.disable(!0)};
+Pa=function(a){var d,e,b=this,f,j,mb,m,h,n,q=!1,y=[],s=0,Oa,u,r=null;e=d=null;this.sID=this.id=a.id;this.url=a.url;this._iO=this.instanceOptions=this.options=v(a);this.pan=this.options.pan;this.volume=this.options.volume;this.isHTML5=!1;this._a=null;this.id3={};this._debug=function(){c._wD(b.id+": Merged options:",b.options)};this.load=function(a){var d=null;a!==g?b._iO=v(a,b.options):(a=b.options,b._iO=a,r&&r!==b.url&&(k("manURL"),b._iO.url=b.url,b.url=null));b._iO.url||(b._iO.url=b.url);b._iO.url=
+ka(b._iO.url);a=b.instanceOptions=b._iO;c._wD(b.id+": load ("+a.url+")");if(a.url===b.url&&0!==b.readyState&&2!==b.readyState)return k("onURL",1),3===b.readyState&&a.onload&&a.onload.apply(b,[!!b.duration]),b;b.loaded=!1;b.readyState=1;b.playState=0;b.id3={};if(la(a))d=b._setup_html5(a),d._called_load?c._wD(b.id+": Ignoring request to load again"):(b._html5_canplay=!1,b.url!==a.url&&(c._wD(k("manURL")+": "+a.url),b._a.src=a.url,b.setPosition(0)),b._a.autobuffer="auto",b._a.preload="auto",b._a._called_load=
+!0,a.autoPlay&&b.play());else try{b.isHTML5=!1,b._iO=ia(ha(a)),a=b._iO,8===l?i._load(b.id,a.url,a.stream,a.autoPlay,a.usePolicyFile):i._load(b.id,a.url,!!a.stream,!!a.autoPlay,a.loops||1,!!a.autoLoad,a.usePolicyFile)}catch(e){k("smError",2),x("onload",!1),O({type:"SMSOUND_LOAD_JS_EXCEPTION",fatal:!0})}b.url=a.url;return b};this.unload=function(){0!==b.readyState&&(c._wD(b.id+": unload()"),b.isHTML5?(m(),b._a&&(b._a.pause(),Da(b._a,"about:blank"),r="about:blank")):8===l?i._unload(b.id,"about:blank"):
+i._unload(b.id),f());return b};this.destruct=function(a){c._wD(b.id+": Destruct");b.isHTML5?(m(),b._a&&(b._a.pause(),Da(b._a),D||mb(),b._a._s=null,b._a=null)):(b._iO.onfailure=null,i._destroySound(b.id));a||c.destroySound(b.id,!0)};this.start=this.play=function(a,d){var e,f,w=!0,w=null;e=b.id+": play(): ";d=d===g?!0:d;a||(a={});b.url&&(b._iO.url=b.url);b._iO=v(b._iO,b.options);b._iO=v(a,b._iO);b._iO.url=ka(b._iO.url);b.instanceOptions=b._iO;if(b._iO.serverURL&&!b.connected)return b.getAutoPlay()||
+(c._wD(e+" Netstream not connected yet - setting autoPlay"),b.setAutoPlay(!0)),b;la(b._iO)&&(b._setup_html5(b._iO),h());1===b.playState&&!b.paused&&((f=b._iO.multiShot)?c._wD(e+"Already playing (multi-shot)",1):(c._wD(e+"Already playing (one-shot)",1),w=b));if(null!==w)return w;a.url&&a.url!==b.url&&b.load(b._iO);b.loaded?c._wD(e):0===b.readyState?(c._wD(e+"Attempting to load"),b.isHTML5||(b._iO.autoPlay=!0),b.load(b._iO),b.instanceOptions=b._iO):2===b.readyState?(c._wD(e+"Could not load - exiting",
+2),w=b):c._wD(e+"Loading - attempting to play...");if(null!==w)return w;!b.isHTML5&&(9===l&&0<b.position&&b.position===b.duration)&&(c._wD(e+"Sound at end, resetting to position:0"),a.position=0);if(b.paused&&0<=b.position&&(!b._iO.serverURL||0<b.position))c._wD(e+"Resuming from paused state",1),b.resume();else{b._iO=v(a,b._iO);if(null!==b._iO.from&&null!==b._iO.to&&0===b.instanceCount&&0===b.playState&&!b._iO.serverURL){f=function(){b._iO=v(a,b._iO);b.play(b._iO)};if(b.isHTML5&&!b._html5_canplay)c._wD(e+
+"Beginning load for from/to case"),b.load({oncanplay:f}),w=!1;else if(!b.isHTML5&&!b.loaded&&(!b.readyState||2!==b.readyState))c._wD(e+"Preloading for from/to case"),b.load({onload:f}),w=!1;if(null!==w)return w;b._iO=u()}c._wD(e+"Starting to play");(!b.instanceCount||b._iO.multiShotEvents||!b.isHTML5&&8<l&&!b.getAutoPlay())&&b.instanceCount++;b._iO.onposition&&0===b.playState&&n(b);b.playState=1;b.paused=!1;b.position=b._iO.position!==g&&!isNaN(b._iO.position)?b._iO.position:0;b.isHTML5||(b._iO=ia(ha(b._iO)));
+b._iO.onplay&&d&&(b._iO.onplay.apply(b),q=!0);b.setVolume(b._iO.volume,!0);b.setPan(b._iO.pan,!0);b.isHTML5?(h(),e=b._setup_html5(),b.setPosition(b._iO.position),e.play()):(w=i._start(b.id,b._iO.loops||1,9===l?b._iO.position:b._iO.position/1E3,b._iO.multiShot),9===l&&!w&&(c._wD(e+"No sound hardware, or 32-sound ceiling hit"),b._iO.onplayerror&&b._iO.onplayerror.apply(b)))}return b};this.stop=function(a){var d=b._iO;1===b.playState&&(c._wD(b.id+": stop()"),b._onbufferchange(0),b._resetOnPosition(0),
+b.paused=!1,b.isHTML5||(b.playState=0),Oa(),d.to&&b.clearOnPosition(d.to),b.isHTML5?b._a&&(a=b.position,b.setPosition(0),b.position=a,b._a.pause(),b.playState=0,b._onTimer(),m()):(i._stop(b.id,a),d.serverURL&&b.unload()),b.instanceCount=0,b._iO={},d.onstop&&d.onstop.apply(b));return b};this.setAutoPlay=function(a){c._wD(b.id+": Autoplay turned "+(a?"on":"off"));b._iO.autoPlay=a;b.isHTML5||(i._setAutoPlay(b.id,a),a&&(!b.instanceCount&&1===b.readyState)&&(b.instanceCount++,c._wD(b.id+": Incremented instance count to "+
+b.instanceCount)))};this.getAutoPlay=function(){return b._iO.autoPlay};this.setPosition=function(a){a===g&&(a=0);var d=b.isHTML5?Math.max(a,0):Math.min(b.duration||b._iO.duration,Math.max(a,0));b.position=d;a=b.position/1E3;b._resetOnPosition(b.position);b._iO.position=d;if(b.isHTML5){if(b._a)if(b._html5_canplay){if(b._a.currentTime!==a){c._wD(b.id+": setPosition("+a+")");try{b._a.currentTime=a,(0===b.playState||b.paused)&&b._a.pause()}catch(e){c._wD(b.id+": setPosition("+a+") failed: "+e.message,
+2)}}}else c._wD(b.id+": setPosition("+a+"): Cannot seek yet, sound not ready")}else a=9===l?b.position:a,b.readyState&&2!==b.readyState&&i._setPosition(b.id,a,b.paused||!b.playState,b._iO.multiShot);b.isHTML5&&b.paused&&b._onTimer(!0);return b};this.pause=function(a){if(b.paused||0===b.playState&&1!==b.readyState)return b;c._wD(b.id+": pause()");b.paused=!0;b.isHTML5?(b._setup_html5().pause(),m()):(a||a===g)&&i._pause(b.id,b._iO.multiShot);b._iO.onpause&&b._iO.onpause.apply(b);return b};this.resume=
+function(){var a=b._iO;if(!b.paused)return b;c._wD(b.id+": resume()");b.paused=!1;b.playState=1;b.isHTML5?(b._setup_html5().play(),h()):(a.isMovieStar&&!a.serverURL&&b.setPosition(b.position),i._pause(b.id,a.multiShot));!q&&a.onplay?(a.onplay.apply(b),q=!0):a.onresume&&a.onresume.apply(b);return b};this.togglePause=function(){c._wD(b.id+": togglePause()");if(0===b.playState)return b.play({position:9===l&&!b.isHTML5?b.position:b.position/1E3}),b;b.paused?b.resume():b.pause();return b};this.setPan=
+function(a,c){a===g&&(a=0);c===g&&(c=!1);b.isHTML5||i._setPan(b.id,a);b._iO.pan=a;c||(b.pan=a,b.options.pan=a);return b};this.setVolume=function(a,d){a===g&&(a=100);d===g&&(d=!1);b.isHTML5?b._a&&(b._a.volume=Math.max(0,Math.min(1,a/100))):i._setVolume(b.id,c.muted&&!b.muted||b.muted?0:a);b._iO.volume=a;d||(b.volume=a,b.options.volume=a);return b};this.mute=function(){b.muted=!0;b.isHTML5?b._a&&(b._a.muted=!0):i._setVolume(b.id,0);return b};this.unmute=function(){b.muted=!1;var a=b._iO.volume!==g;
+b.isHTML5?b._a&&(b._a.muted=!1):i._setVolume(b.id,a?b._iO.volume:b.options.volume);return b};this.toggleMute=function(){return b.muted?b.unmute():b.mute()};this.onposition=this.onPosition=function(a,c,d){y.push({position:parseInt(a,10),method:c,scope:d!==g?d:b,fired:!1});return b};this.clearOnPosition=function(b,a){var c,b=parseInt(b,10);if(isNaN(b))return!1;for(c=0;c<y.length;c++)if(b===y[c].position&&(!a||a===y[c].method))y[c].fired&&s--,y.splice(c,1)};this._processOnPosition=function(){var a,c;
+a=y.length;if(!a||!b.playState||s>=a)return!1;for(a-=1;0<=a;a--)c=y[a],!c.fired&&b.position>=c.position&&(c.fired=!0,s++,c.method.apply(c.scope,[c.position]));return!0};this._resetOnPosition=function(b){var a,c;a=y.length;if(!a)return!1;for(a-=1;0<=a;a--)c=y[a],c.fired&&b<=c.position&&(c.fired=!1,s--);return!0};u=function(){var a=b._iO,d=a.from,e=a.to,f,g;g=function(){c._wD(b.id+': "To" time of '+e+" reached.");b.clearOnPosition(e,g);b.stop()};f=function(){c._wD(b.id+': Playing "from" '+d);if(null!==
+e&&!isNaN(e))b.onPosition(e,g)};null!==d&&!isNaN(d)&&(a.position=d,a.multiShot=!1,f());return a};n=function(){var a,c=b._iO.onposition;if(c)for(a in c)if(c.hasOwnProperty(a))b.onPosition(parseInt(a,10),c[a])};Oa=function(){var a,c=b._iO.onposition;if(c)for(a in c)c.hasOwnProperty(a)&&b.clearOnPosition(parseInt(a,10))};h=function(){b.isHTML5&&Ya(b)};m=function(){b.isHTML5&&Za(b)};f=function(a){a||(y=[],s=0);q=!1;b._hasTimer=null;b._a=null;b._html5_canplay=!1;b.bytesLoaded=null;b.bytesTotal=null;b.duration=
+b._iO&&b._iO.duration?b._iO.duration:null;b.durationEstimate=null;b.buffered=[];b.eqData=[];b.eqData.left=[];b.eqData.right=[];b.failures=0;b.isBuffering=!1;b.instanceOptions={};b.instanceCount=0;b.loaded=!1;b.metadata={};b.readyState=0;b.muted=!1;b.paused=!1;b.peakData={left:0,right:0};b.waveformData={left:[],right:[]};b.playState=0;b.position=null;b.id3={}};f();this._onTimer=function(a){var c,f=!1,g={};if(b._hasTimer||a){if(b._a&&(a||(0<b.playState||1===b.readyState)&&!b.paused))c=b._get_html5_duration(),
+c!==d&&(d=c,b.duration=c,f=!0),b.durationEstimate=b.duration,c=1E3*b._a.currentTime||0,c!==e&&(e=c,f=!0),(f||a)&&b._whileplaying(c,g,g,g,g);return f}};this._get_html5_duration=function(){var a=b._iO;return(a=b._a&&b._a.duration?1E3*b._a.duration:a&&a.duration?a.duration:null)&&!isNaN(a)&&Infinity!==a?a:null};this._apply_loop=function(b,a){!b.loop&&1<a&&c._wD("Note: Native HTML5 looping is infinite.",1);b.loop=1<a?"loop":""};this._setup_html5=function(a){var a=v(b._iO,a),c=decodeURI,d=D?Qa:b._a,e=
+c(a.url),g;D?e===Fa&&(g=!0):e===r&&(g=!0);if(d){if(d._s)if(D)d._s&&(d._s.playState&&!g)&&d._s.stop();else if(!D&&e===c(r))return b._apply_loop(d,a.loops),d;g||(f(!1),d.src=a.url,Fa=r=b.url=a.url,d._called_load=!1)}else b._a=a.autoLoad||a.autoPlay?new Audio(a.url):Ja&&10>opera.version()?new Audio(null):new Audio,d=b._a,d._called_load=!1,D&&(Qa=d);b.isHTML5=!0;b._a=d;d._s=b;j();b._apply_loop(d,a.loops);a.autoLoad||a.autoPlay?b.load():(d.autobuffer=!1,d.preload="auto");return d};j=function(){if(b._a._added_events)return!1;
+var a;b._a._added_events=!0;for(a in E)E.hasOwnProperty(a)&&b._a&&b._a.addEventListener(a,E[a],!1);return!0};mb=function(){var a;c._wD(b.id+": Removing event listeners");b._a._added_events=!1;for(a in E)E.hasOwnProperty(a)&&b._a&&b._a.removeEventListener(a,E[a],!1)};this._onload=function(a){var d=!!a||!b.isHTML5&&8===l&&b.duration,a=b.id+": ";c._wD(a+(d?"onload()":"Failed to load? - "+b.url),d?1:2);!d&&!b.isHTML5&&(!0===c.sandbox.noRemote&&c._wD(a+p("noNet"),1),!0===c.sandbox.noLocal&&c._wD(a+p("noLocal"),
+1));b.loaded=d;b.readyState=d?3:2;b._onbufferchange(0);b._iO.onload&&b._iO.onload.apply(b,[d]);return!0};this._onbufferchange=function(a){if(0===b.playState||a&&b.isBuffering||!a&&!b.isBuffering)return!1;b.isBuffering=1===a;b._iO.onbufferchange&&(c._wD(b.id+": Buffer state change: "+a),b._iO.onbufferchange.apply(b));return!0};this._onsuspend=function(){b._iO.onsuspend&&(c._wD(b.id+": Playback suspended"),b._iO.onsuspend.apply(b));return!0};this._onfailure=function(a,d,e){b.failures++;c._wD(b.id+": Failures = "+
+b.failures);if(b._iO.onfailure&&1===b.failures)b._iO.onfailure(b,a,d,e);else c._wD(b.id+": Ignoring failure")};this._onfinish=function(){var a=b._iO.onfinish;b._onbufferchange(0);b._resetOnPosition(0);if(b.instanceCount&&(b.instanceCount--,b.instanceCount||(Oa(),b.playState=0,b.paused=!1,b.instanceCount=0,b.instanceOptions={},b._iO={},m(),b.isHTML5&&(b.position=0)),(!b.instanceCount||b._iO.multiShotEvents)&&a))c._wD(b.id+": onfinish()"),a.apply(b)};this._whileloading=function(a,c,d,e){var f=b._iO;
+b.bytesLoaded=a;b.bytesTotal=c;b.duration=Math.floor(d);b.bufferLength=e;b.durationEstimate=!b.isHTML5&&!f.isMovieStar?f.duration?b.duration>f.duration?b.duration:f.duration:parseInt(b.bytesTotal/b.bytesLoaded*b.duration,10):b.duration;b.isHTML5||(b.buffered=[{start:0,end:b.duration}]);(3!==b.readyState||b.isHTML5)&&f.whileloading&&f.whileloading.apply(b)};this._whileplaying=function(a,c,d,e,f){var m=b._iO;if(isNaN(a)||null===a)return!1;b.position=Math.max(0,a);b._processOnPosition();!b.isHTML5&&
+8<l&&(m.usePeakData&&(c!==g&&c)&&(b.peakData={left:c.leftPeak,right:c.rightPeak}),m.useWaveformData&&(d!==g&&d)&&(b.waveformData={left:d.split(","),right:e.split(",")}),m.useEQData&&(f!==g&&f&&f.leftEQ)&&(a=f.leftEQ.split(","),b.eqData=a,b.eqData.left=a,f.rightEQ!==g&&f.rightEQ&&(b.eqData.right=f.rightEQ.split(","))));1===b.playState&&(!b.isHTML5&&(8===l&&!b.position&&b.isBuffering)&&b._onbufferchange(0),m.whileplaying&&m.whileplaying.apply(b));return!0};this._oncaptiondata=function(a){c._wD(b.id+
+": Caption data received.");b.captiondata=a;b._iO.oncaptiondata&&b._iO.oncaptiondata.apply(b,[a])};this._onmetadata=function(a,d){c._wD(b.id+": Metadata received.");var e={},f,g;f=0;for(g=a.length;f<g;f++)e[a[f]]=d[f];b.metadata=e;b._iO.onmetadata&&b._iO.onmetadata.apply(b)};this._onid3=function(a,d){c._wD(b.id+": ID3 data received.");var e=[],f,g;f=0;for(g=a.length;f<g;f++)e[a[f]]=d[f];b.id3=v(b.id3,e);b._iO.onid3&&b._iO.onid3.apply(b)};this._onconnect=function(a){a=1===a;c._wD(b.id+": "+(a?"Connected.":
+"Failed to connect? - "+b.url),a?1:2);if(b.connected=a)b.failures=0,t(b.id)&&(b.getAutoPlay()?b.play(g,b.getAutoPlay()):b._iO.autoLoad&&b.load()),b._iO.onconnect&&b._iO.onconnect.apply(b,[a])};this._ondataerror=function(a){0<b.playState&&(c._wD(b.id+": Data error: "+a),b._iO.ondataerror&&b._iO.ondataerror.apply(b))};this._debug()};fa=function(){return h.body||h._docElement||h.getElementsByTagName("div")[0]};A=function(a){return h.getElementById(a)};v=function(a,d){var e=a||{},b,f;b=d===g?c.defaultOptions:
+d;for(f in b)b.hasOwnProperty(f)&&e[f]===g&&(e[f]="object"!==typeof b[f]||null===b[f]?b[f]:v(e[f],b[f]));return e};K={onready:1,ontimeout:1,defaultOptions:1,flash9Options:1,movieStarOptions:1};ta=function(a,d){var e,b=!0,f=d!==g,h=c.setupOptions;if(a===g){b=[];for(e in h)h.hasOwnProperty(e)&&b.push(e);for(e in K)K.hasOwnProperty(e)&&("object"===typeof c[e]?b.push(e+": {...}"):c[e]instanceof Function?b.push(e+": function() {...}"):b.push(e));c._wD(p("setup",b.join(", ")));return!1}for(e in a)if(a.hasOwnProperty(e))if("object"!==
+typeof a[e]||null===a[e]||a[e]instanceof Array||a[e]instanceof RegExp)f&&K[d]!==g?c[d][e]=a[e]:h[e]!==g?(c.setupOptions[e]=a[e],c[e]=a[e]):K[e]===g?(Q(p(c[e]===g?"setupUndef":"setupError",e),2),b=!1):c[e]instanceof Function?c[e].apply(c,a[e]instanceof Array?a[e]:[a[e]]):c[e]=a[e];else if(K[e]===g)Q(p(c[e]===g?"setupUndef":"setupError",e),2),b=!1;else return ta(a[e],e);return b};var nb=function(a){var a=cb.call(a),c=a.length;oa?(a[1]="on"+a[1],3<c&&a.pop()):3===c&&a.push(!1);return a},ob=function(a,
+c){var e=a.shift(),b=[tb[c]];if(oa)e[b](a[0],a[1]);else e[b].apply(e,a)},oa=j.attachEvent,tb={add:oa?"attachEvent":"addEventListener",remove:oa?"detachEvent":"removeEventListener"};u={add:function(){ob(nb(arguments),"add")},remove:function(){ob(nb(arguments),"remove")}};E={abort:q(function(){c._wD(this._s.id+": abort")}),canplay:q(function(){var a=this._s,d;if(a._html5_canplay)return!0;a._html5_canplay=!0;c._wD(a.id+": canplay");a._onbufferchange(0);d=a._iO.position!==g&&!isNaN(a._iO.position)?a._iO.position/
+1E3:null;if(a.position&&this.currentTime!==d){c._wD(a.id+": canplay: Setting position to "+d);try{this.currentTime=d}catch(e){c._wD(a.id+": canplay: Setting position of "+d+" failed: "+e.message,2)}}a._iO._oncanplay&&a._iO._oncanplay()}),canplaythrough:q(function(){var a=this._s;a.loaded||(a._onbufferchange(0),a._whileloading(a.bytesLoaded,a.bytesTotal,a._get_html5_duration()),a._onload(!0))}),ended:q(function(){var a=this._s;c._wD(a.id+": ended");a._onfinish()}),error:q(function(){c._wD(this._s.id+
+": HTML5 error, code "+this.error.code);this._s._onload(!1)}),loadeddata:q(function(){var a=this._s;c._wD(a.id+": loadeddata");!a._loaded&&!Ia&&(a.duration=a._get_html5_duration())}),loadedmetadata:q(function(){c._wD(this._s.id+": loadedmetadata")}),loadstart:q(function(){c._wD(this._s.id+": loadstart");this._s._onbufferchange(1)}),play:q(function(){c._wD(this._s.id+": play()");this._s._onbufferchange(0)}),playing:q(function(){c._wD(this._s.id+": playing");this._s._onbufferchange(0)}),progress:q(function(a){var d=
+this._s,e,b,f;e=0;var g="progress"===a.type,h=a.target.buffered,m=a.loaded||0,j=a.total||1;d.buffered=[];if(h&&h.length){e=0;for(b=h.length;e<b;e++)d.buffered.push({start:1E3*h.start(e),end:1E3*h.end(e)});e=1E3*(h.end(0)-h.start(0));m=e/(1E3*a.target.duration);if(g&&1<h.length){f=[];b=h.length;for(e=0;e<b;e++)f.push(1E3*a.target.buffered.start(e)+"-"+1E3*a.target.buffered.end(e));c._wD(this._s.id+": progress, timeRanges: "+f.join(", "))}g&&!isNaN(m)&&c._wD(this._s.id+": progress, "+Math.floor(100*
+m)+"% loaded")}isNaN(m)||(d._onbufferchange(0),d._whileloading(m,j,d._get_html5_duration()),m&&(j&&m===j)&&E.canplaythrough.call(this,a))}),ratechange:q(function(){c._wD(this._s.id+": ratechange")}),suspend:q(function(a){var d=this._s;c._wD(this._s.id+": suspend");E.progress.call(this,a);d._onsuspend()}),stalled:q(function(){c._wD(this._s.id+": stalled")}),timeupdate:q(function(){this._s._onTimer()}),waiting:q(function(){var a=this._s;c._wD(this._s.id+": waiting");a._onbufferchange(1)})};la=function(a){return a.serverURL||
+a.type&&ba(a.type)?!1:a.type?Z({type:a.type}):Z({url:a.url})||c.html5Only};Da=function(a,c){a&&(a.src=c,a._called_load=!1);D&&(Fa=null)};Z=function(a){if(!c.useHTML5Audio||!c.hasHTML5)return!1;var d=a.url||null,a=a.type||null,e=c.audioFormats,b;if(a&&c.html5[a]!==g)return c.html5[a]&&!ba(a);if(!G){G=[];for(b in e)e.hasOwnProperty(b)&&(G.push(b),e[b].related&&(G=G.concat(e[b].related)));G=RegExp("\\.("+G.join("|")+")(\\?.*)?$","i")}b=d?d.toLowerCase().match(G):null;!b||!b.length?a&&(d=a.indexOf(";"),
+b=(-1!==d?a.substr(0,d):a).substr(6)):b=b[1];b&&c.html5[b]!==g?d=c.html5[b]&&!ba(b):(a="audio/"+b,d=c.html5.canPlayType({type:a}),d=(c.html5[b]=d)&&c.html5[a]&&!ba(a));return d};bb=function(){function a(a){var b,e,f=b=!1;if(!d||"function"!==typeof d.canPlayType)return b;if(a instanceof Array){b=0;for(e=a.length;b<e;b++)if(c.html5[a[b]]||d.canPlayType(a[b]).match(c.html5Test))f=!0,c.html5[a[b]]=!0,c.flash[a[b]]=!!a[b].match(ib);b=f}else a=d&&"function"===typeof d.canPlayType?d.canPlayType(a):!1,b=
+!(!a||!a.match(c.html5Test));return b}if(!c.useHTML5Audio||!c.hasHTML5)return!1;var d=Audio!==g?Ja&&10>opera.version()?new Audio(null):new Audio:null,e,b,f={},h;h=c.audioFormats;for(e in h)if(h.hasOwnProperty(e)&&(b="audio/"+e,f[e]=a(h[e].type),f[b]=f[e],e.match(ib)?(c.flash[e]=!0,c.flash[b]=!0):(c.flash[e]=!1,c.flash[b]=!1),h[e]&&h[e].related))for(b=h[e].related.length-1;0<=b;b--)f["audio/"+h[e].related[b]]=f[e],c.html5[h[e].related[b]]=f[e],c.flash[h[e].related[b]]=f[e];f.canPlayType=d?a:null;c.html5=
+v(c.html5,f);return!0};F={notReady:"Unavailable - wait until onready() has fired.",notOK:"Audio support is not available.",domError:"soundManagerexception caught while appending SWF to DOM.",spcWmode:"Removing wmode, preventing known SWF loading issue(s)",swf404:"soundManager: Verify that %s is a valid path.",tryDebug:"Try soundManager.debugFlash = true for more security details (output goes to SWF.)",checkSWF:"See SWF output for more debug info.",localFail:"soundManager: Non-HTTP page ("+h.location.protocol+
+" URL?) Review Flash player security settings for this special case:\nhttp://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html\nMay need to add/allow path, eg. c:/sm2/ or /users/me/sm2/",waitFocus:"soundManager: Special case: Waiting for SWF to load with window focus...",waitForever:"soundManager: Waiting indefinitely for Flash (will recover if unblocked)...",waitSWF:"soundManager: Waiting for 100% SWF load...",needFunction:"soundManager: Function object expected for %s",
+badID:'Warning: Sound ID "%s" should be a string, starting with a non-numeric character',currentObj:"soundManager: _debug(): Current sound objects",waitOnload:"soundManager: Waiting for window.onload()",docLoaded:"soundManager: Document already loaded",onload:"soundManager: initComplete(): calling soundManager.onload()",onloadOK:"soundManager.onload() complete",didInit:"soundManager: init(): Already called?",secNote:"Flash security note: Network/internet URLs will not load due to security restrictions. Access can be configured via Flash Player Global Security Settings Page: http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html",
+badRemove:"soundManager: Failed to remove Flash node.",shutdown:"soundManager.disable(): Shutting down",queue:"soundManager: Queueing %s handler",smError:"SMSound.load(): Exception: JS-Flash communication failed, or JS error.",fbTimeout:"No flash response, applying .swf_timedout CSS...",fbLoaded:"Flash loaded",flRemoved:"soundManager: Flash movie removed.",fbHandler:"soundManager: flashBlockHandler()",manURL:"SMSound.load(): Using manually-assigned URL",onURL:"soundManager.load(): current URL already assigned.",
+badFV:'soundManager.flashVersion must be 8 or 9. "%s" is invalid. Reverting to %s.',as2loop:"Note: Setting stream:false so looping can work (flash 8 limitation)",noNSLoop:"Note: Looping not implemented for MovieStar formats",needfl9:"Note: Switching to flash 9, required for MP4 formats.",mfTimeout:"Setting flashLoadTimeout = 0 (infinite) for off-screen, mobile flash case",needFlash:"soundManager: Fatal error: Flash is needed to play some required formats, but is not available.",gotFocus:"soundManager: Got window focus.",
+policy:"Enabling usePolicyFile for data access",setup:"soundManager.setup(): allowed parameters: %s",setupError:'soundManager.setup(): "%s" cannot be assigned with this method.',setupUndef:'soundManager.setup(): Could not find option "%s"',setupLate:"soundManager.setup(): url, flashVersion and html5Test property changes will not take effect until reboot().",noURL:"soundManager: Flash URL required. Call soundManager.setup({url:...}) to get started.",sm2Loaded:"SoundManager 2: Ready.",reset:"soundManager.reset(): Removing event callbacks",
+mobileUA:"Mobile UA detected, preferring HTML5 by default.",globalHTML5:"Using singleton HTML5 Audio() pattern for this device."};p=function(){var a=cb.call(arguments),c=a.shift(),c=F&&F[c]?F[c]:"",e,b;if(c&&a&&a.length){e=0;for(b=a.length;e<b;e++)c=c.replace("%s",a[e])}return c};ha=function(a){8===l&&(1<a.loops&&a.stream)&&(k("as2loop"),a.stream=!1);return a};ia=function(a,d){if(a&&!a.usePolicyFile&&(a.onid3||a.usePeakData||a.useWaveformData||a.useEQData))c._wD((d||"")+p("policy")),a.usePolicyFile=
+!0;return a};Q=function(a){console!==g&&console.warn!==g?console.warn(a):c._wD(a)};qa=function(){return!1};Va=function(a){for(var c in a)a.hasOwnProperty(c)&&"function"===typeof a[c]&&(a[c]=qa)};za=function(a){a===g&&(a=!1);(r||a)&&c.disable(a)};Wa=function(a){var d=null;if(a)if(a.match(/\.swf(\?.*)?$/i)){if(d=a.substr(a.toLowerCase().lastIndexOf(".swf?")+4))return a}else a.lastIndexOf("/")!==a.length-1&&(a+="/");a=(a&&-1!==a.lastIndexOf("/")?a.substr(0,a.lastIndexOf("/")+1):"./")+c.movieURL;c.noSWFCache&&
+(a+="?ts="+(new Date).getTime());return a};va=function(){l=parseInt(c.flashVersion,10);8!==l&&9!==l&&(c._wD(p("badFV",l,8)),c.flashVersion=l=8);var a=c.debugMode||c.debugFlash?"_debug.swf":".swf";c.useHTML5Audio&&(!c.html5Only&&c.audioFormats.mp4.required&&9>l)&&(c._wD(p("needfl9")),c.flashVersion=l=9);c.version=c.versionNumber+(c.html5Only?" (HTML5-only mode)":9===l?" (AS3/Flash 9)":" (AS2/Flash 8)");8<l?(c.defaultOptions=v(c.defaultOptions,c.flash9Options),c.features.buffering=!0,c.defaultOptions=
+v(c.defaultOptions,c.movieStarOptions),c.filePatterns.flash9=RegExp("\\.(mp3|"+lb.join("|")+")(\\?.*)?$","i"),c.features.movieStar=!0):c.features.movieStar=!1;c.filePattern=c.filePatterns[8!==l?"flash9":"flash8"];c.movieURL=(8===l?"soundmanager2.swf":"soundmanager2_flash9.swf").replace(".swf",a);c.features.peakData=c.features.waveformData=c.features.eqData=8<l};Ta=function(a,c){if(!i)return!1;i._setPolling(a,c)};ya=function(){c.debugURLParam.test(R)&&(c.debugMode=!0);if(A(c.debugID))return!1;var a,
+d,e,b;if(c.debugMode&&!A(c.debugID)&&(!gb||!c.useConsole||!c.consoleOnly)){a=h.createElement("div");a.id=c.debugID+"-toggle";d={position:"fixed",bottom:"0px",right:"0px",width:"1.2em",height:"1.2em",lineHeight:"1.2em",margin:"2px",textAlign:"center",border:"1px solid #999",cursor:"pointer",background:"#fff",color:"#333",zIndex:10001};a.appendChild(h.createTextNode("-"));a.onclick=Xa;a.title="Toggle SM2 debug console";s.match(/msie 6/i)&&(a.style.position="absolute",a.style.cursor="hand");for(b in d)d.hasOwnProperty(b)&&
+(a.style[b]=d[b]);d=h.createElement("div");d.id=c.debugID;d.style.display=c.debugMode?"block":"none";if(c.debugMode&&!A(a.id)){try{e=fa(),e.appendChild(a)}catch(f){throw Error(p("domError")+" \n"+f.toString());}e.appendChild(d)}}};t=this.getSoundById;k=function(a,d){return!a?"":c._wD(p(a),d)};Xa=function(){var a=A(c.debugID),d=A(c.debugID+"-toggle");if(!a)return!1;sa?(d.innerHTML="+",a.style.display="none"):(d.innerHTML="-",a.style.display="block");sa=!sa};x=function(a,c,e){if(j.sm2Debugger!==g)try{sm2Debugger.handleEvent(a,
+c,e)}catch(b){}return!0};P=function(){var a=[];c.debugMode&&a.push("sm2_debug");c.debugFlash&&a.push("flash_debug");c.useHighPerformance&&a.push("high_performance");return a.join(" ")};Ba=function(){var a=p("fbHandler"),d=c.getMoviePercent(),e={type:"FLASHBLOCK"};if(c.html5Only)return!1;c.ok()?(c.didFlashBlock&&c._wD(a+": Unblocked"),c.oMC&&(c.oMC.className=[P(),"movieContainer","swf_loaded"+(c.didFlashBlock?" swf_unblocked":"")].join(" "))):(B&&(c.oMC.className=P()+" movieContainer "+(null===d?"swf_timedout":
+"swf_error"),c._wD(a+": "+p("fbTimeout")+(d?" ("+p("fbLoaded")+")":""))),c.didFlashBlock=!0,I({type:"ontimeout",ignoreInit:!0,error:e}),O(e))};ua=function(a,c,e){C[a]===g&&(C[a]=[]);C[a].push({method:c,scope:e||null,fired:!1})};I=function(a){a||(a={type:c.ok()?"onready":"ontimeout"});if(!n&&a&&!a.ignoreInit||"ontimeout"===a.type&&(c.ok()||r&&!a.ignoreInit))return!1;var d={success:a&&a.ignoreInit?c.ok():!r},e=a&&a.type?C[a.type]||[]:[],b=[],f,d=[d],g=B&&!c.ok();a.error&&(d[0].error=a.error);a=0;for(f=
+e.length;a<f;a++)!0!==e[a].fired&&b.push(e[a]);if(b.length){a=0;for(f=b.length;a<f;a++)b[a].scope?b[a].method.apply(b[a].scope,d):b[a].method.apply(this,d),g||(b[a].fired=!0)}return!0};L=function(){j.setTimeout(function(){c.useFlashBlock&&Ba();I();"function"===typeof c.onload&&(k("onload",1),c.onload.apply(j),k("onloadOK",1));c.waitForWindowLoad&&u.add(j,"load",L)},1)};Ga=function(){if(z!==g)return z;var a=!1,c=navigator,e=c.plugins,b,f=j.ActiveXObject;if(e&&e.length)(c=c.mimeTypes)&&(c["application/x-shockwave-flash"]&&
+c["application/x-shockwave-flash"].enabledPlugin&&c["application/x-shockwave-flash"].enabledPlugin.description)&&(a=!0);else if(f!==g&&!s.match(/MSAppHost/i)){try{b=new f("ShockwaveFlash.ShockwaveFlash")}catch(h){}a=!!b}return z=a};ab=function(){var a,d,e=c.audioFormats;if(ma&&s.match(/os (1|2|3_0|3_1)/i))c.hasHTML5=!1,c.html5Only=!0,c.oMC&&(c.oMC.style.display="none");else if(c.useHTML5Audio){if(!c.html5||!c.html5.canPlayType)c._wD("SoundManager: No HTML5 Audio() support detected."),c.hasHTML5=!1;
+La&&c._wD("soundManager: Note: Buggy HTML5 Audio in Safari on this OS X release, see https://bugs.webkit.org/show_bug.cgi?id=32159 - "+(!z?" would use flash fallback for MP3/MP4, but none detected.":"will use flash fallback for MP3/MP4, if available"),1)}if(c.useHTML5Audio&&c.hasHTML5)for(d in e)if(e.hasOwnProperty(d)&&(e[d].required&&!c.html5.canPlayType(e[d].type)||c.preferFlash&&(c.flash[d]||c.flash[e[d].type])))a=!0;c.ignoreFlash&&(a=!1);c.html5Only=c.hasHTML5&&c.useHTML5Audio&&!a;return!c.html5Only};
+ka=function(a){var d,e,b=0;if(a instanceof Array){d=0;for(e=a.length;d<e;d++)if(a[d]instanceof Object){if(c.canPlayMIME(a[d].type)){b=d;break}}else if(c.canPlayURL(a[d])){b=d;break}a[b].url&&(a[b]=a[b].url);a=a[b]}return a};Ya=function(a){a._hasTimer||(a._hasTimer=!0,!Ka&&c.html5PollingInterval&&(null===Y&&0===ja&&(Y=j.setInterval($a,c.html5PollingInterval)),ja++))};Za=function(a){a._hasTimer&&(a._hasTimer=!1,!Ka&&c.html5PollingInterval&&ja--)};$a=function(){var a;if(null!==Y&&!ja)return j.clearInterval(Y),
+Y=null,!1;for(a=c.soundIDs.length-1;0<=a;a--)c.sounds[c.soundIDs[a]].isHTML5&&c.sounds[c.soundIDs[a]]._hasTimer&&c.sounds[c.soundIDs[a]]._onTimer()};O=function(a){a=a!==g?a:{};"function"===typeof c.onerror&&c.onerror.apply(j,[{type:a.type!==g?a.type:null}]);a.fatal!==g&&a.fatal&&c.disable()};db=function(){if(!La||!Ga())return!1;var a=c.audioFormats,d,e;for(e in a)if(a.hasOwnProperty(e)&&("mp3"===e||"mp4"===e))if(c._wD("soundManager: Using flash fallback for "+e+" format"),c.html5[e]=!1,a[e]&&a[e].related)for(d=
+a[e].related.length-1;0<=d;d--)c.html5[a[e].related[d]]=!1};this._setSandboxType=function(a){var d=c.sandbox;d.type=a;d.description=d.types[d.types[a]!==g?a:"unknown"];"localWithFile"===d.type?(d.noRemote=!0,d.noLocal=!1,k("secNote",2)):"localWithNetwork"===d.type?(d.noRemote=!1,d.noLocal=!0):"localTrusted"===d.type&&(d.noRemote=!1,d.noLocal=!1)};this._externalInterfaceOK=function(a,d){if(c.swfLoaded)return!1;var e;x("swf",!0);x("flashtojs",!0);c.swfLoaded=!0;na=!1;La&&db();if(!d||d.replace(/\+dev/i,
+"")!==c.versionNumber.replace(/\+dev/i,""))return e='soundManager: Fatal: JavaScript file build "'+c.versionNumber+'" does not match Flash SWF build "'+d+'" at '+c.url+". Ensure both are up-to-date.",setTimeout(function(){throw Error(e);},0),!1;setTimeout(ra,H?100:1)};ga=function(a,d){function e(){var a=[],b,d=[];b="SoundManager "+c.version+(!c.html5Only&&c.useHTML5Audio?c.hasHTML5?" + HTML5 audio":", no HTML5 audio support":"");c.html5Only?c.html5PollingInterval&&a.push("html5PollingInterval ("+
+c.html5PollingInterval+"ms)"):(c.preferFlash&&a.push("preferFlash"),c.useHighPerformance&&a.push("useHighPerformance"),c.flashPollingInterval&&a.push("flashPollingInterval ("+c.flashPollingInterval+"ms)"),c.html5PollingInterval&&a.push("html5PollingInterval ("+c.html5PollingInterval+"ms)"),c.wmode&&a.push("wmode ("+c.wmode+")"),c.debugFlash&&a.push("debugFlash"),c.useFlashBlock&&a.push("flashBlock"));a.length&&(d=d.concat([a.join(" + ")]));c._wD(b+(d.length?" + "+d.join(", "):""),1);eb()}function b(a,
+b){return'<param name="'+a+'" value="'+b+'" />'}if(S&&T)return!1;if(c.html5Only)return va(),e(),c.oMC=A(c.movieID),ra(),T=S=!0,!1;var f=d||c.url,j=c.altURL||f,i=fa(),m=P(),l=null,l=h.getElementsByTagName("html")[0],k,q,n,l=l&&l.dir&&l.dir.match(/rtl/i),a=a===g?c.id:a;va();c.url=Wa($?f:j);d=c.url;c.wmode=!c.wmode&&c.useHighPerformance?"transparent":c.wmode;if(null!==c.wmode&&(s.match(/msie 8/i)||!H&&!c.useHighPerformance)&&navigator.platform.match(/win32|win64/i))J.push(F.spcWmode),c.wmode=null;i=
+{name:a,id:a,src:d,quality:"high",allowScriptAccess:c.allowScriptAccess,bgcolor:c.bgColor,pluginspage:jb+"www.macromedia.com/go/getflashplayer",title:"JS/Flash audio component (SoundManager 2)",type:"application/x-shockwave-flash",wmode:c.wmode,hasPriority:"true"};c.debugFlash&&(i.FlashVars="debug=1");c.wmode||delete i.wmode;if(H)f=h.createElement("div"),q=['<object id="'+a+'" data="'+d+'" type="'+i.type+'" title="'+i.title+'" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="'+jb+'download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0">',
+b("movie",d),b("AllowScriptAccess",c.allowScriptAccess),b("quality",i.quality),c.wmode?b("wmode",c.wmode):"",b("bgcolor",c.bgColor),b("hasPriority","true"),c.debugFlash?b("FlashVars",i.FlashVars):"","</object>"].join("");else for(k in f=h.createElement("embed"),i)i.hasOwnProperty(k)&&f.setAttribute(k,i[k]);ya();m=P();if(i=fa())if(c.oMC=A(c.movieID)||h.createElement("div"),c.oMC.id)n=c.oMC.className,c.oMC.className=(n?n+" ":"movieContainer")+(m?" "+m:""),c.oMC.appendChild(f),H&&(k=c.oMC.appendChild(h.createElement("div")),
+k.className="sm2-object-box",k.innerHTML=q),T=!0;else{c.oMC.id=c.movieID;c.oMC.className="movieContainer "+m;k=m=null;c.useFlashBlock||(c.useHighPerformance?m={position:"fixed",width:"8px",height:"8px",bottom:"0px",left:"0px",overflow:"hidden"}:(m={position:"absolute",width:"6px",height:"6px",top:"-9999px",left:"-9999px"},l&&(m.left=Math.abs(parseInt(m.left,10))+"px")));rb&&(c.oMC.style.zIndex=1E4);if(!c.debugFlash)for(n in m)m.hasOwnProperty(n)&&(c.oMC.style[n]=m[n]);try{H||c.oMC.appendChild(f),
+i.appendChild(c.oMC),H&&(k=c.oMC.appendChild(h.createElement("div")),k.className="sm2-object-box",k.innerHTML=q),T=!0}catch(r){throw Error(p("domError")+" \n"+r.toString());}}S=!0;e();return!0};ea=function(){if(c.html5Only)return ga(),!1;if(i)return!1;if(!c.url)return k("noURL"),!1;i=c.getMovie(c.id);i||(W?(H?c.oMC.innerHTML=Aa:c.oMC.appendChild(W),W=null,S=!0):ga(c.id,c.url),i=c.getMovie(c.id));"function"===typeof c.oninitmovie&&setTimeout(c.oninitmovie,1);Ha();return!0};M=function(){setTimeout(Sa,
+1E3)};Sa=function(){var a,d=!1;if(!c.url||X)return!1;X=!0;u.remove(j,"load",M);if(na&&!Ma)return k("waitFocus"),!1;n||(a=c.getMoviePercent(),0<a&&100>a&&(d=!0));setTimeout(function(){a=c.getMoviePercent();if(d)return X=!1,c._wD(p("waitSWF")),j.setTimeout(M,1),!1;n||(c._wD("soundManager: No Flash response within expected time. Likely causes: "+(0===a?"SWF load failed, ":"")+"Flash blocked or JS-Flash security error."+(c.debugFlash?" "+p("checkSWF"):""),2),!$&&a&&(k("localFail",2),c.debugFlash||k("tryDebug",
+2)),0===a&&c._wD(p("swf404",c.url),1),x("flashtojs",!1,": Timed out"+$?" (Check flash security or flash blockers)":" (No plugin/missing SWF?)"));!n&&hb&&(null===a?c.useFlashBlock||0===c.flashLoadTimeout?(c.useFlashBlock&&Ba(),k("waitForever")):(k("waitForever"),I({type:"ontimeout",ignoreInit:!0})):0===c.flashLoadTimeout?k("waitForever"):za(!0))},c.flashLoadTimeout)};da=function(){if(Ma||!na)return u.remove(j,"focus",da),!0;Ma=hb=!0;k("gotFocus");X=!1;M();u.remove(j,"focus",da);return!0};Ha=function(){J.length&&
+(c._wD("SoundManager 2: "+J.join(" "),1),J=[])};eb=function(){Ha();var a,d=[];if(c.useHTML5Audio&&c.hasHTML5){for(a in c.audioFormats)c.audioFormats.hasOwnProperty(a)&&d.push(a+" = "+c.html5[a]+(!c.html5[a]&&z&&c.flash[a]?" (using flash)":c.preferFlash&&c.flash[a]&&z?" (preferring flash)":!c.html5[a]?" ("+(c.audioFormats[a].required?"required, ":"")+"and no flash support)":""));c._wD("SoundManager 2 HTML5 support: "+d.join(", "),1)}};U=function(a){if(n)return!1;if(c.html5Only)return k("sm2Loaded"),
+n=!0,L(),x("onload",!0),!0;var d=!0,e;if(!c.useFlashBlock||!c.flashLoadTimeout||c.getMoviePercent())n=!0,r&&(e={type:!z&&B?"NO_FLASH":"INIT_TIMEOUT"});c._wD("SoundManager 2 "+(r?"failed to load":"loaded")+" ("+(r?"Flash security/load error":"OK")+")",r?2:1);r||a?(c.useFlashBlock&&c.oMC&&(c.oMC.className=P()+" "+(null===c.getMoviePercent()?"swf_timedout":"swf_error")),I({type:"ontimeout",error:e,ignoreInit:!0}),x("onload",!1),O(e),d=!1):x("onload",!0);r||(c.waitForWindowLoad&&!ca?(k("waitOnload"),
+u.add(j,"load",L)):(c.waitForWindowLoad&&ca&&k("docLoaded"),L()));return d};Ra=function(){var a,d=c.setupOptions;for(a in d)d.hasOwnProperty(a)&&(c[a]===g?c[a]=d[a]:c[a]!==d[a]&&(c.setupOptions[a]=c[a]))};ra=function(){if(n)return k("didInit"),!1;if(c.html5Only)return n||(u.remove(j,"load",c.beginDelayedInit),c.enabled=!0,U()),!0;ea();try{i._externalInterfaceTest(!1),Ta(!0,c.flashPollingInterval||(c.useHighPerformance?10:50)),c.debugMode||i._disableDebug(),c.enabled=!0,x("jstoflash",!0),c.html5Only||
+u.add(j,"unload",qa)}catch(a){return c._wD("js/flash exception: "+a.toString()),x("jstoflash",!1),O({type:"JS_TO_FLASH_EXCEPTION",fatal:!0}),za(!0),U(),!1}U();u.remove(j,"load",c.beginDelayedInit);return!0};N=function(){if(V)return!1;V=!0;Ra();ya();var a=null,a=null,d=j.console!==g&&"function"===typeof console.log,e=R.toLowerCase();-1!==e.indexOf("sm2-usehtml5audio=")&&(a="1"===e.charAt(e.indexOf("sm2-usehtml5audio=")+18),d&&console.log((a?"Enabling ":"Disabling ")+"useHTML5Audio via URL parameter"),
+c.setup({useHTML5Audio:a}));-1!==e.indexOf("sm2-preferflash=")&&(a="1"===e.charAt(e.indexOf("sm2-preferflash=")+16),d&&console.log((a?"Enabling ":"Disabling ")+"preferFlash via URL parameter"),c.setup({preferFlash:a}));!z&&c.hasHTML5&&(c._wD("SoundManager: No Flash detected"+(!c.useHTML5Audio?", enabling HTML5.":". Trying HTML5-only mode."),1),c.setup({useHTML5Audio:!0,preferFlash:!1}));bb();c.html5.usingFlash=ab();B=c.html5.usingFlash;!z&&B&&(J.push(F.needFlash),c.setup({flashLoadTimeout:1}));h.removeEventListener&&
+h.removeEventListener("DOMContentLoaded",N,!1);ea();return!0};Ea=function(){"complete"===h.readyState&&(N(),h.detachEvent("onreadystatechange",Ea));return!0};xa=function(){ca=!0;u.remove(j,"load",xa)};wa=function(){if(Ka&&((!c.setupOptions.useHTML5Audio||c.setupOptions.preferFlash)&&J.push(F.mobileUA),c.setupOptions.useHTML5Audio=!0,c.setupOptions.preferFlash=!1,ma||fb&&!s.match(/android\s2\.3/i)))J.push(F.globalHTML5),ma&&(c.ignoreFlash=!0),D=!0};wa();Ga();u.add(j,"focus",da);u.add(j,"load",M);u.add(j,
+"load",xa);h.addEventListener?h.addEventListener("DOMContentLoaded",N,!1):h.attachEvent?h.attachEvent("onreadystatechange",Ea):(x("onload",!1),O({type:"NO_DOM2_EVENTS",fatal:!0}))}var pa=null;if(void 0===j.SM2_DEFER||!SM2_DEFER)pa=new aa;j.SoundManager=aa;j.soundManager=pa})(window);

BIN
demo/pacman/sound/dies.mp3


BIN
demo/pacman/sound/eatingfruit.mp3


BIN
demo/pacman/sound/eatingghost.mp3


BIN
demo/pacman/sound/opening.mp3


BIN
demo/pacman/sound/siren.mp3


BIN
demo/pacman/sound/wakawaka.mp3


BIN
demo/pacman/swf/soundmanager2.swf


BIN
demo/pacman/swf/soundmanager2_debug.swf


BIN
demo/pacman/swf/soundmanager2_flash9.swf


BIN
demo/pacman/swf/soundmanager2_flash9_debug.swf


+ 7 - 0
demo/pyramid/MIT-LICENCE.txt

@@ -0,0 +1,7 @@
+Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 23 - 0
demo/pyramid/README.md

@@ -0,0 +1,23 @@
+Pyramid is a jQuery implementation of the famous Pyramid solitaire game card.
+
+
+How to :
+--------
+
+The goal is to remove all Pyramid's cards.
+To remove a card, it must be associated with an other one and their sum must be equals to 13.
+Each card has is numeric value and Kings' value is 13 (no need to be associated with an other card), Queens' value is 12 and Jake' value is 11.
+
+A live demo is avalaible [here](http://www.viadeo-playground.com/pyramid/index)
+
+Credits :
+---------
+
+* Graphics : [Byron Knoll](http://code.google.com/p/vector-playing-cards)
+* Code : Fabrice Ecaille aka Febbweiss
+
+Licences :
+----------
+
+Source code is under [MIT Licence](http://opensource.org/licenses/mit-license.php)
+Cards sprite is under [Public domain](http://creativecommons.org/publicdomain/zero/1.0/)

+ 184 - 0
demo/pyramid/css/cards.css

@@ -0,0 +1,184 @@
+.card {
+	position: absolute;
+	width: 80px;
+	height: 124px;
+	background-image: url('../images/cards.png');
+	float: left;
+}
+
+.card.selected {
+	border: 2px solid yellow;
+}
+
+.clubs_1 {
+	background-position: 0px 0px;
+}
+.clubs_2 {
+	background-position: -80px 0px;
+}
+.clubs_3 {
+	background-position: -160px 0px;
+}
+.clubs_4 {
+	background-position: -240px 0px;
+}
+.clubs_5 {
+	background-position: -320px 0px;
+}
+.clubs_6 {
+	background-position: -400px 0px;
+}
+.clubs_7 {
+	background-position: -480px 0px;
+}
+.clubs_8 {
+	background-position: -560px 0px;
+}
+.clubs_9 {
+	background-position: -640px 0px;
+}
+.clubs_10 {
+	background-position: -720px 0px;
+}
+.clubs_11 {
+	background-position: -800px 0px;
+}
+.clubs_12 {
+	background-position: -880px 0px;
+}
+.clubs_13 {
+	background-position: -960px 0px;
+}
+
+.diamonds_1 {
+	background-position: 0px -125px;
+}
+.diamonds_2 {
+	background-position: -80px -125px;
+}
+.diamonds_3 {
+	background-position: -160px -125px;
+}
+.diamonds_4 {
+	background-position: -240px -125px;
+}
+.diamonds_5 {
+	background-position: -320px -125px;
+}
+.diamonds_6 {
+	background-position: -400px -125px;
+}
+.diamonds_7 {
+	background-position: -480px -125px;
+}
+.diamonds_8 {
+	background-position: -560px -125px;
+}
+.diamonds_9 {
+	background-position: -640px -125px;
+}
+.diamonds_10 {
+	background-position: -720px -125px;
+}
+.diamonds_11 {
+	background-position: -800px -125px;
+}
+.diamonds_12 {
+	background-position: -880px -125px;
+}
+.diamonds_13 {
+	background-position: -960px -125px;
+}
+
+.hearts_1 {
+	background-position: 0px -249px;
+}
+.hearts_2 {
+	background-position: -80px -249px;
+}
+.hearts_3 {
+	background-position: -160px -249px;
+}
+.hearts_4 {
+	background-position: -240px -249px;
+}
+.hearts_5 {
+	background-position: -320px -249px;
+}
+.hearts_6 {
+	background-position: -400px -249px;
+}
+.hearts_7 {
+	background-position: -480px -249px;
+}
+.hearts_8 {
+	background-position: -560px -249px;
+}
+.hearts_9 {
+	background-position: -640px -249px;
+}
+.hearts_10 {
+	background-position: -720px -249px;
+}
+.hearts_11 {
+	background-position: -800px -249px;
+}
+.hearts_12 {
+	background-position: -880px -249px;
+}
+.hearts_13 {
+	background-position: -960px -249px;
+}
+
+.spades_1 {
+	background-position: 0px -374px;
+}
+.spades_2 {
+	background-position: -80px -374px;
+}
+.spades_3 {
+	background-position: -160px -374px;
+}
+.spades_4 {
+	background-position: -240px -374px;
+}
+.spades_5 {
+	background-position: -320px -374px;
+}
+.spades_6 {
+	background-position: -400px -374px;
+}
+.spades_7 {
+	background-position: -480px -374px;
+}
+.spades_8 {
+	background-position: -560px -374px;
+}
+.spades_9 {
+	background-position: -640px -374px;
+}
+.spades_10 {
+	background-position: -720px -374px;
+}
+.spades_11 {
+	background-position: -800px -374px;
+}
+.spades_12 {
+	background-position: -880px -374px;
+}
+.spades_13 {
+	background-position: -960px -374px;
+}
+
+.joker_black {
+	background-position: 0px -498px;
+}
+.joker_red {
+	background-position: -80px -498px;
+}
+.background {
+	background-position: -160px -498px;
+}
+.none {
+	background-position: -240px -498px;
+}

BIN
demo/pyramid/images/cards.png


+ 35 - 0
demo/pyramid/index.html

@@ -0,0 +1,35 @@
+<!--
+Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+-->
+<html>
+	<head>
+		<title>Pyramid.js</title>
+		<script src="lib/jquery-1.7.1.min.js" type="text/javascript"></script>
+		<script src="js/pyramid.js" type="text/javascript"></script>
+		<script src="js/cards.js" type="text/javascript"></script>
+		<link href="css/cards.css" type="text/css"rel="stylesheet">
+		<link href="../../extra/css/extra.css" type="text/css" rel="stylesheet" media="screen, projection" />
+		<script src="../../extra/js/pyramid.js" type="text/javascript" ></script>
+	</head>
+	<body>
+		
+		<div id="playground" style="height: 370px">
+			<div id="stock" class="card background" style="position: absolute; left: 0;"></div>
+			<img id="drawn" class="card none"style="position: absolute; right: 0;" />
+		</div>
+		<div style="text-align: center;margin-top: 00px;">
+			<button id="startBtn" class="push--skeuo">Start</button>
+		</div>
+		<script language="javascript">
+			$(document).ready(function () {
+				Pyramid.init();
+			});
+		</script>
+	</body>
+</html>

+ 59 - 0
demo/pyramid/js/cards.js

@@ -0,0 +1,59 @@
+/*
+Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+var CARD_WIDTH = 80;
+var CARD_HEIGHT = 124;
+
+var Deck = {
+	cards: [
+	        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+	        14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
+	        27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
+	        40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52
+	        ],
+	        
+	values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],
+	
+	value: function(card) {
+		return card % Deck.values.length + 1;
+	},
+
+	familly: function( card ) {
+		if( card < 14 )
+			return "clubs";
+		else if( card < 27 )
+			return "diamonds";
+		else if( card < 40 )
+			return "hearts";
+		else
+			return "spades";
+	},
+	
+	point: function(card) {
+		return Deck.values[Deck.value(card) - 1];
+	},
+	
+	shuffle: function() {
+		for (var i = 0; i < Deck.cards.length; ++i) {
+			var x = parseInt(Math.random() * Deck.cards.length);
+			var y = parseInt(Math.random() * Deck.cards.length);
+			if (x == y)
+				continue;
+			var tmp = Deck.cards[x];
+			Deck.cards[x] = Deck.cards[y];
+			Deck.cards[y] = tmp;
+		}
+		return Deck.cards;
+	},
+	
+	pop: function() {
+		return Deck.cards.pop();
+	}
+}

+ 185 - 0
demo/pyramid/js/pyramid.js

@@ -0,0 +1,185 @@
+/*
+Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+var Pyramid = {
+	initial_set: [1, 2, 3, 4, 5, 6, 7],
+	
+	pyramid: new Array(7),
+	
+	selected: null,
+	drawn: new Array(),
+	drawn_count: 0,
+	
+	init: function() {
+		Pyramid.pyramid = new Array(7);
+		Pyramid.selected = null;
+		Pyramid.drawn = new Array();
+		Pyramid.drawn_count = 0;
+		
+		var maxCard = Pyramid.initial_set[ Pyramid.initial_set.length - 1];
+		var initial_x = (maxCard * CARD_WIDTH) / 2;
+		
+		var initial_y = 0;
+		var playground = $("#playground");
+		playground.width( maxCard * CARD_WIDTH);
+		playground.height( Pyramid.initial_set.lentgh * CARD_HEIGHT);
+		
+		
+		Deck.shuffle();
+		
+		for( var i = 0; i < Pyramid.initial_set.length; i++) {
+			var cards = Pyramid.initial_set[i];
+			var offsetx = initial_x - cards * CARD_WIDTH / 2;
+			Pyramid.pyramid[i] = new Array(cards);
+			for( var j = 0; j < cards; j++ ) {
+				var value = Deck.pop();
+				Pyramid.pyramid[i][j] = value;
+				$("<img/>",{
+					id:"card" + i +j,
+					class: "card " + Deck.familly( value ) + "_" + Deck.value(value),
+					style: "left: " + offsetx + "px; top: " + ( i * CARD_HEIGHT / 3 ) + "px;"
+				}).appendTo(playground);
+				
+				offsetx += CARD_WIDTH;
+			}
+		}
+
+		$("#stock").width( CARD_WIDTH ).height( CARD_HEIGHT );
+		$("#drawn").width( CARD_WIDTH ).height( CARD_HEIGHT );
+		$("#control").width( CARD_WIDTH ).height( 2 * CARD_HEIGHT );
+		
+		$(".card").click( Pyramid.click );
+	},
+
+	click: function(e) {
+		var elt = $(this);
+		var id = elt.attr('id');
+		
+		if( id === "stock" ) {
+			Pyramid.draw();
+			return false;
+		}
+		
+		var card = Pyramid.card(elt);
+		
+		if( !card )
+			return false;
+		
+		var point = Deck.point(card);
+		
+		if( point == 13 ) {
+			Pyramid.remove(elt);
+			Pyramid.win();
+			return true;
+		}
+		
+		if( elt.hasClass("selected") ) {
+			elt.removeClass("selected");
+			Pyramid.selected = null;
+			return false;
+		} 
+		
+		var selected = Pyramid.selected; 
+		if( selected ) {
+			var card = Pyramid.card(Pyramid.selected);
+			
+			if( (point + Deck.point(card)) == 13 ) {
+				Pyramid.remove(elt);
+				Pyramid.remove( selected );
+				Pyramid.win();
+			} else {
+				selected.removeClass("selected");
+			}
+			
+			Pyramid.selected = null;
+		} else {
+			elt.addClass("selected");
+			Pyramid.selected = elt;
+		}
+	},
+	
+	next: function() {
+		var card = Deck.pop();
+		Pyramid.drawn.push( card );
+		$("#drawn").removeClass().addClass("card").addClass("card " + Deck.familly( card ) + "_" + Deck.value(card));
+		if( Deck.cards.length == 0 ) {
+			$("#stock").removeClass();
+			return false;
+		}
+	},
+	
+	card: function(elt) {
+		var id = elt.attr('id');
+	
+		if( id === "drawn" ) {
+			if( Pyramid.drawn.length > 0 ) {
+				return Pyramid.drawn[Pyramid.drawn.length - 1];
+			} else
+				return false;
+		} else {
+			var i = parseInt(id.charAt(4));
+			var j = parseInt(id.charAt(5));
+			
+			if( typeof Pyramid.pyramid[i + 1] != "undefined" 
+				&& ( typeof Pyramid.pyramid[i + 1][ j ] != "undefined" || typeof Pyramid.pyramid[i + 1][ j + 1] != "undefined") )
+				return false;
+			
+			return Pyramid.pyramid[i][j];
+		}
+	},
+	
+	remove: function(elt) {
+		var id = elt.attr('id');
+		
+		if( id === "drawn" ) {
+			Pyramid.drawn.pop();
+			$("#drawn").removeClass();
+			if( Pyramid.drawn.length > 0 ) {
+				card = Pyramid.drawn[Pyramid.drawn.length - 1];
+				$("#drawn").addClass("card").addClass("card " + Deck.familly( card ) + "_" + Deck.value(card));
+			}
+		} else {
+			var i = parseInt(id.charAt(4));
+			var j = parseInt(id.charAt(5));
+			Pyramid.pyramid[i][j] = undefined;
+			elt.remove();
+		}
+	},
+	
+	draw: function() {
+		if( Deck.cards.length > 0 ) {
+			Pyramid.drawn_count++;
+			Pyramid.next();
+			return false;
+		}
+		
+		Deck.cards = Pyramid.drawn.reverse();
+		Pyramid.drawn = new Array();
+		$("#drawn").removeClass();
+		$("#stock").addClass("card").addClass("background");
+		$("#")
+	},
+	
+	win: function() {
+		var win = true;
+		$.each($(".card"), function(index, elt) {
+			var id = $(elt).attr('id');
+			if(  id !== "stock" && id !== "drawn")
+				win = false;
+		});
+		if( win ) 
+			Pyramid.show_win();
+		return win;
+	},
+	
+	show_win: function() {
+		alert( "Win in " + Pyramid.drawn_count + " draws !!!");
+	}
+}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
demo/pyramid/lib/jquery-1.7.1.min.js


+ 1 - 0
demo/sis/.gitignore

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

+ 7 - 0
demo/sis/MIT-LICENCE.txt

@@ -0,0 +1,7 @@
+Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 63 - 0
demo/sis/css/countdown.css

@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#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;
+}

+ 64 - 0
demo/sis/css/scoreboard.css

@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+.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;
+}

+ 129 - 0
demo/sis/css/spaceinvaders.css

@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#playground {
+	background-color: black;
+}
+
+.alien {
+}
+
+.shipShot {
+	background-color: green;
+}
+
+.shipShot.carot {
+	background-color: orange;
+}
+
+.shipShot.shotgun {
+	background-color: grey;
+}
+
+.shipShot.shotCorn {
+	background-color: white;
+}
+
+.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 **/
+
+
+/** Modal **/
+.modal {
+	background-color: yellow;
+/** Modal - end **/

BIN
demo/sis/images/aliensprite.png


BIN
demo/sis/images/background.png


BIN
demo/sis/images/background2.png


BIN
demo/sis/images/explosion_big.png


BIN
demo/sis/images/explosion_small.png


BIN
demo/sis/images/farm.png


BIN
demo/sis/images/font.png


BIN
demo/sis/images/invader.png


BIN
demo/sis/images/ufo.png


+ 60 - 0
demo/sis/index.html

@@ -0,0 +1,60 @@
+<!--
+ Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+-->
+
+<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/animations/animations.js" type="text/javascript" ></script>
+		<script src="js/animations/aliens.js" type="text/javascript" ></script>
+		<script src="js/animations/explosions.js" type="text/javascript" ></script>
+		<script src="js/animations/worlds.js" type="text/javascript" ></script>
+
+		<script src="js/spaceinvaders-ui.js" type="text/javascript" ></script>
+		<script src="js/spaceinvaders-core.js" type="text/javascript" ></script>
+		<script src="js/models/weapons.js" type="text/javascript" ></script>
+		<script src="js/models/models.js" type="text/javascript" ></script>
+		<script src="js/models/aliens.js" type="text/javascript" ></script>
+		<script src="js/models/waves.js" type="text/javascript" ></script>
+		<script src="js/models/weapons.js" type="text/javascript" ></script>
+
+		<link href="css/spaceinvaders.css" type="text/css" rel="stylesheet"/>		
+		
+		<link href="../../extra/css/extra.css" type="text/css" rel="stylesheet" media="screen, projection" />
+		<script src="../../extra/js/sis.js" type="text/javascript" ></script>
+	</head>
+	<body>
+
+		<div id="playgroundContainer" style="height: 544;">
+			<div id="playground">
+				 <div id="welcomeScreen">
+					<div style="position: absolute; top: 120px; width: 700px; color: white;">
+						<div id="loadingBar"></div>
+					</div>
+				</div>
+			</div>
+		</div>
+		
+		<div style="width: 448px; text-align: center;margin-top: 00px;">
+			<button id="startBtn" class="push--skeuo">Start</button>
+		</div>
+	</body>
+</html>

+ 55 - 0
demo/sis/js/animations/aliens.js

@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+var 	ALIENS_WIDTH = 25,
+	ALIENS_WIDTH_2 = 18,
+	ALIENS_HEIGHT = 17,
+	ALIENS_SPRITE = IMAGES_PREFIX + "invader.png",
+	ALIENS_RATE = 400;
+
+var ALIENS_TYPE = [
+	{
+		animation : new $.gQ.Animation({
+			imageURL : ALIENS_SPRITE,
+			numberOfFrame : 2,
+			delta : 24,
+			rate : ALIENS_RATE,
+			type : $.gQ.ANIMATION_HORIZONTAL
+		}),
+		width : 24,
+		height: 18
+	},
+	{
+		animation : new $.gQ.Animation({
+			imageURL : ALIENS_SPRITE,
+			numberOfFrame : 2,
+			delta : ALIENS_WIDTH_2,
+			offsety : 20,
+			rate : ALIENS_RATE,
+			type : $.gQ.ANIMATION_HORIZONTAL
+		}),
+		width : ALIENS_WIDTH_2,
+		height: 17
+	},
+	{
+		animation : new $.gQ.Animation({
+			imageURL : ALIENS_SPRITE,
+			numberOfFrame : 2,
+			delta : ALIENS_WIDTH,
+			offsety : 40,
+			rate : ALIENS_RATE,
+			type : $.gQ.ANIMATION_HORIZONTAL
+		}),
+		width : ALIENS_WIDTH,
+		height: 18
+	}
+]
+
+

+ 11 - 0
demo/sis/js/animations/animations.js

@@ -0,0 +1,11 @@
+/*
+ * Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+var	IMAGES_PREFIX = "images/";

+ 127 - 0
demo/sis/js/animations/explosions.js

@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+var	EXPLOSION_BIG = IMAGES_PREFIX + "explosion_big.png",
+	EXPLOSION_BIG_RATE = 50,
+	EXPLOSION_SMALL = IMAGES_PREFIX + "explosion_small.png",
+	EXPLOSION_SMALL_RATE = 50;
+
+var EXPLOSIONS = {
+	BIG : [
+		{
+			animation : new $.gQ.Animation({
+				imageURL : EXPLOSION_BIG,
+				numberOfFrame : 8,
+				delta : 128,
+				rate : EXPLOSION_BIG_RATE,
+				type : $.gQ.ANIMATION_HORIZONTAL | $.gQ.ANIMATION_CALLBACK
+			}),
+			width : 128,
+			height: 128
+		},
+		{
+			animation : new $.gQ.Animation({
+				imageURL : EXPLOSION_BIG,
+				offsety : 128,
+				numberOfFrame : 8,
+				delta : 128,
+				rate : EXPLOSION_BIG_RATE,
+				type : $.gQ.ANIMATION_HORIZONTAL | $.gQ.ANIMATION_CALLBACK
+			}),
+			width : 128,
+			height: 128
+		},
+		{
+			animation : new $.gQ.Animation({
+				imageURL : EXPLOSION_BIG,
+				offsety : 256,
+				numberOfFrame : 8,
+				delta : 128,
+				rate : EXPLOSION_BIG_RATE,
+				type : $.gQ.ANIMATION_HORIZONTAL | $.gQ.ANIMATION_CALLBACK
+			}),
+			width : 128,
+			height: 128
+		},
+		{
+			animation : new $.gQ.Animation({
+				imageURL : EXPLOSION_BIG,
+				offsety : 384,
+				numberOfFrame : 8,
+				delta : 128,
+				rate : EXPLOSION_BIG_RATE,
+				type : $.gQ.ANIMATION_HORIZONTAL | $.gQ.ANIMATION_CALLBACK
+			}),
+			width : 128,
+			height: 128
+		},
+		{
+			animation : new $.gQ.Animation({
+				imageURL : EXPLOSION_BIG,
+				offsety : 512,
+				numberOfFrame : 8,
+				delta : 128,
+				rate : EXPLOSION_BIG_RATE,
+				type : $.gQ.ANIMATION_HORIZONTAL | $.gQ.ANIMATION_CALLBACK
+			}),
+			width : 128,
+			height: 128
+		},
+		{
+			animation : new $.gQ.Animation({
+				imageURL : EXPLOSION_BIG,
+				offsety : 640,
+				numberOfFrame : 8,
+				delta : 128,
+				rate : EXPLOSION_BIG_RATE,
+				type : $.gQ.ANIMATION_HORIZONTAL | $.gQ.ANIMATION_CALLBACK
+			}),
+			width : 128,
+			height: 128
+		},
+		{
+			animation : new $.gQ.Animation({
+				imageURL : EXPLOSION_BIG,
+				offsety : 768,
+				numberOfFrame : 8,
+				delta : 128,
+				rate : EXPLOSION_BIG_RATE,
+				type : $.gQ.ANIMATION_HORIZONTAL | $.gQ.ANIMATION_CALLBACK | $.gQ.ANIMATION_ONCE
+			}),
+			width : 128,
+			height: 128
+		},
+		{
+			animation : new $.gQ.Animation({
+				imageURL : EXPLOSION_BIG,
+				offsety : 896,
+				numberOfFrame : 8,
+				delta : 128,
+				rate : EXPLOSION_BIG_RATE,
+				type : $.gQ.ANIMATION_HORIZONTAL | $.gQ.ANIMATION_CALLBACK | $.gQ.ANIMATION_ONCE
+			}),
+			width : 128,
+			height: 128
+		}
+	],
+	SMALL : [
+		{
+			animation : new $.gQ.Animation({
+				imageURL : EXPLOSION_SMALL,
+				numberOfFrame : 10,
+				delta : 64,
+				rate : EXPLOSION_SMALL_RATE,
+				type : $.gQ.ANIMATION_HORIZONTAL | $.gQ.ANIMATION_CALLBACK  | $.gQ.ANIMATION_ONCE
+			}),
+			width : 64,
+			height: 64
+		}
+	]
+}

+ 131 - 0
demo/sis/js/animations/worlds.js

@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+var FARM_SPRITE = IMAGES_PREFIX + "farm.png",
+	FARM_BACKGROUND_1 = IMAGES_PREFIX + "background.png",
+	FARM_BACKGROUND_2 = IMAGES_PREFIX + "background2.png";
+
+var WORLD = {
+	farm : {
+		hero : {
+			ship : {
+				animation : new $.gameQuery.Animation({
+					imageURL : FARM_SPRITE
+				}),
+				width : 48,
+				height : 24,
+				posx : 0,
+				posy : 17
+			},
+			/*cockpit : {
+				animation : new $.gameQuery.Animation({
+					imageURL : FARM_SPRITE,
+					offsety : 24,
+					offsetx : 20
+				}),
+				width : 20,
+				height : 33,
+				posx : 28,
+				posy : 3
+			},*/
+			smallWheel : {
+				animation  : new $.gameQuery.Animation({
+					imageURL : FARM_SPRITE,
+					offsetx : 0,
+					offsety : 24
+				}),
+				posx : 4,
+				posy : 30,
+				width: 14,
+				height: 14
+			},
+			bigWheel : {
+				animation  : new $.gameQuery.Animation({
+					imageURL : FARM_SPRITE,
+					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 : FARM_SPRITE,
+				offsetx : 14,
+				offsety : 24
+			}),
+			width: 32,
+			height: 16
+		},
+		weapons : {
+			corn : {
+				animation : new $.gameQuery.Animation({
+					imageURL : FARM_SPRITE,
+					offsetx : 19,
+					offsety : 37
+				}),
+				width: 19,
+				height: 19
+			},
+			carot : {
+				animation : new $.gameQuery.Animation({
+					imageURL : FARM_SPRITE,
+					offsetx : 38,
+					offsety : 37
+				}),
+				width: 19,
+				height: 19
+			},
+			gun : {
+				animation : new $.gameQuery.Animation({
+					imageURL : FARM_SPRITE,
+					offsetx : 57,
+					offsety : 37
+				}),
+				width: 19,
+				height: 19
+			}
+		},
+		backgrounds : {
+			farm: {
+				background1 : {
+					animation : new $.gQ.Animation({
+						imageURL : FARM_BACKGROUND_1
+					})
+				},
+				background2 : {
+					animation : new $.gQ.Animation({
+						imageURL : FARM_BACKGROUND_2
+					})
+				}
+			}
+		}	
+	}
+};

+ 94 - 0
demo/sis/js/countdown.js

@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+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" );
+	}
+};

+ 78 - 0
demo/sis/js/lettering.js

@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+(function($) {
+	function injector(t, splitter, forceSmallParam) {
+		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;//forceSmallParam ? forceSmallParam : 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 ) { // Numbers
+							letterSize = 32;
+							if( forceSmall ) {
+								letterSize = 16;
+							}
+							html += "<span class='" + clazz + (forceSmall ? "small" : "") + "' style='top: -50%;background-position: -" + ( parseInt( letter, null ) * letterSize) + "px -" + (forceSmall ? 128 : 0) +"px'></span>";
+							count = count + 1;
+						} else { // Letters
+							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);

+ 91 - 0
demo/sis/js/models/aliens.js

@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+var ALIENS = {
+	alien1 : {
+		health 		: 1,
+		weapon 		: WEAPONS.ALIEN,
+		score 		: 5,
+		aggression 	: 0.0005,
+		animation 	: ALIENS_TYPE[0]
+	},
+	alien2 : {
+		health 		: 2,
+		weapon 		: WEAPONS.ALIEN,
+		score 		: 10,
+		aggression 	: 0.001,
+		animation 	: ALIENS_TYPE[1]
+	},
+	alien3 : {
+		health 		: 3,
+		weapon 		: WEAPONS.ALIEN,
+		score 		: 20,
+		aggression 	: 0.0015,
+		animation 	: ALIENS_TYPE[2]
+	}
+}
+
+
+/*** Actors - Aliens ***/
+function Alien(id, start, move, type) {
+	"use strict";
+
+	this.id = id;
+	this.x = start.x;
+	this.y = start.y;
+	this.moveFct = move;
+
+	this.weapon = new Weapon(type.weapon);
+	this.fireDirectionY = 1;
+
+	this.originX = this.x;
+	this.originY = this.y;
+	this.directionX = -1;
+	this.speed = 0.5;
+	this.animation = type.animation;
+	this.width = type.animation.width;
+	this.height = type.animation.height;
+	this.health = type.health;
+	this.aggression = type.aggression;
+	this.score = type.score;
+}
+
+Alien.prototype = {
+	moveFct : null,
+	width : 0,
+	height : 0,
+	aggression : 0,
+	animation : null,
+	score : 0,
+
+	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( this.score );
+	}
+};
+
+heriter(Alien.prototype, Actor.prototype);
+
+/*** Actors - Aliens - END ***/

+ 234 - 0
demo/sis/js/models/models.js

@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+function getAliensMidHeight() {
+	var higherAlien = Math.max.apply( null, 
+		$(".alien").map(function() {
+			return $(this).y();
+		}).get() ),
+		lowerAlien = Math.min.apply( null, 
+		$(".alien").map(function() {
+			return $(this).y();
+		}).get() );
+		
+	return (higherAlien + lowerAlien) / 2;
+}
+
+WORLD.farm.bonus = [
+	{
+		type: "weapon",
+		clazz: WEAPONS.CAROT,
+		animation: WORLD.farm.weapons.carot
+	},
+	{
+		type: "weapon",
+		clazz: WEAPONS.CORN,
+		animation: WORLD.farm.weapons.corn
+	}
+];
+
+/*** Actors ***/
+function Actor() {
+	"use strict";
+}
+Actor.prototype = {
+	id : null,
+	node : null,
+	x : null,
+	y : null,
+	originX : 0,
+	originY : 0,
+	speed : 0,
+	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).addClass(this.weapon.clazz);
+			$("#" + name)[0].weapon = this.weapon;
+			return true;
+		}
+		return false;
+	},
+	
+	hit : function() {
+		this.health = this.health - 1;
+		if( this.health == 0 ) {
+			this.destroy();
+		}
+		
+		return this.health;
+	},
+
+	destroy : function() {
+		$("#" + this.id).remove();
+	}
+};
+
+
+/*** 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 Weapon(WEAPONS.SHOTGUN);
+	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);
+	},
+	
+	up : function(active) {
+		if (this.y < (PLAYGROUND_HEIGHT - 2 * HUD_HEIGHT)) {
+			return false;
+		}
+		this._super("up", arguments);
+	},
+
+	destroy : function() {
+		var 	_this = this,
+			_hero = $("#hero"),
+			explosion = EXPLOSIONS.BIG;
+
+		$("#life" + this.lives).remove();
+		this.lives = this.lives - 1;
+		
+		$("#actors").addSprite("heroExplosion", 
+			{
+				width: explosion[0].width, 
+				height: explosion[0].height, 
+				posx: _hero.x() - explosion[0].width / 2 + _hero.w(), 
+				posy: _hero.y() - explosion[0].height / 2 - _hero.h() /4
+			}
+		);
+		explosionBig($("#heroExplosion"), function() {$("#heroExplosion").remove()});
+		_hero.children().hide();
+	}, 
+	
+	respawn : function() {
+		$("#heroExplosion").remove();
+		$("#hero").children().show();
+	},
+
+	fire : function() {
+		if(this._super("fire", arguments)) {
+			Game.shots.total = Game.shots.total + 1;
+			this.weapon.stock--;
+			if( this.weapon.stock == 0 ) {
+				this.weapon = new Weapon(WEAPONS.SHOTGUN);
+				$("#current_weapon").setAnimation(this.weapon.animation);
+			} 
+		}
+	}
+
+};
+
+heriter(Ship.prototype, Actor.prototype);
+/*** Actors - Heroes - END ***/
+
+/*** Actors - END ***/

+ 183 - 0
demo/sis/js/models/waves.js

@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+/*** Move ***/
+
+var MOVE = {
+	translation : {
+		init : function (x, y) {
+			return {directionX : 1, directionY : 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);
+			}
+		},
+	},
+	mirror : {
+		init : function(x, y) {
+			if( x < PLAYGROUND_WIDTH / 2 ) {
+				return {directionX: -1, directionY: 0};
+			}
+			return {directionX: 1, directionY: 0};
+		},
+		move : function() {
+			var offset =  this.width / 2;
+			if (Math.abs((this.getOriginX() - this.getX())) >= offset) {
+				this.directionX *= -1;
+				this.y = (this.y + this.height / 4);
+			}
+		},
+	},
+	half_part_rotation : {
+		init : function (x, y) {
+			return {directionX:0, directionY:0};
+		},
+		move : function () {
+			var 	_this = $(this)[0],
+				mid = PLAYGROUND_WIDTH / 2,
+				center = _this.center, 
+				width = _this.width;
+
+			if( this.directionX == 0 && this.directionY == 0 ) {
+				center = {x: ( this.getOriginX() < mid ? PLAYGROUND_WIDTH / 4 : 3 * PLAYGROUND_WIDTH / 4), y: getAliensMidHeight() };
+				width = distance(center, {x: this.x, y: this.y});
+				var	xAxis = {x: width, y: 0}, 
+					current = {x: center.x - this.getOriginX(), y: center.y - this.getOriginY()},
+					alpha = angle( xAxis, current );
+				this.directionX = 0.01;
+				this.directionY = alpha;
+				$(this)[0].center = center;
+				$(this)[0].width = width;
+			}
+				
+			if( this.getOriginX() < mid ) {
+				this.directionY = this.directionY + this.directionX;
+			} else {
+				this.directionY = this.directionY - this.directionX;
+			}
+			if( Math.abs(this.directionY) > 2 * Math.PI ) {
+				this.directionY = 0;
+			}
+			center.y = center.y + 0.1;
+			_this.center = center;
+			this.x = center.x + width * Math.cos(this.directionY);
+			this.y = center.y + width * Math.sin(this.directionY);
+		}
+	},
+
+	rotation : {
+		init : function (x, y) {
+			return {directionX:0, directionY:0};
+		},
+		move : function () {
+			var 	_this = $(this)[0],
+				mid = PLAYGROUND_WIDTH / 2,
+				center = _this.center, 
+				width = _this.width;
+
+			if( this.directionX == 0 && this.directionY == 0 ) {
+				center = {x: mid, y: getAliensMidHeight() };
+				width = distance(center, {x: this.x, y: this.y});
+				var	xAxis = {x: width, y: 0}, 
+					current = {x: center.x - this.getOriginX(), y: center.y - this.getOriginY()},
+					alpha = angle( xAxis, current );
+				this.directionX = 0.01;
+				this.directionY = alpha;
+				$(this)[0].center = center;
+				$(this)[0].width = width;
+			}
+				
+			this.directionY = this.directionY - this.directionX;
+			if( Math.abs(this.directionY) > 2 * Math.PI ) {
+				this.directionY = 0;
+			}
+			center.y = center.y + 0.1;
+			_this.center = center;
+			this.x = center.x + width * Math.cos(this.directionY);
+			this.y = center.y + width * Math.sin(this.directionY);
+		}
+	},
+	
+	sinusoid : {
+		init : function (x, y) {
+			return {directionX : 1, directionY : 1};
+		},
+		move : function () {
+			var offset = this.width / 2;
+			if (Math.abs((this.getOriginX() - this.getX())) >= offset) {
+				this.directionX *= -1;
+			}
+			
+			if( Math.abs(this.getOriginY() - this.getY()) >= 3 * this.height ) {
+				this.directionY *= -1;
+			}
+		}
+	}
+};
+
+/*** Move - end ***/
+
+
+/*** Waves ***/
+
+var WAVES = [
+		{
+			wave : [ 
+				 [ ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1 ],
+				 [ ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1 ],
+				 [ ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1 ],
+				 [ ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1 ],
+				 [ ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1 ]
+			],
+			move : MOVE.translation,
+			bonus : [40, 20]
+		},
+		{
+			wave : [ [ ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1 ], 
+				 [ ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1 ],
+				 [ ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1 ],
+				 [ ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1 ],
+				 [ ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1 ] 
+			],
+			move : MOVE.mirror,
+			bonus : [30, 15]
+		}, 
+		{
+			wave : [ [ undefined, undefined, ALIENS.alien1, undefined, undefined, undefined, undefined, undefined, ALIENS.alien1, undefined, undefined ],
+				 [ undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined, undefined, undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined ],
+				 [ ALIENS.alien1, ALIENS.alien1, undefined, ALIENS.alien1, ALIENS.alien1, undefined, ALIENS.alien1, ALIENS.alien1, undefined, ALIENS.alien1, ALIENS.alien1 ],
+				 [ undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined, undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined ],
+				 [ undefined, undefined, ALIENS.alien1, undefined, undefined, undefined, undefined, undefined, ALIENS.alien1, undefined, undefined ]
+			],
+			move : MOVE.half_part_rotation,
+			bonus : [20, 10]
+		},
+		{
+			wave : [ 
+				[ undefined, undefined, undefined, undefined, ALIENS.alien1, undefined, undefined, undefined, undefined ],
+				[ undefined, undefined, undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined, undefined, undefined ],
+				[ undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined ],
+				[ undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined ],
+				[ ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined, undefined, undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1 ],
+				[ undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined ],
+				[ undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined ],
+				[ undefined, undefined, undefined, ALIENS.alien1, ALIENS.alien1, ALIENS.alien1, undefined, undefined, undefined ],
+				[ undefined, undefined, undefined, undefined, ALIENS.alien1, undefined, undefined, undefined, undefined ]
+			],
+			move : MOVE.rotation,
+			bonus : [25, 12]
+		}
+	];
+
+
+/*** Waves - end ***/

+ 140 - 0
demo/sis/js/models/weapons.js

@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+var WEAPONS = {
+	SHOTGUN : {
+		directionY	: -1,
+		rof			: 200,
+		ror 		: 1500,
+		load 		: 2,
+		max_load 	: 2,
+		width 		: 3,
+		height 		: 3,
+		clazz 		: "Shotgun"	
+	},
+	CAROT : {
+		directionY 	: -1,
+		stock 		: 10,
+		clazz 		: "carot",
+		load 		: 5,
+		max_load	: 5,
+		width		: 5,
+		height		: 10
+	},
+	CORN : {
+		directionY	: -1,
+		stock 		: 3,
+		clazz 		: "corn",
+		load 		: 1,
+		max_load 	: 1,
+		callback 	: function(shot) {
+			var mediumAlien = getAliensMidHeight();
+			
+			if( shot.y() < mediumAlien ) {
+				var 	x = shot.x(),
+					y = shot.y(),
+					explosion = EXPLOSIONS.SMALL[0];
+				$("#shipShots").addSprite("cornExplosion", {width: explosion.width, height: explosion.height, posx: x - explosion.width / 2, posy: y - explosion.height / 2});
+				explosionSmall($("#cornExplosion"), function() {$("#cornExplosion").remove()});
+				shot.remove();
+
+				var shipShots = $("#shipShots");
+				for( var i = 0; i < 8; i++) {
+					var cos = Math.cos( (Math.PI / 4) * i ),
+						sin = Math.sin( (Math.PI / 4) * i);
+					if( Math.abs(cos) < 0.01 ) {
+						cos = 0;
+					}
+					if( Math.abs(sin) < 0.01) {
+						sin = 0;
+					}
+					shipShots.addSprite( "shotCorn" + i, { posx : x + 5 * cos, posy : y + 5 * sin, width: 2, height: 2});
+					var shotCorn = $("#shotCorn" + i);
+					shotCorn.addClass("shipShot").addClass("shotCorn");
+					$("#shotCorn" + i)[0].weapon = $.extend({
+							directionX : cos < 0 ? -1 : cos > 0 ? 1 : 0,
+							directionY : sin < 0 ? -1 : sin > 0 ? 1 : 0,
+							speed : 1
+						}, shot.weapon); 
+				}
+			}
+		}		
+	},
+	ALIEN : {
+		directionY	: 1,
+		width 		: 5,
+		height		: 10
+	}
+}
+
+function Weapon(type) {
+	"use strict";
+	this.directionY 	= type.directionY || 1;
+	this.directionX 	= type.directionX || 0;
+	this.width 			= type.width;
+	this.height 		= type.height;
+	this.speed 			= type.speed || 5;
+	this.strenght 		= type.strength || 10;
+	this.stock			= type.stock || Infinity;
+	this.rof 			= type.rof || 300;
+	this.ror 			= type.ror || 1500;
+	this.load 			= type.load || 1;
+	this.max_load 		= type.max_load || 1;
+	this.animation 		= type.animation;
+	this.clazz 			= type.clazz || "default";
+	this.callback 		= type.callback;
+
+}
+
+Weapon.prototype = {
+	speed : 5,
+	strength : 10,
+	stock: Infinity,
+	rof : 300,
+	ror : 1500,
+	load : 1,
+	max_load : 1,
+	width : 5,
+	height : 5,
+	shot_timer : false,
+	reload_timer : false,
+	directionX : 0,
+	directionY : 1,
+	animation : null,
+	clazz : "default",
+	callback : undefined,
+
+	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, Math.min(_this.max_load, _this.stock));
+				if( _this.load == _this.max_load ) {
+					clearInterval(_this.reload_timer);
+					_this.reload_timer = false;
+				}
+			}, this.ror);
+		}
+		return true;
+	}
+
+}

+ 54 - 0
demo/sis/js/scoreboard.js

@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+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" );
+	}
+};

+ 318 - 0
demo/sis/js/spaceinvaders-core.js

@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+Game = {
+	running : false,
+	wave_index : -1,
+	wave : undefined,
+	aliens : [],
+	ship : null,
+	score : 0,
+	shots : {
+		total : 0,
+		lost : 0
+	},
+	
+	init : function() {
+		"use strict";
+		animations = WORLD.farm;
+		Game.wave_index = Game.wave_index + 1;
+		Game.wave = WAVES[Math.min(Game.wave_index, WAVES.length - 1)];
+		Game.wave.score = 0;
+		
+		var row, col, wave = Game.wave.wave;
+		
+		for (row = 0; row < wave.length; row = row + 1) {
+			var alien_width_avg = 0,
+				aliens_row = wave[row], 
+				offset = 0;
+
+			for (col = 0; col < aliens_row.length; col = col + 1) {
+				var current_width = typeof aliens_row[col] !== "undefined" ? aliens_row[col].animation.width : ALIENS_TYPE[2].width;
+				alien_width_avg = alien_width_avg + current_width;
+			}
+
+			alien_width_avg = alien_width_avg / aliens_row.length;
+			offset = (PLAYGROUND_WIDTH - (aliens_row.length  * 1.5 - 0.5) * alien_width_avg) / 2;
+
+			for (col = 0; col < aliens_row.length; col = col + 1) {
+				Game.setAlien(col, row, offset, alien_width_avg, aliens_row[col], Game.wave.move);
+			}
+		}
+
+		SCOREBOARD.init();
+		SCOREBOARD.set_score( Game.score );
+		
+		updateLevel(Game.wave_index + 1);
+		
+		Game.setShip();
+		
+		hideModal();
+		Game.running = true;
+	},
+
+	game_over : function() {
+		displayModal( {
+			end: "Loose",
+			score: Game.score
+		});
+	},
+	
+	levelComplete : function() {
+		"use strict";
+		Game.running = false;
+		var bonus = Math.round(((Game.shots.total - Game.shots.lost) / Game.shots.total) * Game.wave.score),
+			wave_score = Game.wave.score;
+		Game.addToScore(bonus);
+		
+		displayModal( {
+			bonus: bonus,
+			wave_score: wave_score,
+			score: Game.score,
+		});
+
+		setTimeout(function() {
+			Game.init();
+		}, 5000);
+	},
+	
+	hit : function() {
+		"use strict";
+		if( !Game.running ) {
+			return false;
+		}
+
+		var health = Game.ship.hit();
+		$(".alienShot").remove();
+		$(".shipShot").remove();
+		$("#life" + Game.ship.lives).remove();
+		Game.running = false;
+		$("#hero").children().hide();
+		
+		if( Game.ship.lives > 0 ) {
+			var _this = Game.ship;
+			setTimeout(function() {
+				Game.ship.respawn();
+				Game.running = true;
+			}, 5000);
+		}
+		else {
+			Game.game_over();
+		}
+	},
+	
+	setAlien : function(x, y, offset, width, type, move) {
+		"use strict";
+		if( typeof type === "undefined" ) {
+			return;
+		}
+		var id = Game.aliens.length + 1, 
+			alien = new Alien("alien" + id, {
+				x : offset + x * width * 1.5,
+				y : START_Y + (y * 1.25 * type.animation.height)
+			}, move.move, type),
+			directions = move.init( alien.x, alien.y );
+		alien.directionX = directions.directionX;
+		alien.directionY = directions.directionY;
+		$("#actors").addSprite("alien" + id, $.extend({posx : alien.x, posy : alien.y}, alien.animation));
+		alien.node = $("#alien" + id);
+		alien.node.addClass("alien");
+		$("#alien" + id)[0].alien = alien;
+		Game.aliens.push(alien);
+	},
+
+	setShip : function() {
+		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;
+		Game.wave.score = Game.wave.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();
+			var node = alien.node;
+			if( (node.y() + node.h()) > $("#hero").y() ) {
+				Game.running = false;
+				Game.game_over();
+				return false;
+			}
+			if( alien.health > 0 && Math.random() < (alien.aggression * (Game.wave_index + 1)) ) {
+				alien.fire($("#aliensShots"), "alienShot");
+			}
+		});
+	},
+	
+	heroShotCollision : function() {
+		if( !Game.running ) {
+			return false;		
+		}
+		
+		var shots = $(".shipShot");
+		if( shots.length == 0 ) {
+			return false;
+		}
+		
+		shots.each(function(i,e) {
+			var posy = $(this).y(),
+				posx = $(this).x(),
+				weapon = $(this)[0].weapon;
+				
+			// Lost shots
+			if( posy < -$(this).height() || posy > PLAYGROUND_HEIGHT || posx < -$(this).width() || posx > PLAYGROUND_WIDTH ) {
+				Game.shots.lost = Game.shots.lost + 1;
+				this.remove();
+				return;
+			}
+			$(this).y(weapon.directionY * weapon.speed, true);
+			$(this).x(weapon.directionX * weapon.speed, true);
+			
+			if( weapon.callback ) {
+				weapon.callback($(this));
+			} else {
+				// Shots collisions
+				var collisions = $(this).collision(".alien,."+$.gQ.groupCssClass);
+				collisions.each( function() {
+					var alien = $(this)[0],
+						alienX = alien.alien.x,
+						alienY = alien.alien.y,
+						alienWidth = alien.alien.width,
+						alienHeight = alien.alien.height;
+					
+					alien.alien.hit();
+					Game.aliens = $.grep( Game.aliens, function( elementOfArray, index) {
+								return elementOfArray.health == 0 && elementOfArray.id == alien.id;
+							}, true);
+					var remainingAliens = $(".alien").length;
+					if( remainingAliens == Game.wave.bonus[0] || remainingAliens == Game.wave.bonus[1] ) {
+						Game.bonus(alienX, alienY, alienWidth, alienHeight);					
+					}
+				});
+				if( collisions.length > 0 ) {
+					this.remove()
+				}
+			
+				if( Game.aliens.length == 0 ) {
+					Game.running = false;
+					Game.levelComplete();
+				}
+			}
+		});
+	},
+
+	bonusCollision : function() {
+		if( !Game.running ) {
+			return false;		
+		}
+		
+		$(".bonus").each(function(i,e) {
+			var collisions = $(this).collision("#ship,."+$.gQ.groupCssClass);
+			var bonus = $(this)[0].bonus;
+			if( collisions.length > 0 ) {
+				if( bonus.type === "weapon" ) {
+					Game.ship.weapon = new Weapon(bonus.clazz);
+					$("#current_weapon").setAnimation(bonus.animation.animation);
+					this.remove();
+				}
+			}
+		});
+	},
+
+
+	bonus : function(alienX, alienY, alienWidth, alienHeight) {
+		var bonus = animations.bonus[Math.round(Math.random() * (animations.bonus.length - 1)) ];
+		var id = Math.round(Math.random() * 10000);
+		$("#actors").addSprite("bonus" + id, 
+			$.extend(
+				{
+					posx: alienX + (alienWidth - bonus.animation.width) / 2, 
+					posy: alienY 
+				}, bonus.animation));
+		$("#bonus" + id).addClass("bonus");
+		$("#bonus" + id)[0].bonus = bonus;
+	},
+	
+	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);
+			try {
+				var collisions = $(this).collision("#actors,#hero,#ship");
+				if( collisions.length > 0 ) {
+					Game.hit();
+					this.remove();
+				}
+			}catch(e) {
+			}
+		});
+	}
+};

+ 226 - 0
demo/sis/js/spaceinvaders-ui.js

@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+/*global jQuery */
+
+var PLAYGROUND_WIDTH = 448,
+	PLAYGROUND_HEIGHT = 544,
+	MODAL_WIDTH = 300,
+	REFRESH_RATE = 15,
+	HUD_HEIGHT = 70,
+
+	ROWS = 5,
+	ALIENS = 11,
+
+	ALIENS_WIDTH = 24,
+	ALIENS_HEIGHT = 17,
+
+	START_Y = 40;
+
+var animations = WORLD.farm;
+
+
+function explose( div, explosion, step, callback ) {
+	if( step < explosion.length - 2 ) {
+		div.setAnimation( explosion[step].animation, function() {
+			explose( div, explosion, step + 1, callback );
+		}); 
+	} else {
+		div.setAnimation( explosion[step].animation, callback);
+	}
+};
+
+function explosionBig( div, callback ) {
+	explose(div, EXPLOSIONS.BIG, 0, callback );
+};
+
+function explosionSmall( div, callback ) {
+	explose(div, EXPLOSIONS.SMALL, 0, callback );
+};
+
+function displayModal(data) {
+	var score = data.score.toString();
+	$.playground()
+		.addGroup( "modal_pane", {width: MODAL_WIDTH, height: data.end ? 190 : 140, posx: (PLAYGROUND_WIDTH - MODAL_WIDTH ) /2, posy: (PLAYGROUND_HEIGHT - (140 ))/2})
+			.addSprite("scoreLbl", {width: 180, height: 32, posx: 10, posy: data.end ? 50 : 10})
+			.addSprite("scoreNbr", {width: score.length * 32, height: 32, posx: (MODAL_WIDTH - 10 - score.length * 32), posy: data.end ? 50 : 10})
+		.end();
+	if( data.end ) {
+		var width = (data.end.length - 1 ) * 16 + 32;
+		$("#modal_pane").addSprite("gameSt", {width: width, height: 32, posx: (MODAL_WIDTH - width) / 2, posy: 10});
+		$("#gameSt").append(data.end).lettering();
+	}
+	
+	if( data.bonus ) {
+		var bonus = data.bonus.toString(),
+			total = data.wave_score.toString();
+		$("#modal_pane")
+			.addSprite("totalLbl", {width: 180, height: 32, posx: 10, posy: data.end ? 130 : 90})
+			.addSprite("totalNbr", {width: total.length * 32, height: 32, posx: (MODAL_WIDTH - 10 - total.length * 32), posy: data.end ? 130 : 90})
+			.addSprite("bonusLbl", {width: 180, height: 32, posx: 10, posy: data.end ? 90 : 50})
+			.addSprite("bonusNbr", {width: bonus.length * 32, height: 32, posx: (MODAL_WIDTH - 10 - bonus.length * 32), posy: data.end ? 90 : 50})
+			;
+		$("#totalLbl").append("Wave").lettering();
+		$("#totalNbr").append(total).lettering();
+		$("#bonusLbl").append("Bonus").lettering();
+		$("#bonusNbr").append(bonus).lettering();
+	}
+	
+	$("#modal_pane").addClass( "modal" );
+	$("#scoreLbl").append("Score").lettering();
+	$("#scoreNbr").append(score).lettering();
+}
+
+function hideModal() {
+	$("#modal_pane").remove();
+}
+
+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 updateLevel(level) {
+	var strLevel = level.toString();
+	var div = $("#level");
+	div.w(strLevel.length * 32);
+	div.x( PLAYGROUND_WIDTH  / 2 - 10 - strLevel.length * 32);
+	div.empty().append(strLevel).lettering();
+}
+
+$(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()
+/*			.addSprite("alienA", $.extend({posx : 50, posy : 300}, ALIENS[0]))
+			.addSprite("alienB", $.extend({posx : 50, posy : 400}, ALIENS[1]))
+			.addSprite("alienC", $.extend({posx : 50, posy : 500}, ALIENS[2]))
+*/			.addSprite("explosion", {
+				width: 64,
+				height: 64,
+				posx: 100,
+				posy: 100
+			})
+		.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("life2", animations.life)
+				.addSprite("life1", $.extend({posx: 32}, animations.life))
+				.addSprite("life0", $.extend({posx: 64}, animations.life))
+			.end()
+			.addGroup("levelGrp", {
+				posx: PLAYGROUND_WIDTH / 2,
+				posy: 2,
+				width: PLAYGROUND_WIDTH / 2,
+				height: 16
+				})
+				.addSprite("levelLbl", {
+					width: 32 + 5 * 16,
+					posx: 10
+				})
+				.addSprite("level", {
+					width : 64,
+					posx: 32 + 7 * 16,
+					height: 32
+				})
+			.end()
+		.end()
+		;
+
+	$("#ground").css("background-color", "brown");
+	$("#levelLbl").append("Level").lettering();
+	$("#level").append("0").lettering();
+	$("#scoreboard").addClass("scoreboard");
+	$("#subScoreboard").addClass("subScoreboard");
+	var hud = $("#hud");
+
+	// Controls
+	$.playground().registerCallback(Game.control, REFRESH_RATE);
+	$.playground().registerCallback(Game.alienControl, REFRESH_RATE);
+	
+	// Collisions management
+	$.playground().registerCallback(Game.bonusCollision, REFRESH_RATE);
+	$.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();
+			$(".bonus").each( function(index, bonus) {
+				var posy = $(this).y();
+				if( posy < Game.ship.y + 10 ) {
+					$(this).y(3, true);
+					return;
+				}			
+			});
+		}
+	}, REFRESH_RATE);
+});

+ 83 - 0
demo/sis/js/tools.js

@@ -0,0 +1,83 @@
+function heriter(destination, source) { 
+    function initClassIfNecessary(obj) { 
+        if( typeof obj["_super"] == "undefined" ) { 
+            obj["_super"] = function() { 
+                var methodName = arguments[0]; 
+                var parameters = arguments[1]; 
+                return 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]; 
+        } 
+    } 
+}
+
+function distance(point1, point2) {
+	return Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2));
+}
+
+function normalize(vector) {
+	var norm = Math.sqrt(vector.x * vector.x + vector.y * vector.y);
+	if (norm == 0)
+		return {
+			x : 0,
+			y : 0
+		};
+
+	var x = vector.x / norm;
+	var y = vector.y / norm;
+	return {
+		x : x,
+		y : y
+	}
+}
+
+function angle(vector1, vector2) {
+	if ((vector1.x == 0 && vector1.y == 0)
+			&& (vector1.x == 0 && vector1.y == 0)) {
+		return 0;
+	}
+
+	if (vector1.x == 0 && vector1.y == 0) {
+		return Math.acos(vector2.x
+				/ Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y));
+	}
+	
+	if (vector2.x == 0 && vector2.y == 0) {
+		return Math.acos(vector1.x
+				/ Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y));
+	}
+	
+	var 	product = vector1.x * vector2.x + vector1.y * vector2.y,
+		alpha = Math.acos(product
+			/ (Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y) * Math
+					.sqrt(vector2.x * vector2.x + vector2.y * vector2.y))),
+		sign = normalize(vector1).y > normalize(vector2).y ? -1 : 1;
+		
+	return sign * alpha;
+}
+
+
+function getAliensMidHeight() {
+	var higherAlien = Math.max.apply( null, 
+		$(".alien").map(function() {
+			return $(this).y();
+		}).get() ),
+		lowerAlien = Math.min.apply( null, 
+		$(".alien").map(function() {
+			return $(this).y();
+		}).get() );
+		
+	return (higherAlien + lowerAlien) / 2;
+}

+ 1849 - 0
demo/sis/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);

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
demo/sis/lib/jquery-1.7.1.min.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 4 - 0
demo/sis/lib/jquery-ui-1.8.23.custom.min.js


+ 7 - 0
demo/yahtzee/LICENSE.md

@@ -0,0 +1,7 @@
+Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 31 - 0
demo/yahtzee/README.md

@@ -0,0 +1,31 @@
+The classical dice game Yahtzee
+
+
+How to :
+--------
+
+The goal is to complete the two combinaisons grids with five dice
+
+Each player can launch three times the dice to have a combinaison. After each launch, the player chooses to keep or not some dice. Each combinaison has a score.
+
+When both grids are completed, their scores are added and the player with the higher score wins. If the upper grid has a score higher than 63 points, the player wins a bonus of 35 points.
+
+A live demo is avalaible [here](http://www.viadeo-playground.com/yahtzee/index)
+
+Credits :
+---------
+
+* Graphics : Fabrice Ecaille aka Febbweiss
+* Code : Fabrice Ecaille aka Febbweiss
+
+Licences :
+----------
+
+Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+

+ 65 - 0
demo/yahtzee/css/yahtzee.css

@@ -0,0 +1,65 @@
+.possibility {
+}
+
+.dice {
+	float: left;
+	margin: 4px;
+	width: 50px;
+	height: 50px;
+	background-image: url('../images/dices.png');
+}
+
+.dice.face6 {
+	background-position: 0 0;
+}
+
+.dice.face5 {
+	background-position: -50px 0;
+}
+
+.dice.face4 {
+	background-position: -100px 0;
+}
+
+.dice.face3 {
+	background-position: -149px 0;
+}
+
+.dice.face2 {
+	background-position: -199px 0;
+}
+
+.dice.face1 {
+	background-position: -249px 0;
+}
+
+.dice.empty {
+	visibility: hidden;
+}
+
+.dice.selected {
+	background-image: url('../images/red_dices.png');
+}
+
+.keep {
+	visibility: hidden;
+}
+
+.trash {
+	visibility: hidden;
+}
+
+.score {
+	font-family: "Zeyada", verdana, arial, helvetica, cursive;
+	color: blue;
+	font-size: 2em;
+	font-weight: bold;
+	text-align: center;
+}
+.score.trashed {
+	color: red;
+}
+
+.progress {
+	margin-bottom: 0;
+}

BIN
demo/yahtzee/images/dices.png


BIN
demo/yahtzee/images/red_dices.png


+ 394 - 0
demo/yahtzee/index.html

@@ -0,0 +1,394 @@
+<!--
+Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+-->
+<!DOCTYPE html>
+<html>
+    <head>
+    	<meta name="viewport" content="width=device-width, initial-scale=1.0">
+        <title>Yahtzee</title>
+        <!--[if IE]>
+        <style type="text/css" media="screen">
+        	body {
+ 				behavior: url("/css/hack/csshover3.htc");
+			}
+        </style>
+        <![endif]-->
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+		<link href='http://fonts.googleapis.com/css?family=Zeyada' rel='stylesheet' type='text/css'>
+        <script src="lib/jquery-1.7.1.min.js" type="text/javascript" ></script>
+		<link href="css/yahtzee.css" type="text/css" rel="stylesheet" media="screen, projection" />
+		<link href="../../extra/css/extra.css" type="text/css" rel="stylesheet" media="screen, projection" />
+		<script src="../../extra/js/yahtzee.js" type="text/javascript" ></script>
+    </head>
+    <body>
+			<div style="width: 50%;float: left;">
+				<div class="dice empty" id="dice1"></div>
+				<div class="dice empty" id="dice2"></div>
+				<div class="dice empty" id="dice3"></div>
+				<div class="dice empty" id="dice4"></div>
+				<div class="dice empty" id="dice5"></div>
+				<div class="dice empty" id="dice6"></div>
+				<div class="clear"></div>
+				<div>
+					<span>Launch</span>
+					<span id="launch"></span>
+				</div>
+				<a href="javascript:void(0)" id="launchBtn">Launch</a>
+				<div style="margin-top: 25px;">
+					<dl>
+						<dt>Three of a kind</dt>
+						<dd>Three dice showing the same face</dd>
+						<dd>Sum of all dice</dd>
+						<dt>Four of a kind</dt>
+						<dd>Four dice showing the same face</dd>
+						<dd>Sum of all dice</dd>
+						<dt>Full</dt>
+						<dd>A three-of-a-kind and a pair</dd>
+						<dd>25 points</dd>
+						<dt>Small straight</dt>
+						<dd>Four sequential dice</dd>
+						<dd>30 points</dd>
+						<dt>Large straight</dt>
+						<dd>Five sequential dice</dd>
+						<dd>40 points</dd>
+						<dt>Yahtzee</dt>
+						<dd>Five dice showing the same face</dd>
+						<dd>50 points</dd>
+						<dt>Chance</dt>
+						<dd>Sum of all dice</dd>
+					</dl>
+				</div>
+				<div id="scorePanel" style="display: none;">
+					<table class="table table-bordered">
+						<tr>
+							<td>Upper grid score</td>
+							<td id="upperScore" style="text-align: center"></td>
+						</tr>
+						<tr>
+							<td>Bonus</td>
+							<td id="bonus" style="text-align: center"></td>
+						</tr>
+						<tr>
+							<td>Lower grid score</td>
+							<td id="lowerScore" style="text-align: center"></td>
+						</tr>
+						<tr>
+							<td><b>Total</b></td>
+							<td id="totalScore" style="text-align: center"><b></b></td>
+						</tr>
+					</table>
+				</div>
+			</div>
+			<div style="width: 50%;float: left;">
+				
+				<table border="1">
+					<thead>
+						<tr>
+							<th style="text-align: center;">Combinaison</th>
+							<th style="text-align: center;" width="84px">
+								Score
+							</th>
+							<th style="text-align: center;" width="100px"></th>
+						</tr>
+					</thead>
+					<tbody>
+						
+							<tr>
+								<td>
+									<span id="oneLabel">
+										One
+									</span>
+								</td>
+								<td style="text-align: center;">
+									<span id="oneScore" class="score">&nbsp;</span>
+								</td>
+								<td>
+									<a href="javascript:void(0)"id="oneKeep" data-id="one" class="keep">Keep</a>
+									<a href="javascript:void(0)"id="oneTrash" data-id="one" class="trash">Trash</a>
+								</td>
+							</tr>
+						
+							<tr>
+								<td>
+									<span id="twoLabel">
+										Two
+									</span>
+								</td>
+								<td style="text-align: center;">
+									<span id="twoScore" class="score">&nbsp;</span>
+								</td>
+								<td>
+									<a href="javascript:void(0)"id="twoKeep" data-id="two" class="keep">Keep</a>
+									<a href="javascript:void(0)"id="twoTrash" data-id="two" class="trash">Trash</a>
+								</td>
+							</tr>
+						
+							<tr>
+								<td>
+									<span id="threeLabel">
+										Three
+									</span>
+								</td>
+								<td style="text-align: center;">
+									<span id="threeScore" class="score">&nbsp;</span>
+								</td>
+								<td>
+									<a href="javascript:void(0)"id="threeKeep" data-id="three" class="keep">Keep</a>
+									<a href="javascript:void(0)"id="threeTrash" data-id="three" class="trash">Trash</a>
+								</td>
+							</tr>
+						
+							<tr>
+								<td>
+									<span id="fourLabel">
+										Four
+									</span>
+								</td>
+								<td style="text-align: center;">
+									<span id="fourScore" class="score">&nbsp;</span>
+								</td>
+								<td>
+									<a href="javascript:void(0)"id="fourKeep" data-id="four" class="keep">Keep</a>
+									<a href="javascript:void(0)"id="fourTrash" data-id="four" class="trash">Trash</a>
+								</td>
+							</tr>
+						
+							<tr>
+								<td>
+									<span id="fiveLabel">
+										Five
+									</span>
+								</td>
+								<td style="text-align: center;">
+									<span id="fiveScore" class="score">&nbsp;</span>
+								</td>
+								<td>
+									<a href="javascript:void(0)"id="fiveKeep" data-id="five" class="keep">Keep</a>
+									<a href="javascript:void(0)"id="fiveTrash" data-id="five" class="trash">Trash</a>
+								</td>
+							</tr>
+						
+							<tr>
+								<td>
+									<span id="sixLabel">
+										Six
+									</span>
+								</td>
+								<td style="text-align: center;">
+									<span id="sixScore" class="score">&nbsp;</span>
+								</td>
+								<td>
+									<a href="javascript:void(0)"id="sixKeep" data-id="six" class="keep">Keep</a>
+									<a href="javascript:void(0)"id="sixTrash" data-id="six" class="trash">Trash</a>
+								</td>
+							</tr>
+						
+					</tbody>
+				</table>
+				
+				<table border="1">
+					<thead>
+						<tr>
+							<th style="text-align: center;">Combinaison</th>
+							<th style="text-align: center;">Probability</th>
+							<th style="text-align: center;" width="84px">
+								Score
+							</th>
+							<th style="text-align: center;" width="100px"></th>
+						</tr>
+					</thead>
+					<tbody>
+						
+							<tr>
+								<td>
+									<span id="threeOfAKindLabel">
+										Three of a kind
+									</span>
+								</td>
+								<td>
+									<div class="progress">
+										<div class="bar" id="threeOfAKindBar"></div>
+										<span id="threeOfAKindProbability" style="padding-left: 1px;">&nbsp;</span>
+									</div>
+								</td>
+								<td style="text-align: center;">
+									<span id="threeOfAKindScore" class="score">&nbsp;</span>
+								</td>
+								<td>
+									<a href="javascript:void(0)"id="threeOfAKindKeep" data-id="threeOfAKind" class="keep">Keep</a>
+									<a href="javascript:void(0)"id="threeOfAKindTrash" data-id="threeOfAKind" class="trash">Trash</a>
+								</td>
+							</tr>
+						
+							<tr>
+								<td>
+									<span id="fourOfAKindLabel">
+										Four of a kind
+									</span>
+								</td>
+								<td>
+									<div class="progress">
+										<div class="bar" id="fourOfAKindBar"></div>
+										<span id="fourOfAKindProbability" style="padding-left: 1px;">&nbsp;</span>
+									</div>
+								</td>
+								<td style="text-align: center;">
+									<span id="fourOfAKindScore" class="score">&nbsp;</span>
+								</td>
+								<td>
+									<a href="javascript:void(0)"id="fourOfAKindKeep" data-id="fourOfAKind" class="keep">Keep</a>
+									<a href="javascript:void(0)"id="fourOfAKindTrash" data-id="fourOfAKind" class="trash">Trash</a>
+								</td>
+							</tr>
+						
+							<tr>
+								<td>
+									<span id="fullLabel">
+										Full
+									</span>
+								</td>
+								<td>
+									<div class="progress">
+										<div class="bar" id="fullBar"></div>
+										<span id="fullProbability" style="padding-left: 1px;">&nbsp;</span>
+									</div>
+								</td>
+								<td style="text-align: center;">
+									<span id="fullScore" class="score">&nbsp;</span>
+								</td>
+								<td>
+									<a href="javascript:void(0)"id="fullKeep" data-id="full" class="keep">Keep</a>
+									<a href="javascript:void(0)"id="fullTrash" data-id="full" class="trash">Trash</a>
+								</td>
+							</tr>
+						
+							<tr>
+								<td>
+									<span id="smallStraightLabel">
+										Small straight
+									</span>
+								</td>
+								<td>
+									<div class="progress">
+										<div class="bar" id="smallStraightBar"></div>
+										<span id="smallStraightProbability" style="padding-left: 1px;">&nbsp;</span>
+									</div>
+								</td>
+								<td style="text-align: center;">
+									<span id="smallStraightScore" class="score">&nbsp;</span>
+								</td>
+								<td>
+									<a href="javascript:void(0)"id="smallStraightKeep" data-id="smallStraight" class="keep">Keep</a>
+									<a href="javascript:void(0)"id="smallStraightTrash" data-id="smallStraight" class="trash">Trash</a>
+								</td>
+							</tr>
+						
+							<tr>
+								<td>
+									<span id="largeStraightLabel">
+										Large straight
+									</span>
+								</td>
+								<td>
+									<div class="progress">
+										<div class="bar" id="largeStraightBar"></div>
+										<span id="largeStraightProbability" style="padding-left: 1px;">&nbsp;</span>
+									</div>
+								</td>
+								<td style="text-align: center;">
+									<span id="largeStraightScore" class="score">&nbsp;</span>
+								</td>
+								<td>
+									<a href="javascript:void(0)"id="largeStraightKeep" data-id="largeStraight" class="keep">Keep</a>
+									<a href="javascript:void(0)"id="largeStraightTrash" data-id="largeStraight" class="trash">Trash</a>
+								</td>
+							</tr>
+						
+							<tr>
+								<td>
+									<span id="yahtzeeLabel">
+										Yahtzee
+									</span>
+								</td>
+								<td>
+									<div class="progress">
+										<div class="bar" id="yahtzeeBar"></div>
+										<span id="yahtzeeProbability" style="padding-left: 1px;">&nbsp;</span>
+									</div>
+								</td>
+								<td style="text-align: center;">
+									<span id="yahtzeeScore" class="score">&nbsp;</span>
+								</td>
+								<td>
+									<a href="javascript:void(0)"id="yahtzeeKeep" data-id="yahtzee" class="keep">Keep</a>
+									<a href="javascript:void(0)"id="yahtzeeTrash" data-id="yahtzee" class="trash">Trash</a>
+								</td>
+							</tr>
+						
+							<tr>
+								<td>
+									<span id="luckLabel">
+										Chance
+									</span>
+								</td>
+								<td>
+									<div class="progress">
+										<div class="bar" id="luckBar"></div>
+										<span id="luckProbability" style="padding-left: 1px;">&nbsp;</span>
+									</div>
+								</td>
+								<td style="text-align: center;">
+									<span id="luckScore" class="score">&nbsp;</span>
+								</td>
+								<td>
+									<a href="javascript:void(0)"id="luckKeep" data-id="luck" class="keep">Keep</a>
+									<a href="javascript:void(0)"id="luckTrash" data-id="luck" class="trash">Trash</a>
+								</td>
+							</tr>
+						
+					</tbody>
+				</table>
+				
+			</div>
+		</div>
+	
+				    </div> <!-- End of span9 -->
+	<div style="text-align: center;margin-top: 00px;">
+		<button id="startBtn" class="push--skeuo">Start</button>
+	</div>
+<script src="js/yahtzee.js" type="text/javascript" ></script>
+<script type="text/javascript">
+			$(document).ready(function () {
+				$(".dice").click(function(evt) {
+					if( $(this).hasClass("selected") )
+						$(this).removeClass("selected");
+					else
+						$(this).addClass("selected");
+				});
+				
+				$(".keep").click(function(evt) {
+					console.log("Keeping " + $(this).attr('data-id'));
+					Yahtzee.keep($(this).attr('data-id'));
+				});
+				
+				$(".trash").click(function(evt) {
+					console.log("Trashing " + $(this).attr('data-id'));
+					Yahtzee.trash($(this).attr('data-id'));
+				});
+				
+				$("#launchBtn").click(function() {
+					Yahtzee.shuffle();
+					Yahtzee.findCombinaisons();
+					if( Yahtzee.launch < 3 )
+						Yahtzee.findPossibilities();
+				});
+			});
+		</script>
+    </body>
+</html>

+ 310 - 0
demo/yahtzee/js/yahtzee.js

@@ -0,0 +1,310 @@
+/*
+Copyright (c) 2013 Fabrice ECAILLE aka Febbweiss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+*/
+var SMALL_STRAIGHT_MASK1 = (1 << 0) + (1 << 1) + (1 << 2) + (1 << 3);
+var SMALL_STRAIGHT_MASK2 = (1 << 1) + (1 << 2) + (1 << 3) + (1 << 4);
+var SMALL_STRAIGHT_MASK3 = (1 << 2) + (1 << 3) + (1 << 4) + (1 << 5);
+var LARGE_STRAIGHT_MASK1 = (1 << 0) + (1 << 1) + (1 << 2) + (1 << 3) + (1 << 4);
+var LARGE_STRAIGHT_MASK2 = (1 << 1) + (1 << 2) + (1 << 3) + (1 << 4) + (1 << 5);
+var INCOMPLETE_STRAIGHT_MASK1 = (1 << 0) + (1 << 1) + (1 << 2);
+var INCOMPLETE_STRAIGHT_MASK2 = (1 << 1) + (1 << 2) + (1 << 3);
+var INCOMPLETE_STRAIGHT_MASK3 = (1 << 2) + (1 << 3) + (1 << 4);
+var INCOMPLETE_STRAIGHT_MASK4 = (1 << 3) + (1 << 4) + (1 << 5);
+
+Yahtzee = {
+		
+		launch : 0,
+		dices : [],
+		combinaisons : [],
+		pair : false,
+		doublePair : false,
+		three_of_a_kind : false,
+		four_of_a_kind : false,
+		full : false,
+		yahtzee : false,
+		small_straight : false,
+		large_straight : false,
+		straight : 0,
+
+		scoreUp : 0,
+		scoreDown: 0,
+		keptCombinaisons: [],
+		
+		clear: function(all) {
+			if( all )
+				Yahtzee.launch = 0;
+			
+			Yahtzee.pair = false;
+			Yahtzee.doublePair = false;
+			Yahtzee.three_of_a_kind = false;
+			Yahtzee.four_of_a_kind = false;
+			Yahtzee.full = false;
+			Yahtzee.yahtzee = false;
+			Yahtzee.small_straight = false;
+			Yahtzee.large_straight = false;
+			Yahtzee.straight = 0;
+			
+			if( all )
+				for( var i = 0; i < 5; i++ ) {
+					Yahtzee.dices[i] = 0;
+					$("#dice" + (i+1)).removeClass().addClass("dice").addClass("empty");
+				}
+			
+			for( var i = 0; i < 6; i++ ) {
+				Yahtzee.combinaisons[i] = 0;
+			}
+			
+			$(".possibility").each(function(incr, elt) {
+				$(elt).removeClass("possibility");
+				$(elt).empty();
+			});
+			
+			$(".keep").each(function(incr, elt) {
+				$(elt).attr('style', 'visibility:hidden;');
+			})
+			$(".trash").each(function(incr, elt) {
+				$(elt).attr('style', 'visibility:hidden;');
+			})
+		},
+		
+		shuffle: function() {
+			if($("#launchBtn").hasClass("disabled") )
+				return false;
+			
+			if( Yahtzee.launch == 3 )
+				Yahtzee.clear(true);
+			else
+				Yahtzee.clear(false);
+			
+			Yahtzee.launch++;
+			
+			for( var i = 0; i < 5; i++ ) {
+				var html = $("#dice" + (i+1));
+				if( !html.hasClass("selected") ) {
+					var value = Math.round(5 * Math.random());
+					Yahtzee.dices[i] = value;
+					html.removeClass().addClass("dice").addClass("face" + (value + 1));
+				}
+			}
+			
+			$("#launch").html(Yahtzee.launch);
+			if( Yahtzee.launch == 3 )
+				$("#launchBtn").addClass("disabled");
+		},
+		
+		findCombinaisons: function() {
+			for( var i = 0; i < 5; i++) {
+				Yahtzee.combinaisons[Yahtzee.dices[i]]++;
+				Yahtzee.straight = Yahtzee.straight | (1 << Yahtzee.dices[i]);
+			}
+			
+			for( var i = 0; i < 6; i++ ) {
+				var value = Yahtzee.combinaisons[i];
+				switch( value ) {
+				case 2:
+					if( Yahtzee.pair )
+						Yahtzee.doublePair = true;
+					Yahtzee.pair = true;
+					break;
+				case 5:
+					Yahtzee.yahtzee = true;
+				case 4:
+					Yahtzee.four_of_a_kind = true;
+				case 3:
+					Yahtzee.three_of_a_kind = true;
+					break;
+				}
+				
+			}
+			
+			Yahtzee.full = Yahtzee.pair && Yahtzee.three_of_a_kind && !Yahtzee.doublePair;
+			
+			if( (Yahtzee.straight & LARGE_STRAIGHT_MASK1) == LARGE_STRAIGHT_MASK1 ) {
+				Yahtzee.large_straight = true;		
+				Yahtzee.small_straight = true;		
+			} else if( (Yahtzee.straight & LARGE_STRAIGHT_MASK2) == LARGE_STRAIGHT_MASK2 ) {
+				Yahtzee.large_straight = true;
+				Yahtzee.small_straight = true;
+			} else if( (Yahtzee.straight & SMALL_STRAIGHT_MASK1) == SMALL_STRAIGHT_MASK1 ) {
+				Yahtzee.small_straight = true;		
+			} else if( (Yahtzee.straight & SMALL_STRAIGHT_MASK2) == SMALL_STRAIGHT_MASK2  ) {
+				Yahtzee.small_straight = true;
+			} else if( (Yahtzee.straight & SMALL_STRAIGHT_MASK3) == SMALL_STRAIGHT_MASK3  ) {
+				Yahtzee.small_straight = true;		
+			}
+			
+			if( Yahtzee.three_of_a_kind && Yahtzee.keptCombinaisons.indexOf("threeOfAKind") == -1 )
+				$("#threeOfAKindKeep").attr('style', 'visibility:visible;');
+			if( Yahtzee.four_of_a_kind && Yahtzee.keptCombinaisons.indexOf("fourOfAKind") == -1 )
+				$("#fourOfAKindKeep").attr('style', 'visibility:visible;');
+			if( Yahtzee.full && Yahtzee.keptCombinaisons.indexOf("full") == -1 )
+				$("#fullKeep").attr('style', 'visibility:visible;');
+			if( Yahtzee.yahtzee && Yahtzee.keptCombinaisons.indexOf("yahtzee") == -1 )
+				$("#yahtzeeKeep").attr('style', 'visibility:visible;');
+			if( Yahtzee.small_straight && Yahtzee.keptCombinaisons.indexOf("smallStraight") == -1 )
+				$("#smallStraightKeep").attr('style', 'visibility:visible;');
+			if( Yahtzee.large_straight && Yahtzee.keptCombinaisons.indexOf("largeStraight") == -1 )
+				$("#largeStraightKeep").attr('style', 'visibility:visible;');
+			
+			if( Yahtzee.combinaisons[0] > 0 && Yahtzee.keptCombinaisons.indexOf("one") == -1 )
+				$("#oneKeep").attr('style', 'visibility:visible;');
+			if( Yahtzee.combinaisons[1] > 0  && Yahtzee.keptCombinaisons.indexOf("two") == -1 )
+				$("#twoKeep").attr('style', 'visibility:visible;');
+			if( Yahtzee.combinaisons[2] > 0  && Yahtzee.keptCombinaisons.indexOf("three") == -1 )
+				$("#threeKeep").attr('style', 'visibility:visible;');
+			if( Yahtzee.combinaisons[3] > 0  && Yahtzee.keptCombinaisons.indexOf("four") == -1 )
+				$("#fourKeep").attr('style', 'visibility:visible;');
+			if( Yahtzee.combinaisons[4] > 0  && Yahtzee.keptCombinaisons.indexOf("five") == -1 )
+				$("#fiveKeep").attr('style', 'visibility:visible;');
+			if( Yahtzee.combinaisons[5] > 0  && Yahtzee.keptCombinaisons.indexOf("six") == -1 )
+				$("#sixKeep").attr('style', 'visibility:visible;');
+			
+			if( Yahtzee.keptCombinaisons.indexOf("luck") == -1 )
+				$("#luckKeep").attr('style', 'visibility:visible;');
+			
+			$(".trash").each(function(incr, elt){
+				var html = $(elt)
+				if( Yahtzee.keptCombinaisons.indexOf(html.attr('data-id')) == -1 )
+					html.attr('style', 'visibility:visible;');
+			});
+		},
+		
+		findPossibilities: function() {
+			var possibilities = {
+					"threeOfAKind" : 0,
+					"fourOfAKind" : 0,
+					"full" : 0,
+					"yahtzee" : 0,
+					"smallStraight" : 0,
+					"largeStraight" : 0
+			};
+			
+			if( Yahtzee.three_of_a_kind && !Yahtzee.full) {
+				possibilities["full"] = Math.max( possibilities["full"], 17 );
+				possibilities["fourOfAKind"] = Math.max( possibilities["fourOfAKind"], 31);
+				possibilities["yahtzee"] = Math.max( possibilities["yahtzee"],  3);
+			}
+			if( Yahtzee.pair ) {
+				if( !Yahtzee.three_of_a_kind )
+					possibilities["threeOfAKind"] = Math.max( possibilities["threeOfAKind"], 42);
+				possibilities["fourOfAKind"] = Math.max( possibilities["fourOfAKind"], 7);
+				if( !Yahtzee.full )
+					possibilities["full"] = Math.max( possibilities["full"], 10);
+				possibilities["yahtzee"] = Math.max( possibilities["yahtzee"], 7);
+			}
+			if( Yahtzee.four_of_a_kind ) {
+				possibilities["yahtzee"] = Math.max( possibilities["yahtzee"], 17);
+			}
+			if( Yahtzee.doublePair ) {
+				possibilities["full"] = Math.max( possibilities["full"], 33);
+			}
+			if( !Yahtzee.large_straight && 
+					((Yahtzee.straight & SMALL_STRAIGHT_MASK1) == SMALL_STRAIGHT_MASK1 || (Yahtzee.straight & SMALL_STRAIGHT_MASK3) == SMALL_STRAIGHT_MASK3) ) {
+				possibilities["largeStraight"] = Math.max( possibilities["largeStraight"], 17);
+			} else if( !Yahtzee.large_straight && (Yahtzee.straight & SMALL_STRAIGHT_MASK2) == SMALL_STRAIGHT_MASK2 ) {
+				possibilities["largeStraight"] = Math.max( possibilities["largeStraight"], 33);
+			}
+			
+			$.each(possibilities, function(key, value) {
+				var html = $("#" + key + "Probability");
+				html.empty().addClass("possibility");
+				if( value > 0 && Yahtzee.keptCombinaisons.indexOf(key) == -1 ) {
+					html.append(value + "%");
+				} else
+					html.append("&nbsp;");
+			});
+		},
+		
+		keep: function(id) {
+			var score = 0;
+			switch(id) {
+			case "one" :
+				score = Yahtzee.combinaisons[0] * 1;
+				Yahtzee.scoreUp += score;
+				break;
+			case "two" :
+				score = Yahtzee.combinaisons[1] * 2;
+				Yahtzee.scoreUp += score;
+				break;
+			case "three" :
+				score = Yahtzee.combinaisons[2] * 3;
+				Yahtzee.scoreUp += score;
+				break;
+			case "four" :
+				score = Yahtzee.combinaisons[3] * 4;
+				Yahtzee.scoreUp += score;
+				break;
+			case "five" :
+				score = Yahtzee.combinaisons[4] * 5;
+				Yahtzee.scoreUp += score;
+				break;
+			case "six" :
+				score = Yahtzee.combinaisons[5] * 6;
+				Yahtzee.scoreUp += score;
+				break;
+			case "threeOfAKind" :
+			case "fourOfAKind" :
+			case "luck" :
+				$.each(Yahtzee.dices, function(index, elt){score += (elt + 1)});
+				Yahtzee.scoreDown += score;
+				break;
+			case "full" :
+				score = 25;
+				Yahtzee.scoreDown += score;
+				break;
+			case "smallStraight" :
+				score = 30;
+				Yahtzee.scoreDown += score;
+				break;
+			case "largeStraight" :
+				score = 40;
+				Yahtzee.scoreDown += score;
+				break;
+			case "yahtzee" :
+				score = 50;
+				Yahtzee.scoreDown += score;
+				break;
+			}
+			
+			$("#" + id + "Score").html(score);
+			
+			Yahtzee.keptCombinaisons.push(id);
+			Yahtzee.clear(true);
+			
+			if( Yahtzee.keptCombinaisons.length < 13 )
+				$("#launchBtn").removeClass("disabled");
+			else
+				Yahtzee.show_game_over();
+		},
+		
+		trash : function(id) {
+			$("#" + id + "Score").html(0).addClass('trashed');
+			$("#" + id + "Label").attr('style', 'text-decoration:line-through;');
+			Yahtzee.keptCombinaisons.push(id);
+			Yahtzee.clear(true);
+			if( Yahtzee.keptCombinaisons.length < 13 )
+				$("#launchBtn").removeClass("disabled");
+			else
+				Yahtzee.show_game_over();
+		},
+		
+		show_game_over: function() {
+			$("#upperScore").append(Yahtzee.scoreUp);
+			if( Yahtzee.scoreUp > 63 ) {
+				$("#bonus").append(35);
+				Yahtzee.scoreUp += 35;
+			} else
+				$("#bonus").append(0);
+			$("#lowerScore").append(Yahtzee.scoreDown);
+			$("#totalScore").append(Yahtzee.scoreDown + Yahtzee.upperScore);
+
+			$("#scorePanel").show();
+		}
+}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
demo/yahtzee/lib/jquery-1.7.1.min.js


+ 53 - 0
docs/development/cloudbudget.md

@@ -0,0 +1,53 @@
+#Introduction [![Build Status][build-image]][build-url] [![Coverage Status][coverage-image]][coverage-url]
+Cloudbudget is a work-in-progress online Money-like application.
+
+It's written using ExpressJS and provided only REST services. The web interface is in a different project [CloudBugdet-AngularJS](/development/cloudbudget_angularjs).
+
+##Requirements
+CloudBudget needs a Mongo database. Database configuration is done in the _config/db.js_ file.
+
+The environment mode is defined by the environment variable NODE_ENV.
+
+##Features
+The following features are developed:
+
+* API security done with JWT
+* API documentation
+* User log on / log in
+* User deregistration
+* Bank account CRUD
+* Bank accounts listing
+* Deposit / Bill entry CRUD
+* Deposit / Bill entries listing
+
+##Usage
+To initialize the application, install all dependencies with
+```
+npm install
+```
+
+To launch CloudBudget, use the following command :
+```
+NODE_ENV=development node app.js
+```
+
+If the PORT environment variable is set, it will be used, if not, the default port is 3000.
+The server configuration is done with the _config/server.js_ file.
+
+##API documentation
+The root URL is linked to the API documentation.
+The API documentation is generated with apidocs into the _public_ folder. To generate it, use the following command :
+```
+npm run-script generate-doc
+```
+
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-fork-ribbon-css/0.2.0/gh-fork-ribbon.min.css" />
+<!--[if lt IE 9]>
+  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-fork-ribbon-css/0.2.0/gh-fork-ribbon.ie.min.css" />
+<![endif]-->
+ <a class="github-fork-ribbon" href="https://github.com/Febbweiss/CloudBudget" target="_blank" title="Fork me on GitHub">Fork me on GitHub</a>
+ 
+ [build-image]: https://travis-ci.org/Febbweiss/CloudBudget.svg?branch=master
+[build-url]: https://travis-ci.org/Febbweiss/CloudBudget
+[coverage-image]:https://coveralls.io/repos/Febbweiss/CloudBudget/badge.svg?branch=master&service=github
+[coverage-url]: https://coveralls.io/github/Febbweiss/CloudBudget?branch=master

+ 35 - 0
docs/development/cloudbudget_angularjs.md

@@ -0,0 +1,35 @@
+#Introduction [![Build Status][build-image]][build-url] [![Coverage Status][coverage-image]][coverage-url]
+Cloudbudget-AngularJS is a work-in-progress web application for [CloudBugdet](/development/cloudbudget) written in AngularJS.
+
+##Requirements
+CloudBudget-AngularJS needs a running CloudBudget instance. The access to this instance is set in the _public/js/app.js_ file with the _HOST_ variable.
+
+
+##Features
+This web application covers all CloudBudget features.
+
+##Usage
+To initialize the application, install all dependencies with
+```
+npm install
+```
+
+To launch CloudBudget, use the following command :
+```
+NODE_ENV=development node app.js
+```
+
+If the PORT environment variable is set, it will be used, if not, the default port is 3000.
+The server configuration is done with the _config/server.js_ file.
+
+
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-fork-ribbon-css/0.2.0/gh-fork-ribbon.min.css" />
+<!--[if lt IE 9]>
+  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-fork-ribbon-css/0.2.0/gh-fork-ribbon.ie.min.css" />
+<![endif]-->
+ <a class="github-fork-ribbon" href="https://github.com/Febbweiss/CloudBudget-AngularJS" target="_blank" title="Fork me on GitHub">Fork me on GitHub</a>
+ 
+ [build-image]: https://travis-ci.org/Febbweiss/CloudBudget-AngularJS.svg?branch=master
+[build-url]: https://travis-ci.org/Febbweiss/CloudBudget-AngularJS
+[coverage-image]:https://coveralls.io/repos/Febbweiss/CloudBudget-AngularJS/badge.svg?branch=master&service=github
+[coverage-url]: https://coveralls.io/github/Febbweiss/CloudBudget-AngularJS?branch=master

+ 33 - 0
docs/development/filebrowser_durandal_widget.md

@@ -0,0 +1,33 @@
+# Durandal Filebrowser widget
+
+## What's this widget ?
+
+This [Durandal](http://durandaljs.com/) widget allows to display a folder tree and add some actions to manipulate this items :
+
+- A clic on a file or folder selects it
+- A double-clic opens / closes a folder
+- A double-clic displays the file content in the editor
+- A right-clic opens a context menu with different options
+    - Rename the item
+    - Copy the selected item(s)
+    - Paste the selected item(s)
+    - Create an item (in a folder)
+    - Delete an item (and its components)
+
+##Demo
+<object data="/demo/filebrowser-durandal-widget/index.html" width="800" height="600">
+    <embed src="/demo/filebrowser-durandal-widget/index.html" width="800" height="600"> </embed>
+    <iframe scrolling="no" frameborder="0" src="/demo/filebrowser-durandal-widget/index.html" style="width: 800px; height: 600px; overflow:hidden;">
+</iframe>
+</object>	
+   
+##Licence
+
+Source code is under [MIT Licence](http://opensource.org/licenses/mit-license.php) 
+
+
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-fork-ribbon-css/0.2.0/gh-fork-ribbon.min.css" />
+<!--[if lt IE 9]>
+  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-fork-ribbon-css/0.2.0/gh-fork-ribbon.ie.min.css" />
+<![endif]-->
+ <a class="github-fork-ribbon" href="https://github.com/Febbweiss/filebrowser-durandal-widget" target="_blank" title="Fork me on GitHub">Fork me on GitHub</a>

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä