├── README.md ├── css └── main.css ├── img ├── apple-touch-icon-114x114.png ├── apple-touch-icon-72x72.png ├── bg.png ├── blank.png ├── door.png ├── end.png ├── favicon.ico ├── game-window.png ├── goal.png ├── loading-text.png ├── loading.gif ├── menu-button.png ├── new-game.png ├── next-button.png ├── platform-left.png ├── platform-middle.png ├── platform-right.png ├── player.png ├── reset.png ├── scorecard-background.png ├── screenshot-1.png ├── screenshot-2.png ├── screenshot-3.png ├── screenshot-4.png ├── selected.png ├── splash-screen.png ├── star-off.png ├── star-on.png ├── switch.png └── touch-icon-iphone.png ├── index.htm └── js ├── gamejs ├── gamejs.min.js └── yabble.js ├── jquery-1.8.2.js ├── lib ├── block.js ├── camera.js ├── door.js ├── gates │ ├── andGate.js │ ├── notGate.js │ └── orGate.js ├── goal.js ├── io.js ├── levels │ ├── level_1.js │ ├── level_2.js │ ├── level_3.js │ ├── level_4.js │ └── level_5.js ├── lever.js ├── menu.js ├── menuItem.js ├── platform.js ├── playable.js ├── player.js ├── scorecard.js ├── startMenu.js ├── tooltip.js ├── utils.js └── world.js └── main.js /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | ## Synopsis 5 | 6 | Alge has been trapped in Area 51! Help him escape by moving him through the levels to the escape tube at the end. 7 | Clone your way top victory by turning the correct combination of switches on to turn off the lasers and escape to 8 | freedom. 9 | 10 | Can you get three stars on every level? 11 | 12 | ## Controls 13 | 14 | Control Alge by using the following keys: 15 | 16 | * D or Right arrow to move Alge right 17 | * A or Left arrow to move Alge left 18 | * W, Up arrow or Space to jump 19 | * E or Enter to activate or deactivate a switch 20 | * C to clone Alge 21 | 22 | ##Browser Compatibility 23 | 24 | Tested in Firefox Safari, and Chrome on Windows, Linux and Mac. 25 | 26 | ##Technology Used 27 | 28 | * HTML5 29 | * JavaScript 30 | * [GameJs](http://gamejs.org/) 31 | 32 | ##Screenshots 33 |   34 | 35 |   36 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | body 2 | { 3 | background-color:#ECECEC; 4 | overflow:hidden; 5 | } 6 | 7 | #preload, #gameWindow 8 | { 9 | margin:auto; 10 | } 11 | 12 | #gameWindow 13 | { 14 | padding:19px 29px; 15 | width:800px; 16 | height:600px; 17 | background-image:url('../img/game-window.png'); 18 | background-repeat:no-repeat; 19 | display:none; 20 | } 21 | 22 | canvas 23 | { 24 | -moz-box-shadow: 0 0 10px #000000; 25 | -webkit-box-shadow: 0 0 10px #000000; 26 | box-shadow: 0 0 10px #000000; 27 | } 28 | 29 | #gameEnd 30 | { 31 | position:relative; 32 | display:none; 33 | width:800px; 34 | height:600px; 35 | background-image:url('../img/end.png'); 36 | top:-604px; 37 | } 38 | 39 | #preload 40 | { 41 | padding-top:250px; 42 | width:800px; 43 | height:600px; 44 | text-align:center; 45 | } 46 | 47 | #preload img 48 | { 49 | display:block; 50 | margin: auto; 51 | } 52 | 53 | #game_tooltip 54 | { 55 | position:relative; 56 | width:800px; 57 | background: -webkit-linear-gradient(#ECECEC, #CACACA); 58 | background: -moz-linear-gradient(#ECECEC, #CACACA); 59 | background: -o-linear-gradient(#ECECEC, #CACACA); 60 | background: -ms-linear-gradient(#ECECEC, #CACACA); 61 | background: linear-gradient(#ECECEC, #CACACA); 62 | border-bottom: 1px solid #9A9A9A; 63 | border-radius: 0px 0px 10px 10px; 64 | color:#585351; 65 | text-shadow: 0px 1px 0px white; 66 | padding-top:10px; 67 | padding-bottom:10px; 68 | font-weight: bold; 69 | text-align: center; 70 | top:-605px; 71 | display:none; 72 | } 73 | 74 | #game_scorecard_bg 75 | { 76 | position:relative; 77 | width:800px; 78 | height:600px; 79 | top:-624px; 80 | background-color: rgba(0,0,0,0.4); 81 | display:none; 82 | } 83 | 84 | #game_scorecard 85 | { 86 | background-image:url('../img/scorecard-background.png'); 87 | position:relative; 88 | top:68px; 89 | left:140px; 90 | width:500px; 91 | height:460px; 92 | z-index: 999; 93 | font-family: Helvetica,arial,sans-serif; 94 | } 95 | 96 | #game_scorecard h1 97 | { 98 | font-size:30px; 99 | text-transform:uppercase; 100 | padding-top:37px; 101 | margin-left:41px; 102 | } 103 | 104 | #game_scorecard p 105 | { 106 | font-size:20px; 107 | font-weight: bold; 108 | margin-left:38px; 109 | border-bottom:1px solid #000000; 110 | width:418px; 111 | margin-top:10px; 112 | } 113 | 114 | #game_scorecard p.scoreholder 115 | { 116 | height:38px; 117 | } 118 | 119 | #game_scorecard p span 120 | { 121 | float:right; 122 | } 123 | 124 | 125 | #game_scorecard .score .star 126 | { 127 | width:42px; 128 | height:41px; 129 | background-image:url(../img/star-off.png); 130 | background-repeat: no-repeat; 131 | float:left; 132 | position:relative; 133 | top:-10px; 134 | } 135 | 136 | #game_scorecard .score .star.enabled 137 | { 138 | background-image:url(../img/star-on.png); 139 | } 140 | 141 | #game_scorecard .nextLevel, 142 | #game_scorecard .resetLevel, 143 | #game_scorecard .mainMenu 144 | { 145 | display:block; 146 | font-size:-1px; 147 | text-indent:-9999px; 148 | outline: none; 149 | background-repeat:no-repeat; 150 | position:relative; 151 | margin-top:60px; 152 | } 153 | 154 | #game_scorecard .nextLevel 155 | { 156 | background-image:url(../img/next-button.png); 157 | width:180px; 158 | height:92px; 159 | top:-313px; 160 | margin-left:277px; 161 | background-position: 0px 0px; 162 | } 163 | 164 | #game_scorecard .nextLevel:hover 165 | { 166 | background-position: -190px 0px; 167 | } 168 | 169 | #game_scorecard .mainMenu 170 | { 171 | background-image:url(../img/menu-button.png); 172 | width:180px; 173 | height:92px; 174 | margin-left:28px; 175 | background-position: 0px 0px; 176 | } 177 | 178 | #game_scorecard .mainMenu:hover 179 | { 180 | background-position: -190px 0px; 181 | } 182 | 183 | #game_scorecard .resetLevel 184 | { 185 | background-image:url(../img/reset.png); 186 | width:100px; 187 | height:101px; 188 | top:-160px; 189 | margin-left:198px; 190 | } 191 | 192 | #game_scorecard .disabled 193 | { 194 | opacity:0.5; 195 | } 196 | 197 | #game_scorecard .disabled:hover 198 | { 199 | cursor: default; 200 | background-position: 0px 0px; 201 | } 202 | -------------------------------------------------------------------------------- /img/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /img/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /img/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/bg.png -------------------------------------------------------------------------------- /img/blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/blank.png -------------------------------------------------------------------------------- /img/door.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/door.png -------------------------------------------------------------------------------- /img/end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/end.png -------------------------------------------------------------------------------- /img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/favicon.ico -------------------------------------------------------------------------------- /img/game-window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/game-window.png -------------------------------------------------------------------------------- /img/goal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/goal.png -------------------------------------------------------------------------------- /img/loading-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/loading-text.png -------------------------------------------------------------------------------- /img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/loading.gif -------------------------------------------------------------------------------- /img/menu-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/menu-button.png -------------------------------------------------------------------------------- /img/new-game.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/new-game.png -------------------------------------------------------------------------------- /img/next-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/next-button.png -------------------------------------------------------------------------------- /img/platform-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/platform-left.png -------------------------------------------------------------------------------- /img/platform-middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/platform-middle.png -------------------------------------------------------------------------------- /img/platform-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/platform-right.png -------------------------------------------------------------------------------- /img/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/player.png -------------------------------------------------------------------------------- /img/reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/reset.png -------------------------------------------------------------------------------- /img/scorecard-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/scorecard-background.png -------------------------------------------------------------------------------- /img/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/screenshot-1.png -------------------------------------------------------------------------------- /img/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/screenshot-2.png -------------------------------------------------------------------------------- /img/screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/screenshot-3.png -------------------------------------------------------------------------------- /img/screenshot-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/screenshot-4.png -------------------------------------------------------------------------------- /img/selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/selected.png -------------------------------------------------------------------------------- /img/splash-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/splash-screen.png -------------------------------------------------------------------------------- /img/star-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/star-off.png -------------------------------------------------------------------------------- /img/star-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/star-on.png -------------------------------------------------------------------------------- /img/switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/switch.png -------------------------------------------------------------------------------- /img/touch-icon-iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dave-and-Mike/game-off-2012/7780711180fc2b5a074928f804d47c7344851264/img/touch-icon-iphone.png -------------------------------------------------------------------------------- /index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 23 | Alge's Escapade 24 | 25 | 26 |
27 | 28 | 29 |
30 |
31 | 32 |
33 |
34 | 35 | -------------------------------------------------------------------------------- /js/gamejs/yabble.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 James Brantly 3 | * 4 | * Permission is hereby granted, free of charge, to any person 5 | * obtaining a copy of this software and associated documentation 6 | * files (the "Software"), to deal in the Software without 7 | * restriction, including without limitation the rights to use, 8 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the 10 | * Software is furnished to do so, subject to the following 11 | * conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | * OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | (function(globalEval) { 27 | 28 | var Yabble = function() { 29 | throw "Synchronous require() is not supported."; 30 | }; 31 | 32 | Yabble.unit = {}; 33 | 34 | var _moduleRoot = '', 35 | _modules, 36 | _callbacks, 37 | _fetchFunc, 38 | _timeoutLength = 20000, 39 | _mainProgram; 40 | 41 | 42 | var head = document.getElementsByTagName('head')[0]; 43 | 44 | // Shortcut to native hasOwnProperty 45 | var hasOwnProperty = Object.prototype.hasOwnProperty; 46 | 47 | // A for..in implementation which uses hasOwnProperty and fixes IE non-enumerable issues 48 | if ((function() {for (var prop in {hasOwnProperty: true}) { return prop; }})() == 'hasOwnProperty') { 49 | var forIn = function(obj, func, ctx) { 50 | for (var prop in obj) { 51 | if (hasOwnProperty.call(obj, prop)) { 52 | func.call(ctx, prop); 53 | } 54 | } 55 | }; 56 | } 57 | else { 58 | var ieBadProps = [ 59 | 'isPrototypeOf', 60 | 'hasOwnProperty', 61 | 'toLocaleString', 62 | 'toString', 63 | 'valueOf' 64 | ]; 65 | 66 | var forIn = function(obj, func, ctx) { 67 | for (var prop in obj) { 68 | if (hasOwnProperty.call(obj, prop)) { 69 | func.call(ctx, prop); 70 | } 71 | } 72 | 73 | for (var i = ieBadProps.length; i--;) { 74 | var prop = ieBadProps[i]; 75 | if (hasOwnProperty.call(obj, prop)) { 76 | func.call(ctx, prop); 77 | } 78 | } 79 | }; 80 | } 81 | 82 | // Array convenience functions 83 | var indexOf = function(arr, val) { 84 | for (var i = arr.length; i--;) { 85 | if (arr[i] == val) { return i; } 86 | } 87 | return -1; 88 | }; 89 | 90 | var removeWhere = function(arr, func) { 91 | var i = 0; 92 | while (i < arr.length) { 93 | if (func.call(null, arr[i], i) === true) { 94 | arr.splice(i, 1); 95 | } 96 | else { 97 | i++; 98 | } 99 | } 100 | }; 101 | 102 | var combinePaths = function(relPath, refPath) { 103 | var relPathParts = relPath.split('/'); 104 | refPath = refPath || ''; 105 | if (refPath.length && refPath.charAt(refPath.length-1) != '/') { 106 | refPath += '/'; 107 | } 108 | var refPathParts = refPath.split('/'); 109 | refPathParts.pop(); 110 | var part; 111 | while (part = relPathParts.shift()) { 112 | if (part == '.') { continue; } 113 | else if (part == '..' 114 | && refPathParts.length 115 | && refPathParts[refPathParts.length-1] != '..') { refPathParts.pop(); } 116 | else { refPathParts.push(part); } 117 | } 118 | return refPathParts.join('/'); 119 | }; 120 | 121 | // Takes a relative path to a module and resolves it according to the reference path 122 | var resolveModuleId = Yabble.unit.resolveModuleId = function(relModuleId, refPath) { 123 | if (relModuleId.charAt(0) != '.') { 124 | return relModuleId; 125 | } 126 | else { 127 | return combinePaths(relModuleId, refPath); 128 | } 129 | }; 130 | 131 | // Takes a module's ID and resolves a URI according to the module root path 132 | var resolveModuleUri = function(moduleId) { 133 | if (moduleId.charAt(0) != '.') { 134 | return _moduleRoot+moduleId+'.js'; 135 | } 136 | else { 137 | return this._resolveModuleId(moduleId, _moduleRoot)+'.js'; 138 | } 139 | }; 140 | 141 | // Returns a module object from the module ID 142 | var getModule = function(moduleId) { 143 | if (!hasOwnProperty.call(_modules, moduleId)) { 144 | return null; 145 | } 146 | return _modules[moduleId]; 147 | }; 148 | 149 | // Adds a callback which is executed when all deep dependencies are loaded 150 | var addCallback = function(deps, cb) { 151 | _callbacks.push([deps.slice(0), cb]); 152 | }; 153 | 154 | // Generic implementation of require.ensure() which takes a reference path to 155 | // use when resolving relative module IDs 156 | var ensureImpl = function(deps, cb, refPath) { 157 | var unreadyModules = []; 158 | 159 | for (var i = deps.length; i--;) { 160 | var moduleId = resolveModuleId(deps[i], refPath), 161 | module = getModule(moduleId); 162 | 163 | if (!areDeepDepsDefined(moduleId)) { 164 | unreadyModules.push(moduleId); 165 | } 166 | } 167 | 168 | if (unreadyModules.length) { 169 | addCallback(unreadyModules, function() { 170 | cb(createRequireFunc(refPath)); 171 | }); 172 | queueModules(unreadyModules); 173 | } 174 | else { 175 | setTimeout(function() { 176 | cb(createRequireFunc(refPath)); 177 | }, 0); 178 | } 179 | }; 180 | 181 | // Creates a require function that is passed into module factory functions 182 | // and require.ensure() callbacks. It is bound to a reference path for 183 | // relative require()s 184 | var createRequireFunc = function(refPath) { 185 | var require = function(relModuleId) { 186 | var moduleId = resolveModuleId(relModuleId, refPath), 187 | module = getModule(moduleId); 188 | 189 | if (!module) { 190 | throw "Module not loaded"; 191 | } 192 | else if (module.error) { 193 | throw "Error loading module"; 194 | } 195 | 196 | if (!module.exports) { 197 | module.exports = {}; 198 | var moduleDir = moduleId.substring(0, moduleId.lastIndexOf('/')+1), 199 | injects = module.injects, 200 | args = []; 201 | 202 | for (var i = 0, n = injects.length; i= 0; 456 | }); 457 | 458 | transport.modules.push({ 459 | id: arguments[0], 460 | factory: arguments[2], 461 | injects: arguments[1] 462 | }); 463 | } 464 | return transport; 465 | }; 466 | 467 | // Set the uri which forms the conceptual module namespace root 468 | Yabble.setModuleRoot = function(path) { 469 | if (!(/^http(s?):\/\//.test(path))) { 470 | var href = window.location.href; 471 | href = href.substr(0, href.lastIndexOf('/')+1); 472 | path = combinePaths(path, href); 473 | } 474 | 475 | if (path.length && path.charAt(path.length-1) != '/') { 476 | path += '/'; 477 | } 478 | 479 | _moduleRoot = path; 480 | }; 481 | 482 | // Set a timeout period for async module loading 483 | Yabble.setTimeoutLength = function(milliseconds) { 484 | _timeoutLength = milliseconds; 485 | }; 486 | 487 | // Use script tags with wrapped code instead of XHR+eval() 488 | Yabble.useScriptTags = function() { 489 | _fetchFunc = loadModuleByScript; 490 | }; 491 | 492 | // Define a module per various transport specifications 493 | Yabble.def = Yabble.define = function() { 494 | var transport = normalizeTransport.apply(null, arguments); 495 | 496 | var unreadyModules = [], 497 | definedModules = []; 498 | 499 | var deps = transport.deps; 500 | 501 | for (var i = transport.modules.length; i--;) { 502 | var moduleDef = transport.modules[i], 503 | moduleId = moduleDef.id, 504 | module = getModule(moduleId); 505 | 506 | if (!module) { 507 | module = _modules[moduleId] = {}; 508 | } 509 | module.module = { 510 | id: moduleId, 511 | uri: resolveModuleUri(moduleId) 512 | }; 513 | 514 | module.defined = true; 515 | module.deps = deps.slice(0); 516 | module.injects = moduleDef.injects; 517 | module.factory = moduleDef.factory; 518 | definedModules.push(module); 519 | } 520 | 521 | for (var i = deps.length; i--;) { 522 | var moduleId = deps[i], 523 | module = getModule(moduleId); 524 | 525 | if (!module || !areDeepDepsDefined(moduleId)) { 526 | unreadyModules.push(moduleId); 527 | } 528 | } 529 | 530 | if (unreadyModules.length) { 531 | setTimeout(function() { 532 | queueModules(unreadyModules); 533 | }, 0); 534 | } 535 | 536 | fireCallbacks(); 537 | }; 538 | 539 | Yabble.isKnown = function(moduleId) { 540 | return getModule(moduleId) != null; 541 | }; 542 | 543 | Yabble.isDefined = function(moduleId) { 544 | var module = getModule(moduleId); 545 | return !!(module && module.defined); 546 | }; 547 | 548 | // Do an async lazy-load of modules 549 | Yabble.ensure = function(deps, cb) { 550 | ensureImpl(deps, cb, ''); 551 | }; 552 | 553 | // Start an application via a main program module 554 | Yabble.run = function(program, cb) { 555 | program = _mainProgram = resolveModuleId(program, ''); 556 | Yabble.ensure([program], function(require) { 557 | require(program); 558 | if (cb != null) { cb(); } 559 | }); 560 | }; 561 | 562 | // Reset internal state. Used mostly for unit tests. 563 | Yabble.reset = function() { 564 | _mainProgram = null; 565 | _modules = {}; 566 | _callbacks = []; 567 | 568 | // Built-in system module 569 | Yabble.define({ 570 | 'system': function(require, exports, module) {} 571 | }); 572 | }; 573 | 574 | Yabble.reset(); 575 | 576 | // Export to the require global 577 | window.require = Yabble; 578 | })(function(code) { 579 | with (window) { 580 | return eval(code); 581 | }; 582 | }); -------------------------------------------------------------------------------- /js/lib/block.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a block that the player may hit 3 | * 4 | * @author David North 5 | */ 6 | function block() 7 | { 8 | //Load the variables required by gamejs.sprite.Sprite 9 | block.superConstructor.apply(this, [0, 0]); 10 | this.image = gamejs.image.load('img/blank.png'); 11 | 12 | var _size = this.image.getSize(); 13 | this.rect = new gamejs.Rect([0, 0], [800, 20]); 14 | 15 | /** 16 | * Sets the position of the object 17 | * 18 | * @param float x The X co-ordinate 19 | * @param float y The Y co-ordinate 20 | * 21 | * @return block 22 | */ 23 | this.setPosition = function(x, y){ 24 | if ( typeof(x) !== 'number' ) 25 | { 26 | throw 'X must be a number'; 27 | } 28 | 29 | if ( typeof(y) !== 'number' ) 30 | { 31 | throw 'Y must be a number'; 32 | } 33 | 34 | this.rect.x = x; 35 | this.rect.y = y; 36 | 37 | return this; 38 | } 39 | 40 | /** 41 | * Returns the position of the object 42 | * 43 | * @return object Contains an x and y property 44 | */ 45 | this.getPosition = function(){ 46 | return {"x": this.rect.x, "y": this.rect.y}; 47 | } 48 | 49 | /** 50 | * Handles the collision between a playable and this object 51 | * 52 | * @return block 53 | */ 54 | this.handleCollision = function( playable ){ 55 | playerCollides(playable, this.rect); 56 | 57 | return this; 58 | } 59 | } 60 | 61 | //Extend the playable object so that the parent is the sprite 62 | gamejs.utils.objects.extend(block, gamejs.sprite.Sprite); -------------------------------------------------------------------------------- /js/lib/camera.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the a camera, which is capable of panning around the world 3 | * 4 | * @author David North 5 | */ 6 | function camera( world ) 7 | { 8 | //The maximum speed that the camera can move at 9 | const MAX_VELOCITY = 300; 10 | 11 | /** 12 | * @var world The world that is being looked at 13 | */ 14 | var _world = world; 15 | 16 | /** 17 | * @var boolean|gamejs.Rect Whether or not the camera is currently 18 | * tracking an object. This variable contains the object being tracked if so. 19 | */ 20 | var _track = false; 21 | 22 | /** 23 | * @var boolean Whether or not the camera is animating to position 24 | */ 25 | var _animating = false; 26 | 27 | /** 28 | * @var gamejs.Rect The size and position of the viewport 29 | * (what the player can see) 30 | */ 31 | var _viewport = new gamejs.Rect([0, 0], [0, 0]); 32 | 33 | /** 34 | * Sets the width of the viewport 35 | * 36 | * @param float width 37 | * 38 | * @return camera 39 | */ 40 | this.setWidth = function( width ){ 41 | if ( typeof(width) != 'number' ) 42 | { 43 | throw 'Width must be a number'; 44 | } 45 | 46 | _viewport.width = width; 47 | return this; 48 | } 49 | 50 | /** 51 | * Sets the height of the viewport 52 | * 53 | * @param float height 54 | * 55 | * @return camera 56 | */ 57 | this.setHeight = function( height ){ 58 | if ( typeof(height) != 'number' ) 59 | { 60 | throw 'Height must be a number'; 61 | } 62 | 63 | _viewport.height = height; 64 | return this; 65 | } 66 | 67 | /** 68 | * Sets the new position of the camera. Sanatises the position so that it 69 | * never looks outside of the world 70 | * 71 | * @param float x 72 | * @param float y 73 | * 74 | * @return camera 75 | */ 76 | this.setPosition = function( x, y ){ 77 | if ( typeof(x) != 'number' ) 78 | { 79 | throw 'X position must be a number'; 80 | } 81 | 82 | if ( typeof(y) != 'number' ) 83 | { 84 | throw 'Y position must be a number'; 85 | } 86 | 87 | //Get the last position that the camera was at before being moved 88 | var oldX = _viewport.x; 89 | var oldY = _viewport.y; 90 | 91 | //Sanitise the position and set the camera 92 | var newPosition = _getSanatisedPosition(x, y); 93 | _viewport.x = newPosition.x; 94 | _viewport.y = newPosition.y; 95 | 96 | //Update the objects in the world (i.e. shift them, the number of pixels 97 | //the camera has 'moved', giving the impression of movement) 98 | _updateObjects((_viewport.x - oldX), (_viewport.y - oldY)); 99 | 100 | return this; 101 | } 102 | 103 | /** 104 | * Whether or not the camera is currently animating towards an object 105 | * 106 | * @return boolean 107 | */ 108 | this.isAnimating = function(){ 109 | return _animating; 110 | } 111 | 112 | /** 113 | * Focuses the camera on a rectangle, with the option to track it 114 | * continuously and to animate the movement 115 | * 116 | * @param gamejs.Rect rect The rectangle to focus on 117 | * @param boolean track Optional. Whether to track the object continuously 118 | * @param boolean animate Optional. Whether to animate the camera or not 119 | * 120 | * @return camera 121 | */ 122 | this.focusOn = function( rect, track, animate ){ 123 | //Mmmmm.. type hinting 124 | if ( !(rect instanceof gamejs.Rect) ) 125 | { 126 | throw 'Rectangle must be an instance of gamejs.Rect'; 127 | } 128 | 129 | //If track is not defined, then set the default value to false. 130 | //Otherwise, ensure that it's a boolean 131 | if (typeof(track) === "undefined") 132 | { 133 | track = false; 134 | } 135 | else if ( typeof(track) !== 'boolean' ) 136 | { 137 | throw 'Optional track flag must be a boolean'; 138 | } 139 | 140 | //If animate is not defined, then set the default value to false. 141 | //Otherwise, ensure that it's a boolean 142 | if (typeof(animate) === "undefined") 143 | { 144 | animate = false; 145 | } 146 | else if ( typeof(animate) !== 'boolean' ) 147 | { 148 | throw 'Optional animate flag must be a boolean'; 149 | } 150 | 151 | //The new Camera position should have the middle of the camera pointing 152 | //at the middle of the rectangle 153 | var newCameraX = rect.center[0] - (_viewport.width / 2); 154 | var newCameraY = rect.center[1] - (_viewport.height / 2); 155 | 156 | //If we are contantly tracking this object, then set that here 157 | if ( track ) 158 | { 159 | _track = rect; 160 | } 161 | else 162 | { 163 | _track = false; 164 | } 165 | 166 | //If we are animating, don't move the camera (let the update method 167 | //do it), otherwise set the position now 168 | if ( animate ) 169 | { 170 | _animating = true; 171 | } 172 | else 173 | { 174 | this.setPosition( newCameraX, newCameraY ); 175 | } 176 | 177 | return this; 178 | } 179 | 180 | /** 181 | * Updates the camera position if it is tracking and also moves the camera 182 | * animation if animating 183 | * 184 | * @param int msDuration 185 | */ 186 | this.update = function( msDuration ){ 187 | if ( _track ) 188 | { 189 | //Get the new X and Y c-ordinates, so that the camera is focused 190 | //on the middle of the object 191 | var destinationX = _track.center[0] - (_viewport.width / 2); 192 | var destinationY = _track.center[1] - (_viewport.height / 2); 193 | 194 | destinationX += _viewport.x 195 | destinationY += _viewport.y 196 | 197 | //If the tracking is animated then get the next frame, before 198 | //setting the new position to that instead 199 | if ( _animating ) 200 | { 201 | var pos = _getNextAnimatedPosition( 202 | destinationX, destinationY, msDuration 203 | ); 204 | 205 | destinationX = pos.x; 206 | destinationY = pos.y; 207 | } 208 | 209 | this.setPosition( destinationX, destinationY ); 210 | } 211 | } 212 | 213 | /** 214 | * Ensures that the camera is not intersecting the level (i.e. going over 215 | * the bounding box). This is so that the camera is always focused on 216 | * objects inside the level, not outside it 217 | * 218 | * @param float x The proposed X position 219 | * @param float y The proposed Y position 220 | * 221 | * @return object An object containing the x and y position 222 | */ 223 | var _getSanatisedPosition = function( x, y ){ 224 | var position = { 'x': x, 'y': y }; 225 | var level = _world.getBoundingRect(); 226 | 227 | //Set up the collision test object (essentially a copy of the cameras 228 | //viewport, with the new x and y co-ordinates) 229 | var collideTest = new gamejs.Rect( 230 | [x, y], [_viewport.width, _viewport.height] 231 | ); 232 | 233 | //Set up the edges of trhe level to test collisions on 234 | var rightEdge = [ 235 | [level.right, level.top], 236 | [level.right, level.bottom] 237 | ]; 238 | 239 | var leftEdge = [ 240 | [level.left, level.top], 241 | [level.left, level.bottom] 242 | ]; 243 | 244 | var topEdge = [ 245 | [level.left, level.top], 246 | [level.right, level.top] 247 | ]; 248 | 249 | var bottomEdge = [ 250 | [level.left, level.bottom], 251 | [level.right, level.bottom] 252 | ]; 253 | 254 | //Test the left and right edges, setting as appropriate 255 | if ( collideTest.collideLine(rightEdge[0], rightEdge[1]) ) 256 | { 257 | position['x'] = level.right - collideTest.width; 258 | } 259 | else if ( collideTest.collideLine(leftEdge[0], leftEdge[1]) ) 260 | { 261 | position['x'] = level.left; 262 | } 263 | 264 | //Test the top and bottom edges, setting as appropriate 265 | if ( collideTest.collideLine( topEdge[0], topEdge[1]) ) 266 | { 267 | position['y'] = level.top; 268 | } 269 | else if ( collideTest.collideLine(bottomEdge[0], bottomEdge[1]) ) 270 | { 271 | position['y'] = (level.bottom - collideTest.height); 272 | } 273 | 274 | return position; 275 | } 276 | 277 | /** 278 | * Gets the next frame for the camera animation 279 | * 280 | * @param float destinationX The target destination X position 281 | * @param float destinationY Tha target destination Y position 282 | * @param int msDuration The amount of time that has passed since the 283 | * last frame 284 | * 285 | * @return object An object containing the new X and Y 286 | */ 287 | var _getNextAnimatedPosition = function( 288 | destinationX, destinationY, msDuration 289 | ){ 290 | //Make sure that the new destination is not outside the world 291 | var sanePosition = _getSanatisedPosition(destinationX, destinationY); 292 | 293 | var position = { 'x': sanePosition.x, 'y': sanePosition.y }; 294 | 295 | var targetX = position.x; 296 | var targetY = position.y; 297 | var deltaX = _viewport.x - targetX; 298 | var deltaY = _viewport.y - targetY; 299 | var velocityX = velocityY = MAX_VELOCITY; 300 | 301 | //If the delta Y is zero, then the velocity is zero as the camera is 302 | //not moving anywhere along the Y axis 303 | if ( 0 === deltaY ) 304 | { 305 | velocityY = 0; 306 | } 307 | 308 | //If the delta X is zero, then the velocity is zero as the camera is 309 | //not moving anywhere along the X axis 310 | if ( 0 === deltaX ) 311 | { 312 | velocityX = 0; 313 | } 314 | 315 | //Find out if the difference on the X or Y axis is bigger and slow 316 | //down the smaller of the two. This gives a nice diagonal effect so 317 | //that the camera doesn't look like it's panning around trying to find 318 | //the object 319 | if ( Math.abs(deltaX) > Math.abs(deltaY) ) 320 | { 321 | if ( 0 != deltaY ) 322 | { 323 | velocityY *= (deltaX / deltaY); 324 | } 325 | } 326 | else if ( Math.abs(deltaX) < Math.abs(deltaY) ) 327 | { 328 | if ( 0 != deltaX ) 329 | { 330 | velocityX *= (deltaX / deltaY); 331 | } 332 | } 333 | 334 | //A small delta X means that the camera needs to move to the right, and 335 | //a large delta means to move it to the left 336 | if ( deltaX < 0 ) 337 | { 338 | position.x = _viewport.x + ( velocityX * (msDuration / 1000) ); 339 | } 340 | else 341 | { 342 | position.x = _viewport.x - ( velocityX * (msDuration / 1000) ); 343 | } 344 | 345 | //A small delta X means that the camera needs to move down, and 346 | //a large delta means to move it up 347 | if ( deltaY < 0 ) 348 | { 349 | position.y = _viewport.y + ( velocityY * (msDuration / 1000) ); 350 | } 351 | else if ( deltaY < 0 ) 352 | { 353 | position.y = _viewport.y - ( velocityY * (msDuration / 1000) ); 354 | } 355 | 356 | 357 | //Check to see if the camera is close to the target. If it is, then move 358 | //it to the target so that on the next frame it doesn't overshoot 359 | if ( position.x <= (targetX + 5) && position.x >= (targetX - 5) ) 360 | { 361 | position.x = targetX; 362 | } 363 | 364 | if ( position.y <= (targetY + 5) && position.y >= (targetY - 5) ) 365 | { 366 | position.y = targetY; 367 | } 368 | 369 | //If the camera has reached it's target, then stop it animating 370 | if ( targetX == position.x && targetY == position.y ) 371 | { 372 | _animating = false; 373 | } 374 | 375 | return position; 376 | } 377 | 378 | /** 379 | * Updates all objects with their new position. This gives the illusion that 380 | * the camera has moved, when in reality it's all the objects that 381 | * have moved 382 | * 383 | * @param float distanceX The distance the camera has travelled on the X 384 | * @param float distanceY The distance the camera has travelled on the Y 385 | */ 386 | var _updateObjects = function( distanceX, distanceY ){ 387 | var objects = _world.getObjects(); 388 | for ( var i = 0; i < objects.length; i++) 389 | { 390 | objects[i].forEach(function(obj){ 391 | obj.rect.x -= distanceX; 392 | obj.rect.y -= distanceY; 393 | }); 394 | } 395 | } 396 | } -------------------------------------------------------------------------------- /js/lib/door.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a door which can be passed only when the state is set to true 3 | * 4 | * @author David North 5 | */ 6 | function door() 7 | { 8 | door.prototype.constructor.call(this); 9 | 10 | //Set up the variables required by the sprite inheritance 11 | this.image = gamejs.image.load('img/door.png'); 12 | this.image.crop( new gamejs.Rect( [0,0], [83,466] )); 13 | 14 | var _size = this.image.getSize(); 15 | this.rect = new gamejs.Rect([0, 0], [_size[0], _size[1]]); 16 | 17 | /** 18 | * Overrides the setState method of the parent so that the object changes 19 | * depending on whether it is on or off 20 | * 21 | * @param boolean state The state to apply 22 | * 23 | * @return door 24 | */ 25 | this.setState = function( state ){ 26 | //Only update if the state has actually changed 27 | if ( state != this.getState() ) 28 | { 29 | if ( state ) 30 | { 31 | this.image.crop( new gamejs.Rect( [83,0], [83,466] )); 32 | } 33 | else 34 | { 35 | this.image.crop( new gamejs.Rect( [0,0], [83,466] )); 36 | } 37 | } 38 | 39 | //Update the state using the parent setState method 40 | return door.prototype.setState.call(this, state); 41 | } 42 | 43 | /** 44 | * Handles the collision between a playable and this object 45 | * 46 | * @return door 47 | */ 48 | this.handleCollision = function( playable ){ 49 | 50 | if ( !this.getState() ) 51 | { 52 | //Modify the rectangle. The player shouldn't hit the door 53 | //until they are at the beam 54 | var targetX = this.rect.x + 26; 55 | var targetY = this.rect.y; 56 | 57 | var targetWidth = this.rect.width - 52; 58 | var targetHeight = this.rect.height; 59 | 60 | var rect = new gamejs.Rect( 61 | [targetX, targetY], [targetWidth, targetHeight] 62 | ); 63 | 64 | playerCollides(playable, rect); 65 | } 66 | 67 | return this; 68 | } 69 | } 70 | 71 | //Set the parent of the door to io 72 | include_once(['lib/io.js']); 73 | door.prototype = new io(); 74 | -------------------------------------------------------------------------------- /js/lib/gates/andGate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simulates an AND gate in JavaScript. As an io object this can be used to 3 | * chain together logic operators and objects 4 | * 5 | * @author David North 6 | */ 7 | function andGate() 8 | { 9 | andGate.prototype.constructor.call(this); 10 | 11 | /** 12 | * Overrides the setState method of the parent so that the state may only 13 | * be changed to true if all inputs are also set to true 14 | * 15 | * @param boolean state The state to attempt to change to 16 | * 17 | * @return andGate 18 | */ 19 | this.setState = function( state ){ 20 | state = true; 21 | 22 | //Keep the state at true unless a false value is found 23 | for( var i = 0; i < this.getInputs().length; i++ ) 24 | { 25 | if ( !(this.getInputs()[i].getState()) ) 26 | { 27 | state = false; 28 | break; 29 | } 30 | } 31 | 32 | //Update the state using the parent setState method 33 | return andGate.prototype.setState.call(this, state); 34 | } 35 | } 36 | 37 | //Set the parent of the andGate to io 38 | include_once(['lib/io.js']); 39 | andGate.prototype = new io(); 40 | -------------------------------------------------------------------------------- /js/lib/gates/notGate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simulates a NOT gate in JavaScript. As an io object this can be used to 3 | * chain together logic operators and objects 4 | * 5 | * @author David North 6 | */ 7 | function notGate() 8 | { 9 | notGate.prototype.constructor.call(this); 10 | 11 | /** 12 | * Overrides the addInput method of the parent so that only a single 13 | * input can be added to this object. This is because the NOT gate can only 14 | * operate by setting itself to a modified state of it's single input 15 | * 16 | * @param io input The input to add 17 | * 18 | * @return io 19 | */ 20 | this.addInput = function( input ){ 21 | notGate.prototype.addInput.call(this, input); 22 | 23 | if ( this.getInputs().length > 1 ) 24 | { 25 | throw 'You may only have one input assigned to a Not gate'; 26 | } 27 | 28 | return this; 29 | } 30 | 31 | /** 32 | * Overrides the setState method of the parent so that the state that this 33 | * object is set to is the opposite to that provided 34 | * 35 | * @param boolean state The state to apply 36 | * 37 | * @return notGate 38 | */ 39 | this.setState = function( state ){ 40 | return notGate.prototype.setState.call(this, !state); 41 | } 42 | } 43 | 44 | //Set the parent of the notGate to io 45 | include_once(['lib/io.js']); 46 | notGate.prototype = new io(); 47 | -------------------------------------------------------------------------------- /js/lib/gates/orGate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simulates an OR gate in JavaScript. As an io object this can be used to 3 | * chain together logic operators and objects 4 | * 5 | * @author David North 6 | */ 7 | function orGate() 8 | { 9 | orGate.prototype.constructor.call(this); 10 | 11 | /** 12 | * Overrides the setState method of the parent so that the state may only 13 | * be changed to false if all inputs are also set to false. If even a single 14 | * input is true then the state is true 15 | * 16 | * @param boolean state The state to attempt to change to 17 | * 18 | * @return orGate 19 | */ 20 | this.setState = function( state ){ 21 | state = false; 22 | 23 | //Keep the state at false unless a true value is found 24 | for( var i = 0; i < this.getInputs().length; i++ ) 25 | { 26 | if ( this.getInputs()[i].getState() ) 27 | { 28 | state = true; 29 | break; 30 | } 31 | } 32 | 33 | //Update the state using the parent setState method 34 | return orGate.prototype.setState.call(this, state); 35 | } 36 | } 37 | 38 | //Set the parent of the orGate to io 39 | include_once(['lib/io.js']); 40 | orGate.prototype = new io(); 41 | -------------------------------------------------------------------------------- /js/lib/goal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The end goal of the game. All of the goals present in the game need to be 3 | * active before the game is complete 4 | * 5 | * @author David North 6 | */ 7 | function goal() 8 | { 9 | //Load the variables required by gamejs.sprite.Sprite 10 | goal.superConstructor.apply(this, [0, 0]); 11 | this.image = gamejs.image.load('img/goal.png'); 12 | 13 | var _size = this.image.getSize(); 14 | this.rect = new gamejs.Rect([0, 0], [_size[0], _size[1]]); 15 | 16 | /** 17 | * @var boolean Whether the goal has been activated 18 | */ 19 | var _active = false; 20 | 21 | /** 22 | * Sets the position of the object 23 | * 24 | * @param float x The X co-ordinate 25 | * @param float y The Y co-ordinate 26 | * 27 | * @return goal 28 | */ 29 | this.setPosition = function(x, y){ 30 | this.rect.x = x; 31 | this.rect.y = y; 32 | 33 | return this; 34 | } 35 | 36 | /** 37 | * Returns whether or not the goal has been activated 38 | * 39 | * @return boolean 40 | */ 41 | this.isActive = function(){ 42 | return _active; 43 | } 44 | 45 | /** 46 | * Updates the object, ready for the next draw request 47 | * 48 | * @param msDuration 49 | * 50 | * @return goal 51 | */ 52 | this.update = function(msDuration){ 53 | //The default state for thisobject is deactivated, unless a 54 | //player has collided with it 55 | _active = false; 56 | 57 | return this; 58 | } 59 | 60 | /** 61 | * Handles the collision between a playable and this object 62 | * 63 | * @return goal 64 | */ 65 | this.handleCollision = function( playable ){ 66 | //Modify the rectangle. The player shouldn't end the level until 67 | //they are fully within the tube 68 | var targetX = this.rect.x + (this.rect.width / 2); 69 | targetX += (playable.rect.width / 2); 70 | 71 | var targetY = this.rect.y; 72 | var rect = new gamejs.Rect([targetX, targetY], [40, 144]); 73 | 74 | //Check if the player has collided with this goal, and whether it 75 | //should be activated 76 | _active = playable.rect.collideRect(rect); 77 | 78 | return this; 79 | } 80 | } 81 | 82 | //Extend the playable object so that the parent is the sprite 83 | gamejs.utils.objects.extend(goal, gamejs.sprite.Sprite); -------------------------------------------------------------------------------- /js/lib/io.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Basic input/output class. Listens for state changes on the inputs and 3 | * notifies outputs if a change happens 4 | * 5 | * @author David North 6 | */ 7 | function io() 8 | { 9 | /** 10 | * @var boolean 11 | */ 12 | this._state = false; 13 | 14 | /** 15 | * @var array An array of inputs, that determine the state of this object 16 | */ 17 | this._inputs = []; 18 | 19 | /** 20 | * @var array An array of outputs, to send the state of this object to 21 | */ 22 | this._outputs = []; 23 | 24 | //Load the variables required by gamejs.sprite.Sprite 25 | io.superConstructor.apply(this, [0, 0]); 26 | this.image = gamejs.image.load('img/blank.png'); 27 | this.rect = new gamejs.Rect([0,0]); 28 | 29 | /** 30 | * Sets the position of the object 31 | * 32 | * @param float x The X co-ordinate 33 | * @param float y The Y co-ordinate 34 | * 35 | * @return io 36 | */ 37 | this.setPosition = function(x, y){ 38 | this.rect.x = x; 39 | this.rect.y = y; 40 | return this; 41 | } 42 | 43 | /** 44 | * Private method fired when the state of the object has changed 45 | * (such as when a switch is pressed). Once the state has changed, 46 | * all other outputs need to be made aware of the change 47 | * 48 | * @return io 49 | */ 50 | var _stateChange = function(obj){ 51 | //Change the state of all outputs to the new state of this object, 52 | //effectively causing a knock-on affect down the chain 53 | for( var i = 0; i < obj.getOutputs().length; i++ ) 54 | { 55 | obj.getOutputs()[i].setState( obj.getState() ); 56 | } 57 | 58 | return this; 59 | }; 60 | 61 | /** 62 | * Sets the state of this object 63 | * 64 | * @param boolean state The state to change to 65 | * 66 | * @return io 67 | */ 68 | this.setState = function(state){ 69 | if ( typeof(state) != 'boolean' ) 70 | { 71 | throw 'State must be a boolean'; 72 | } 73 | 74 | //Set the new state and set the state change event only if the state 75 | //has actually changed, otherwise we could waste time notifying objects 76 | ///that don't require notification 77 | if ( state != this.getState() ) 78 | { 79 | this._state = state; 80 | _stateChange(this); 81 | } 82 | 83 | return this; 84 | }; 85 | 86 | /** 87 | * Gets the state of the object 88 | * 89 | * @return boolean 90 | */ 91 | this.getState = function(){ 92 | return this._state; 93 | }; 94 | 95 | /** 96 | * Gets the inputs assigned to this object 97 | * 98 | * @return array 99 | */ 100 | this.getInputs = function(){ 101 | return this._inputs; 102 | }; 103 | 104 | /** 105 | * Gets the outputs assigned to this object 106 | * 107 | * @return array 108 | */ 109 | this.getOutputs = function(){ 110 | return this._outputs; 111 | }; 112 | 113 | /** 114 | * Adds a new input to the object. Essentially this adds subscribes 115 | * this object to the stateChange event of the input 116 | * 117 | * @param io input 118 | * 119 | * @return io 120 | */ 121 | this.addInput = function( input ){ 122 | if ( !(input instanceof io) ) 123 | { 124 | throw 'Input must be of type \'io\''; 125 | } 126 | 127 | //Ensure that the input has not already been added. We don't want to 128 | //attempt to add it again, that's just damned inefficient 129 | if ( $.inArray(input, this._inputs) == -1 ) 130 | { 131 | this._inputs.push(input); 132 | input.addOutput(this); 133 | } 134 | 135 | return this; 136 | }; 137 | 138 | /** 139 | * Adds an output to this object. Effectively this subscribes 'output' 140 | * to the stateChange event of this object 141 | * 142 | * @param io output 143 | * 144 | * @return io 145 | */ 146 | this.addOutput = function( output ){ 147 | if ( !(output instanceof io) ) 148 | { 149 | throw 'Output must be of type \'io\''; 150 | } 151 | 152 | //Ensure that the output has not already been added. We don't want to 153 | //attempt to add it again, that would be silly 154 | if ( $.inArray(output, this._outputs) == -1 ) 155 | { 156 | this._outputs.push(output); 157 | output.addInput(this); 158 | _stateChange(this); 159 | } 160 | 161 | return this; 162 | }; 163 | } 164 | 165 | //Some IOs need to be drawn. To deal with this, extend the sprite 166 | gamejs.utils.objects.extend(io, gamejs.sprite.Sprite); 167 | -------------------------------------------------------------------------------- /js/lib/levels/level_1.js: -------------------------------------------------------------------------------- 1 | [ 2 | {"type":"stats","clonePar":1,"timePar":10}, 3 | {"type":"wall","x":0,"y":680,"repeat-x":2}, 4 | {"type":"player","x":150,"y": 550}, 5 | {"type":"lever","x":350,"y": 641,"outputs":[ 6 | {"type":"door", "x":700,"y":218} 7 | ]}, 8 | { 9 | "type":"tooltip","x":150,"y":641,"width":50,"height":50, 10 | "text":"Press the left arrow or a to move left
Press the right arrow or d to move right" 11 | }, 12 | { 13 | "type":"tooltip","x":350,"y":641,"width":50,"height":50, 14 | "text":"Press e or enter to activate switches
At least one clone must remain at a switch to keep it active" 15 | }, 16 | { 17 | "type":"tooltip","x":650,"y":641,"width":50,"height":50, 18 | "text":"Press c to create a clone
Press tab to switch between them" 19 | }, 20 | {"type":"goal","x":890,"y":540} 21 | ] -------------------------------------------------------------------------------- /js/lib/levels/level_2.js: -------------------------------------------------------------------------------- 1 | [ 2 | {"type":"stats","clonePar":2,"timePar":10}, 3 | {"type":"wall","x":0,"y":680,"repeat-x":2}, 4 | {"type":"player","x":150,"y": 550}, 5 | {"type":"andGate","inputs":[ 6 | {"type":"lever","x":250,"y": 641}, 7 | {"type":"lever","x":450,"y": 641} 8 | ], "outputs": [ 9 | {"type":"door", "x":700,"y":218} 10 | ]}, 11 | { 12 | "type":"tooltip","x":250,"y":641,"width":100,"height":100, 13 | "text":"Some doors can only be opened when two or more
switches are held down at the same time"}, 14 | { 15 | "type":"tooltip","x":650,"y":641,"width":50,"height":50, 16 | "text":"Press c to create a clone
Press tab to switch between them"}, 17 | {"type":"goal","x":890,"y":540} 18 | ] -------------------------------------------------------------------------------- /js/lib/levels/level_3.js: -------------------------------------------------------------------------------- 1 | [ 2 | {"type":"stats","clonePar":1,"timePar":15}, 3 | {"type":"wall","x":0,"y":680,"repeat-x":2}, 4 | {"type":"player","x":150,"y": 550}, 5 | {"type":"lever","x":350,"y": 641,"outputs":[ 6 | {"type":"door", "x":500,"y":218}, 7 | {"type":"notGate", "outputs":[ 8 | {"type":"door", "x":700,"y":218} 9 | ]} 10 | ]}, 11 | { 12 | "type":"tooltip","x":300,"y":641,"width":100,"height":100, 13 | "text":"Press e or enter to activate switches
Some switches will only activate a door when they are off"}, 14 | { 15 | "type":"tooltip","x":550,"y":641,"width":50,"height":50, 16 | "text":"Press c to create a clone
Press tab to switch between them"}, 17 | {"type":"goal","x":890,"y":540} 18 | ] -------------------------------------------------------------------------------- /js/lib/levels/level_4.js: -------------------------------------------------------------------------------- 1 | [ 2 | {"type":"stats","clonePar":2,"timePar":15}, 3 | {"type":"wall","x":0,"y":680,"repeat-x":2}, 4 | {"type":"player","x":150,"y": 550}, 5 | {"type":"lever","x":350,"y": 641,"outputs":[ 6 | {"type":"door", "x":500,"y":218}, 7 | {"type":"notGate", "outputs":[ 8 | {"type":"andGate", "outputs":[ 9 | {"type":"door", "x":700,"y":218} 10 | ],"inputs":[ 11 | {"type":"lever","x":600,"y": 641} 12 | ]} 13 | ]} 14 | ]}, 15 | { 16 | "type":"tooltip","x":300,"y":641,"width":100,"height":100, 17 | "text":"The right combination of switches will release certain doors.
Experiment to find the correct combination"}, 18 | { 19 | "type":"tooltip","x":550,"y":641,"width":50,"height":50, 20 | "text":"Press c to create a clone
Press tab to switch between them"}, 21 | {"type":"goal","x":890,"y":540} 22 | ] -------------------------------------------------------------------------------- /js/lib/levels/level_5.js: -------------------------------------------------------------------------------- 1 | [ 2 | {"type":"stats","clonePar":1,"timePar":20,"lastLevel":true}, 3 | {"type":"wall","x":0,"y":680,"repeat-x":2}, 4 | {"type":"player","x":50,"y": 550}, 5 | {"type":"platform", "x": 150, "y": 520,"width":250,"height":26}, 6 | {"type":"orGate","x":0,"y":0,"outputs":[ 7 | {"type":"door", "x":150,"y":68}, 8 | {"type":"door", "x":315,"y":68} 9 | ],"inputs":[ 10 | {"type":"lever","x":450,"y": 641}, 11 | {"type":"lever","x":800,"y": 641,"outputs":[ 12 | {"type":"door", "x":900,"y":218}, 13 | {"type":"orGate","x":0,"y":0,"outputs":[ 14 | {"type":"door", "x":600,"y":218} 15 | ],"inputs":[ 16 | {"type":"lever","x":235,"y": 492} 17 | ]} 18 | ]} 19 | ]}, 20 | {"type":"goal","x":1090,"y":540} 21 | ] -------------------------------------------------------------------------------- /js/lib/lever.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a lever. Levers can be pulled and turned on or off, but must be 3 | * held down or they will revert to their off state 4 | * 5 | * @author David North 6 | */ 7 | function lever() 8 | { 9 | lever.prototype.constructor.call(this); 10 | 11 | //Fulfil the requirements of the gamejs.sprite.Sprite object 12 | this.image = gamejs.image.load('img/switch.png'); 13 | this.image.crop( new gamejs.Rect( [0,0], [83,43] )); 14 | 15 | var _size = this.image.getSize(); 16 | 17 | /** 18 | * @var boolean Whether or the lever is being held down 19 | */ 20 | var _heldDown = false; 21 | 22 | this.rect = new gamejs.Rect([0, 0], [_size[0], _size[1]]); 23 | 24 | /** 25 | * Overrides the setState method of the parent so that the object changes 26 | * depending on whether it is on or off 27 | * 28 | * @param boolean state The state to apply 29 | * 30 | * @return lever 31 | */ 32 | this.setState = function( state ){ 33 | if ( state != this.getState() ) 34 | { 35 | if ( state ) 36 | { 37 | this.image.crop( new gamejs.Rect( [83,0], [83,43] )); 38 | } 39 | else 40 | { 41 | this.image.crop( new gamejs.Rect( [0,0], [83,43] )); 42 | } 43 | } 44 | 45 | //Update the state using the parent setState method 46 | lever.prototype.setState.call(this, state); 47 | } 48 | 49 | /** 50 | * Updates the object, ready for the next draw request 51 | * 52 | * @param msDuration 53 | * 54 | * @return lever 55 | */ 56 | this.update = function( msDuration ){ 57 | //If the lever isn't held down then turn it off 58 | if ( !_heldDown ) 59 | { 60 | this.setState(false); 61 | } 62 | 63 | //the default state is to be not held down, unless otherwise told 64 | _heldDown = false; 65 | 66 | return this; 67 | } 68 | 69 | /** 70 | * Handles the collision between a playable and this object 71 | * 72 | * @param playable playable The playable that collision has happened on 73 | * 74 | * @return lever 75 | */ 76 | this.handleCollision = function( playable ){ 77 | 78 | if ( _hasCollided(this, playable ) ) 79 | { 80 | //If the player is colliding with this lever then it is held down 81 | _heldDown = true; 82 | } 83 | 84 | return this; 85 | } 86 | 87 | /** 88 | * Handles player input. If the player activates the switch then actions 89 | * may need to be taken 90 | * 91 | * @param world world The world this event came from 92 | * @param gamejs.Event event The event that fired 93 | */ 94 | this.handleInput = function(world, event){ 95 | var playable = world.getPlayer().getCurrentPlayable(); 96 | 97 | //Only check the event if a key has been pushed and the player is 98 | //colliding with the lever 99 | if ( event.type === gamejs.event.KEY_DOWN 100 | && _hasCollided(this, playable) ) 101 | { 102 | //If the key was 'enter' or 'e' then set the new state of the lever 103 | switch( event.key ) 104 | { 105 | case gamejs.event.K_e: 106 | case gamejs.event.K_ENTER: 107 | this.setState( !this.getState() ); 108 | break; 109 | } 110 | } 111 | } 112 | 113 | var _hasCollided = function ( self, playable ) 114 | { 115 | var _collideRect = new gamejs.Rect( 116 | [self.rect.x + 34, self.rect.y], 117 | [32 , _size[1]] 118 | ); 119 | 120 | return _collideRect.collideRect(playable.rect); 121 | } 122 | } 123 | 124 | //Set the parent of the lever to io 125 | include_once(['lib/io.js']); 126 | lever.prototype = new io(); 127 | -------------------------------------------------------------------------------- /js/lib/menu.js: -------------------------------------------------------------------------------- 1 | 2 | include_once(['lib/menuItem.js']) 3 | function menu() 4 | { 5 | var _items = []; 6 | var _currentActive = 0; 7 | var _x = 0; 8 | var _y = 0; 9 | 10 | this.setPosition = function(x, y) 11 | { 12 | if ( typeof(x) != 'number' ) 13 | { 14 | throw 'X position must be a number'; 15 | } 16 | 17 | if ( typeof(y) != 'number' ) 18 | { 19 | throw 'Y position must be a number'; 20 | } 21 | 22 | _x = x; 23 | _y = y; 24 | return this; 25 | } 26 | 27 | this.addItem = function( mItem ){ 28 | if ( !(mItem instanceof menuItem) ) 29 | { 30 | throw 'Argument must be of type menuItem'; 31 | } 32 | 33 | if ( _items.length === 0 ) 34 | { 35 | mItem.setActive(true); 36 | } 37 | 38 | _items.push(mItem); 39 | return this; 40 | } 41 | 42 | this.activate = function(index){ 43 | for( var i = 0; i < _items; i++ ) 44 | { 45 | if ( i != index ) 46 | { 47 | _items[i].setActive(false); 48 | } 49 | else 50 | { 51 | _items[i].setActive(true); 52 | } 53 | } 54 | } 55 | 56 | this.activateNext = function(){ 57 | _currentActive++; 58 | 59 | if ( _currentActive >= _items.length ) 60 | { 61 | _currentActive = 0; 62 | } 63 | 64 | return this.activate(_currentActive); 65 | } 66 | 67 | this.draw = function( surface ){ 68 | var dest = new gamejs.Rect([_x, _y]); 69 | 70 | for( var i = 0; i < _items.length; i++ ) 71 | { 72 | surface.blit( _items[i].getCanvas(), dest ); 73 | dest.y += 30; 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /js/lib/menuItem.js: -------------------------------------------------------------------------------- 1 | 2 | function menuItem( image ) 3 | { 4 | if ( typeof(image) != 'string' ) 5 | { 6 | throw 'Constructor argument must be text'; 7 | } 8 | 9 | var _image = gamejs.image.load(image); 10 | var _size = _image.getSize(); 11 | var _active = false; 12 | var _callback = null; 13 | 14 | this.setActive = function( active ){ 15 | if ( typeof(active) != 'boolean' ) 16 | { 17 | throw 'Active flag must be boolean'; 18 | } 19 | 20 | var rect = new gamejs.Rect([0,0],[parseInt(_size[0] / 2), _size[1]]); 21 | 22 | if ( active ) 23 | { 24 | rect.x = parseInt(_size[0] / 2); 25 | } 26 | 27 | _image.crop(rect); 28 | _active = active; 29 | 30 | return this; 31 | } 32 | 33 | this.addCallback = function( callback ){ 34 | if ( typeof(callback) !== 'function' ) 35 | { 36 | throw 'Callback must be a function'; 37 | } 38 | 39 | _callback = callback; 40 | return this; 41 | } 42 | 43 | this.setText = function( text ){ 44 | if ( typeof(text) != 'string' ) 45 | { 46 | throw 'Text argument must be text'; 47 | } 48 | 49 | _text = text; 50 | return this; 51 | } 52 | 53 | this.getCanvas = function(){ 54 | return _image; 55 | } 56 | } -------------------------------------------------------------------------------- /js/lib/platform.js: -------------------------------------------------------------------------------- 1 | function platform() 2 | { 3 | //Load the variables required by gamejs.sprite.Sprite 4 | platform.superConstructor.apply(this, [0, 0]); 5 | this.image = new gamejs.Surface([0,0]); 6 | 7 | var _size = this.image.getSize(); 8 | this.rect = new gamejs.Rect([0, 0]); 9 | 10 | var _leftImg = gamejs.image.load('img/platform-left.png'); 11 | var _rightImg = gamejs.image.load('img/platform-right.png'); 12 | var _midImg = gamejs.image.load('img/platform-middle.png'); 13 | 14 | this.setDimensions = function(width, height){ 15 | this.rect.width = width; 16 | this.rect.height = height; 17 | } 18 | 19 | this.setPosition = function(x, y){ 20 | this.rect.x = x; 21 | this.rect.y = y; 22 | } 23 | 24 | this.draw = function( surface ){ 25 | var rightSize = _rightImg.getSize(); 26 | var leftSize = _leftImg.getSize(); 27 | 28 | var rightSide = new gamejs.Rect( 29 | [(this.rect.right - rightSize[0]), this.rect.top], 30 | [rightSize[0], this.rect.height] 31 | ); 32 | 33 | var leftSide = new gamejs.Rect( 34 | [this.rect.left, this.rect.top], 35 | [leftSize[0], this.rect.height] 36 | ); 37 | 38 | var middle = new gamejs.Rect( 39 | [(this.rect.left + leftSize[0]) , this.rect.top], 40 | [(rightSide.left - leftSide.right), this.rect.height] 41 | ); 42 | 43 | surface.blit(_midImg, middle); 44 | surface.blit(_leftImg, leftSide); 45 | surface.blit(_rightImg, rightSide); 46 | } 47 | 48 | this.handleCollision = function(playable){ 49 | var collider = new gamejs.Rect( 50 | [this.rect.x, this.rect.y + (this.rect.height / 2) ], 51 | [this.rect.width, this.rect.height / 2] 52 | ); 53 | 54 | playerCollides(playable, collider); 55 | 56 | return this; 57 | } 58 | } 59 | 60 | //Extend the playable object so that the parent is the sprite 61 | gamejs.utils.objects.extend(platform, gamejs.sprite.Sprite); -------------------------------------------------------------------------------- /js/lib/playable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a playable sprite. This is manipulated by the player object which 3 | * determines which playable should be modified 4 | * 5 | * @author David North 6 | */ 7 | function playable() 8 | { 9 | //Load the variables required by gamejs.sprite.Sprite 10 | playable.superConstructor.apply(this, [0, 0]); 11 | this.image = gamejs.image.load('img/player.png'); 12 | 13 | this.rect = new gamejs.Rect([0, 0]); 14 | 15 | /** 16 | * @var int The amount of health the playable has 17 | */ 18 | var _health = 100; 19 | 20 | /** 21 | * @var float The velocity left to right that the playable is experiencing 22 | */ 23 | var _velocityX = 0.0; 24 | 25 | /** 26 | * @var float The velocity bottom to top that this playable is experiencing 27 | */ 28 | var _velocityY = 0.0; 29 | 30 | /** 31 | * @var string How the player is currently moving (walk, jump, fall etc.) 32 | */ 33 | var _moveType = ''; 34 | 35 | /** 36 | * Sets how the player is currently moving, and generates the correct 37 | * sprite image 38 | * 39 | * @param string type The type of movement 40 | * 41 | * @return playable 42 | */ 43 | this.setMovement = function( type ){ 44 | //Only change the type if it has actually changed. No need to do any 45 | //processing otherwise 46 | if ( type != _moveType ) 47 | { 48 | //Get the old size so that we can modify the X and Y co-ordinates 49 | //accordingly if the sprite changes height or width 50 | var oldSize = this.image.getSize(); 51 | 52 | //Set the correct sprite image depending on the type of movement 53 | switch(type) 54 | { 55 | case 'walk': 56 | this.image.crop( new gamejs.Rect([0,0], [46,55] )); 57 | break; 58 | case 'jump': 59 | this.image.crop( new gamejs.Rect([0,55], [46,69] )); 60 | break; 61 | case 'fall': 62 | this.image.crop( new gamejs.Rect([46,55], [46,69] )); 63 | break; 64 | } 65 | 66 | //Get and set the new sizes 67 | var _size = this.image.getSize(); 68 | this.rect.width = _size[0]; 69 | this.rect.height = _size[1]; 70 | 71 | //Set the new X and Y co-ordinates 72 | this.rect.x += oldSize[0] - _size[0]; 73 | this.rect.y += oldSize[1] - _size[1]; 74 | 75 | _moveType = type; 76 | } 77 | 78 | return this; 79 | } 80 | 81 | /** 82 | * Returns the current movement type of the playable 83 | * 84 | * @return string 85 | */ 86 | this.getMovement = function(){ 87 | return _moveType; 88 | } 89 | 90 | /** 91 | * Sets the position of the object 92 | * 93 | * @param float x The X co-ordinate 94 | * @param float y The Y co-ordinate 95 | * 96 | * @return playable 97 | */ 98 | this.setPosition = function(x, y){ 99 | this.setX(x); 100 | this.setY(y); 101 | 102 | return this; 103 | } 104 | 105 | /** 106 | * Sets the X value of the playable (useful when initiating a new playable, 107 | * teleporting, etc) 108 | * 109 | * @param float x 110 | * 111 | * @return playable 112 | */ 113 | this.setX = function( x ){ 114 | if ( typeof(x) != 'number') 115 | { 116 | throw 'Value for X must be a number'; 117 | } 118 | 119 | this.rect.x = x; 120 | return this; 121 | } 122 | 123 | /** 124 | * Gets the current X position 125 | * 126 | * @return float 127 | */ 128 | this.getX = function(){ 129 | return this.rect.x; 130 | } 131 | 132 | /** 133 | * Sets the Y value of the playable (useful when initiating a new playable, 134 | * teleporting, etc) 135 | * 136 | * @param float y 137 | * 138 | * @return playable 139 | */ 140 | this.setY = function( y ){ 141 | if ( typeof(y) != 'number') 142 | { 143 | throw 'Value for Y must be a number'; 144 | } 145 | 146 | this.rect.y = y; 147 | return this; 148 | } 149 | 150 | /** 151 | * Gets the current Y position 152 | * 153 | * @return float 154 | */ 155 | this.getY = function(){ 156 | return this.rect.y; 157 | } 158 | 159 | /** 160 | * Sets the velocity of the playable so that the speed in one way or 161 | * another can be set 162 | * 163 | * @param float x The velocity left to right 164 | * @param float y The velocity bottom to top 165 | * 166 | * @return playable 167 | */ 168 | this.setVelocity = function( x, y ){ 169 | if ( typeof(x) != 'number') 170 | { 171 | throw 'Value for X must be a number'; 172 | } 173 | 174 | if ( typeof(y) != 'number') 175 | { 176 | throw 'Value for Y must be a number'; 177 | } 178 | 179 | _velocityX = x; 180 | _velocityY = y; 181 | return this; 182 | } 183 | 184 | /** 185 | * Gets the X and Y velocity for this playable object by returning an object 186 | * with an x and y parameter 187 | * 188 | * @return object 189 | */ 190 | this.getVelocity = function(){ 191 | return { 'x':_velocityX, 'y':_velocityY }; 192 | } 193 | 194 | /** 195 | * Updates the object, ready for the next draw request 196 | * 197 | * @param msDuration 198 | * 199 | * @return playable 200 | */ 201 | this.update = function( msDuration ){ 202 | //Set the default movement to wealking, if it hasn't already been set 203 | if ( this.getMovement() == '' ) 204 | { 205 | this.setMovement('walk'); 206 | } 207 | 208 | //If the player is falling then set that sprite up. Other movement 209 | //types are dealt with outside this object 210 | if ( this.getVelocity().y > 0 ) 211 | { 212 | this.setMovement('fall'); 213 | } 214 | 215 | //Calculate the distance the playable has moved since the last frame 216 | var distanceX = (this.getVelocity().x * (msDuration/1000)); 217 | var distanceY = (this.getVelocity().y * (msDuration/1000)); 218 | 219 | //Move the playable the calculated distance 220 | this.rect.moveIp( distanceX, distanceY ); 221 | 222 | return this; 223 | } 224 | } 225 | 226 | //Extend the playable object so that the parent is the sprite 227 | gamejs.utils.objects.extend(playable, gamejs.sprite.Sprite); 228 | -------------------------------------------------------------------------------- /js/lib/player.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a player. Players have control over multiple playables, but only 3 | * one playable at a time. 4 | * 5 | * @author David North 6 | */ 7 | include_once(['lib/playable.js']); 8 | function player() 9 | { 10 | //The maximum X velocity a player can trvel (heading right) 11 | const MAX_X_VELOCITY = 200; 12 | 13 | //The minimum X velocity a player can trvel (heading left) 14 | const MIN_X_VELOCITY = -200; 15 | 16 | //The minimum Y velocity a player can trvel (heading up) 17 | const MIN_Y_VELOCITY = -350; 18 | 19 | /** 20 | * @var array An array of playables at the players disposal 21 | */ 22 | var _playables = new gamejs.sprite.Group(); 23 | 24 | /** 25 | * @var float How fast the player is currently walking (minus 26 | * figure represents left, positive right) 27 | */ 28 | var _walkVelocity = 0; 29 | 30 | /** 31 | * @var int The number of clones created 32 | */ 33 | var _numClones = 0; 34 | 35 | //Start the player with a single playable 36 | _playables.add( new playable() ); 37 | 38 | /** 39 | * @var int The current index of the playable currently under the 40 | * players control 41 | */ 42 | var _currentIndex = 0; 43 | 44 | var _selectedImg = gamejs.image.load('img/selected.png'); 45 | 46 | /** 47 | * Handles player input. 48 | * 49 | * @param world world The world this event came from 50 | * @param gamejs.Event event The event that fired 51 | */ 52 | this.handleInput = function(event){ 53 | //If a key has been pressed then check it to see if an 54 | //action needs taking place 55 | if ( event.type === gamejs.event.KEY_DOWN ) 56 | { 57 | switch( event.key ) 58 | { 59 | //The space, w, or up key denotes a jump. The player is not allowed 60 | //to jump if they are already falling or jumping 61 | case gamejs.event.K_SPACE: 62 | case gamejs.event.K_w: 63 | case gamejs.event.K_UP: 64 | if ( this.getCurrentPlayable().getMovement() == 'walk' ) 65 | { 66 | this.setVelocity( this.getVelocity().x, MIN_Y_VELOCITY ); 67 | this.getCurrentPlayable().setMovement('jump'); 68 | } 69 | break; 70 | 71 | //The A key, or left arrow starts to move the player left 72 | case gamejs.event.K_a: 73 | case gamejs.event.K_LEFT: 74 | _walkVelocity = MIN_X_VELOCITY; 75 | break; 76 | 77 | //The D key, or right arrow starts to move the player right 78 | case gamejs.event.K_d: 79 | case gamejs.event.K_RIGHT: 80 | _walkVelocity = MAX_X_VELOCITY; 81 | break; 82 | 83 | //The C key clones a playable, so that the player can use 84 | //that instead 85 | case gamejs.event.K_c: 86 | if ( _numClones < 100 ) 87 | { 88 | this.clone(); 89 | _numClones++; 90 | } 91 | break; 92 | 93 | //The Tab key switches between the playables that the 94 | //player can control 95 | case gamejs.event.K_TAB: 96 | _walkVelocity = 0; 97 | this.moveToNext(); 98 | } 99 | } 100 | else if ( event.type === gamejs.event.KEY_UP ) 101 | { 102 | switch( event.key ) 103 | { 104 | //Letting go of direction events will stop the player dead 105 | case gamejs.event.K_a: 106 | case gamejs.event.K_LEFT: 107 | case gamejs.event.K_d: 108 | case gamejs.event.K_RIGHT: 109 | _walkVelocity = 0; 110 | break; 111 | } 112 | } 113 | } 114 | 115 | /** 116 | * Returns the number of clonmes that the player has created 117 | * 118 | * @return int 119 | */ 120 | this.getNumClones = function(){ 121 | return _numClones; 122 | } 123 | 124 | /** 125 | * Sets the velocity of the current playable 126 | * 127 | * @param float x The X velocity 128 | * @param float y The Y velocity 129 | * 130 | * @return player 131 | */ 132 | this.setVelocity = function( x, y ){ 133 | var playable = this.getCurrentPlayable(); 134 | playable.setVelocity( x, y ); 135 | 136 | return this; 137 | } 138 | 139 | /** 140 | * Returns an object taht contains the X and Y velocity of the current 141 | * playable 142 | * 143 | * @return object 144 | */ 145 | this.getVelocity = function(){ 146 | return this.getCurrentPlayable().getVelocity(); 147 | } 148 | 149 | /** 150 | * Updates all of the playable objects 151 | * 152 | * @param int msDuration 153 | * 154 | * @return player 155 | */ 156 | this.update = function( msDuration ){ 157 | if ( _walkVelocity ) 158 | { 159 | this.setVelocity( _walkVelocity, this.getVelocity().y ); 160 | } 161 | 162 | _playables.update( msDuration ); 163 | return this; 164 | } 165 | 166 | /** 167 | * Draws all of the playable objects to the surface 168 | * 169 | * @param object mainSurface 170 | * 171 | * @return player 172 | */ 173 | this.draw = function( mainSurface ){ 174 | _playables.draw( mainSurface ); 175 | 176 | var x = this.getCurrentPlayable().getX(); 177 | var y = this.getCurrentPlayable().getY(); 178 | 179 | x += (this.getCurrentPlayable().rect.width / 2); 180 | x -= (_selectedImg.getSize()[0] / 2); 181 | 182 | y -= (_selectedImg.getSize()[1] + 2); 183 | 184 | var rect = new gamejs.Rect([x, y]) 185 | mainSurface.blit(_selectedImg, rect); 186 | return this; 187 | } 188 | 189 | /** 190 | * Moves to the next available playable for control. If the last playable 191 | * is selected then the first element is sed instead 192 | * 193 | * @return player 194 | */ 195 | this.moveToNext = function(){ 196 | if ( (_currentIndex + 1) == this.getPlayables().sprites().length ) 197 | { 198 | _currentIndex = 0; 199 | } 200 | else 201 | { 202 | _currentIndex++ 203 | } 204 | 205 | return this; 206 | } 207 | 208 | /** 209 | * Gets all of the playables available to the player 210 | * 211 | * @return array 212 | */ 213 | this.getPlayables = function(){ 214 | return _playables; 215 | } 216 | 217 | /** 218 | * Gets the current playable object that the player is in control of 219 | * 220 | * @return playable 221 | */ 222 | this.getCurrentPlayable = function(){ 223 | return this.getPlayables().sprites()[_currentIndex]; 224 | } 225 | 226 | /** 227 | * Clones the current playable and adds it to the playables at the players 228 | * disposal 229 | * 230 | * @return player 231 | */ 232 | this.clone = function(){ 233 | //Get the template for the clone, and create a clone to the left 234 | //of the current playable 235 | var template = this.getCurrentPlayable(); 236 | var clone = new playable(); 237 | var newX = ( template.getX() - (template.rect.width * 2) ); 238 | 239 | clone.setPosition(newX, template.getY() - template.image.getSize()[1]); 240 | 241 | _playables.add( clone ); 242 | 243 | return this; 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /js/lib/scorecard.js: -------------------------------------------------------------------------------- 1 | 2 | function scorecard() 3 | { 4 | var _clonePar = 0; 5 | 6 | var _timePar = 0; 7 | 8 | var _clones = 0; 9 | 10 | var _timeTaken = 0; 11 | 12 | var _lastLevel = false; 13 | 14 | if ( !($('#game_scorecard').length) ) 15 | { 16 | $('#gameWindow').append('
'); 17 | $('#game_scorecard_bg').append('
'); 18 | $('#game_scorecard').append('

Results

'); 19 | $('#game_scorecard').append('

Number of Clones0

'); 20 | $('#game_scorecard').append('

Time0s

'); 21 | $('#game_scorecard').append('

Score

'); 22 | $('#game_scorecard').append('Main Menu'); 23 | $('#game_scorecard').append('Reset Level'); 24 | $('#game_scorecard').append('Next Level'); 25 | } 26 | 27 | this.setParForClones = function( par ){ 28 | _clonePar = par; 29 | return this; 30 | } 31 | 32 | this.setParForTime = function( par ){ 33 | _timePar = par; 34 | return this; 35 | } 36 | 37 | this.setTimeTaken = function( time ){ 38 | _timeTaken = time; 39 | return this; 40 | } 41 | 42 | this.setClonesUsed = function( clones ){ 43 | _clones = clones; 44 | return this; 45 | } 46 | 47 | this.setLastLevel = function(last){ 48 | _lastLevel = last; 49 | return this; 50 | } 51 | 52 | this.show = function(){ 53 | $('#game_scorecard .clones').text(_clones); 54 | $('#game_scorecard .time').text(_calcTime()); 55 | 56 | var score = 0; 57 | 58 | if ( _timeTaken <= (_timePar * 2) ) 59 | { 60 | score++; 61 | } 62 | 63 | if ( _clones <= _clonePar ) 64 | { 65 | score++; 66 | } 67 | 68 | if ( _timeTaken <= _timePar ) 69 | { 70 | score++; 71 | } 72 | 73 | $('#game_scorecard .score .star').remove(); 74 | 75 | for ( var i = 1; i <= 3; i++ ) 76 | { 77 | var star = $('
'); 78 | if ( score >= i ) 79 | { 80 | star.addClass('enabled'); 81 | } 82 | 83 | $('#game_scorecard .score').append(star); 84 | } 85 | 86 | if ( _lastLevel ) 87 | { 88 | $('.nextLevel').on('click', function( event ){ 89 | $('#game_scorecard_bg').hide(); 90 | $('#gameEnd').fadeIn(); 91 | 92 | $('body').on('keydown',function(event2){ 93 | switch ( event2.keyCode ) 94 | { 95 | //Enter and e will all move to the next level 96 | case 13: 97 | case 69: 98 | event2.preventDefault(); 99 | event2.stopImmediatePropagation(); 100 | 101 | $('#game_scorecard .mainMenu').click(); 102 | $('#gameEnd').fadeOut(); 103 | $(this).off(); 104 | } 105 | 106 | return false; 107 | }); 108 | 109 | $(this).off(); 110 | event.preventDefault(); 111 | event.stopImmediatePropagation(); 112 | }); 113 | } 114 | 115 | $('#game_scorecard_bg').fadeIn(); 116 | } 117 | 118 | this.hide = function(){ 119 | $('#game_scorecard_bg').fadeOut(); 120 | } 121 | 122 | var _calcTime = function(){ 123 | var seconds = parseInt(_timeTaken); 124 | var minutes = 0; 125 | var hours = 0; 126 | var days = 0; 127 | var ret = ''; 128 | 129 | if ( seconds > 60 ) 130 | { 131 | minutes = Math.floor( seconds / 60 ); 132 | seconds = (seconds % 60); 133 | 134 | if ( minutes >= 60 ) 135 | { 136 | hours = Math.floor( minutes / 60 ); 137 | minutes = (minutes % 60); 138 | 139 | if ( hours >= 24 ) 140 | { 141 | days = Math.floor( hours / 24 ); 142 | hours = (hours % 24); 143 | } 144 | } 145 | } 146 | 147 | if ( days ) 148 | { 149 | ret = days+'d '+hours+'h'; 150 | } 151 | else if ( hours ) 152 | { 153 | ret = hours+'h '+minutes+'m'; 154 | } 155 | else if ( minutes ) 156 | { 157 | ret = minutes+'m '+seconds+'s'; 158 | } 159 | else 160 | { 161 | ret = seconds+'s'; 162 | } 163 | 164 | return ret; 165 | } 166 | 167 | } -------------------------------------------------------------------------------- /js/lib/startMenu.js: -------------------------------------------------------------------------------- 1 | 2 | include_once(['lib/world.js', 'lib/menu.js','lib/menuItem.js']) 3 | function startMenu() 4 | { 5 | var _menu = new menu(); 6 | _menu.setPosition(260, 500) 7 | _menu.addItem( new menuItem('img/new-game.png') ); 8 | 9 | var _bg = gamejs.image.load('img/splash-screen.png'); 10 | var _world = null; 11 | 12 | var _setupLevel = function( mainSurface ){ 13 | //Initiate the world amd set the level to a reset 14 | _world = new world(); 15 | var lvlNum = 0; 16 | var self = this; 17 | 18 | var nextLevel = function(event){ 19 | if ( typeof(event) !== 'undefined' ) 20 | { 21 | event.preventDefault(); 22 | } 23 | 24 | if ( $('#game_scorecard .nextLevel').hasClass('disabled') ) 25 | { 26 | return false; 27 | } 28 | 29 | lvlNum++; 30 | 31 | resetLevel(); 32 | return false; 33 | }; 34 | 35 | var resetLevel = function(event){ 36 | if ( typeof(event) !== 'undefined' ) 37 | { 38 | event.preventDefault(); 39 | } 40 | 41 | $('#game_scorecard_bg').hide(); 42 | 43 | _world.init(lvlNum, mainSurface); 44 | return false; 45 | } 46 | 47 | var backToMenu = function(event){ 48 | if ( typeof(event) !== 'undefined' ) 49 | { 50 | event.preventDefault(); 51 | } 52 | 53 | _world = null; 54 | $('#game_scorecard_bg').remove(); 55 | return false; 56 | } 57 | 58 | $('.nextLevel').die(); 59 | $('.resetLevel').die(); 60 | $('.mainMenu').die(); 61 | 62 | $('.nextLevel').live('click', nextLevel); 63 | $('.resetLevel').live('click', resetLevel); 64 | $('.mainMenu').live('click', backToMenu); 65 | 66 | //Initialise the first level 67 | nextLevel(); 68 | } 69 | 70 | this.handleInput = function( mainSurface ){ 71 | if ( _world === null ) 72 | { 73 | gamejs.event.get().forEach(function(event){ 74 | if ( event.type === gamejs.event.KEY_DOWN ) 75 | { 76 | if ( event.key == gamejs.event.K_ENTER ) 77 | { 78 | _setupLevel( mainSurface ); 79 | } 80 | } 81 | }); 82 | } 83 | else 84 | { 85 | _world.handleInput(); 86 | } 87 | } 88 | 89 | this.update = function( msDuration ){ 90 | if ( _world !== null ) 91 | { 92 | _world.update( msDuration ); 93 | } 94 | } 95 | 96 | this.draw = function( surface ){ 97 | if ( _world === null ) 98 | { 99 | surface.blit(_bg); 100 | _menu.draw( surface ); 101 | } 102 | else 103 | { 104 | _world.draw( surface ); 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /js/lib/tooltip.js: -------------------------------------------------------------------------------- 1 | 2 | function tooltip() 3 | { 4 | //Load the variables required by gamejs.sprite.Sprite 5 | tooltip.superConstructor.apply(this, [0, 0]); 6 | this.image = gamejs.image.load('img/blank.png'); 7 | this.rect = new gamejs.Rect([0, 0]); 8 | 9 | if ( !($('#game_tooltip').length) ) 10 | { 11 | $('#gameWindow').append('
'); 12 | } 13 | 14 | var _text = ''; 15 | 16 | this.setDimensions = function(width, height){ 17 | this.rect.width = width; 18 | this.rect.height = height; 19 | } 20 | 21 | this.setPosition = function(x, y){ 22 | this.rect.x = x; 23 | this.rect.y = y; 24 | } 25 | 26 | this.setText = function( text ){ 27 | _text = text; 28 | } 29 | 30 | this.show = function(){ 31 | $('#game_tooltip').html(_text); 32 | $('#game_tooltip').fadeIn(); 33 | 34 | if ( null != tooltip.timer ) 35 | { 36 | clearInterval(tooltip.timer); 37 | tooltip.timer = null; 38 | } 39 | 40 | tooltip.timer = setTimeout(this.hide, 5000) 41 | } 42 | 43 | this.hide = function(animate){ 44 | if ( typeof(animate) == 'undefined' ) 45 | { 46 | animate = true; 47 | } 48 | 49 | if ( animate ) 50 | { 51 | $('#game_tooltip').fadeOut(); 52 | } 53 | else 54 | { 55 | $('#game_tooltip').hide(); 56 | } 57 | 58 | 59 | tooltip.timer = null; 60 | } 61 | 62 | this.handleCollision = function( playable, isCurrentPlayer){ 63 | if ( isCurrentPlayer ) 64 | { 65 | this.show(); 66 | } 67 | } 68 | 69 | this.draw = function(){ 70 | return null; 71 | } 72 | } 73 | 74 | //Set a static variable, so that we can keep track of when to hide tooltips 75 | tooltip.timer = null; 76 | 77 | //Extend the playable object so that the parent is the sprite 78 | gamejs.utils.objects.extend(tooltip, gamejs.sprite.Sprite); -------------------------------------------------------------------------------- /js/lib/utils.js: -------------------------------------------------------------------------------- 1 | function include_once(includes) 2 | { 3 | if ( typeof($('head').data('included')) == 'undefined' ) 4 | { 5 | $('head').data('included', []); 6 | } 7 | 8 | for ( var i = 0; i < includes.length; i++ ) 9 | { 10 | if ( $.inArray(includes[i], $('head').data('included')) === -1 ) 11 | { 12 | $('head').append( 13 | '' 14 | ); 15 | 16 | $('head').data('included').push(includes[i]); 17 | } 18 | } 19 | } 20 | 21 | function playerCollides(playable, rect, drag) 22 | { 23 | if ( typeof(drag) == 'undefined' ) 24 | { 25 | drag = 50; 26 | } 27 | 28 | //Define the top edge (left to right, along the top of the 29 | //colliding block) 30 | var topEdge = [ [rect.left, rect.top], [rect.right, rect.top] ]; 31 | 32 | //Define the top edge (left to right, along the bottom of the 33 | //colliding this) 34 | var bottomEdge = [ [rect.left, rect.bottom], [rect.right, rect.bottom] ]; 35 | 36 | //Define the left edge (top to bottom, along the left of the 37 | //colliding this) 38 | var leftEdge = [ [rect.left, rect.top], [rect.left, rect.bottom] ]; 39 | 40 | //Define the right edge (top to bottom, along the right of the 41 | //colliding this) 42 | var rightEdge = [ [rect.right, rect.top], [rect.right, rect.bottom] ]; 43 | 44 | //Check the top and bottom colliision points. If a collision is 45 | //detected then set the velocity on the Y axis to zero and move 46 | //the playable so that it is no longer colliding 47 | if ( playable.rect.collideLine(topEdge[0], topEdge[1]) ) 48 | { 49 | var newX = playable.getVelocity().x; 50 | if (newX < 0 ) 51 | { 52 | newX += drag; 53 | } 54 | else if ( newX > 0 ) 55 | { 56 | newX -= drag; 57 | } 58 | playable.setVelocity( newX, 0 ); 59 | playable.rect.bottom = (rect.top - 0.01); 60 | playable.setMovement('walk'); 61 | } 62 | else if ( playable.rect.collideLine(bottomEdge[0], bottomEdge[1]) ) 63 | { 64 | playable.setVelocity( playable.getVelocity().x, 0 ); 65 | playable.rect.top = (rect.bottom + 0.01); 66 | } 67 | 68 | //Check the left and right colliision points. If a collision is 69 | //detected then set the velocity on the X axis to zero and move 70 | //the playable so that it is no longer colliding 71 | if ( playable.rect.collideLine(leftEdge[0], leftEdge[1]) ) 72 | { 73 | playable.setVelocity( 0, playable.getVelocity().y ); 74 | playable.rect.right = (rect.left - 0.01); 75 | 76 | } 77 | else if ( playable.rect.collideLine(rightEdge[0], rightEdge[1]) ) 78 | { 79 | playable.setVelocity( 0, playable.getVelocity().y ); 80 | playable.rect.left = (rect.right + 0.01); 81 | } 82 | } -------------------------------------------------------------------------------- /js/lib/world.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents the game world, containing the game logic and sprites that 3 | * require rendering 4 | * 5 | * @author David North 6 | */ 7 | include_once(['lib/camera.js','lib/player.js', 'lib/scorecard.js', 8 | 'lib/lever.js', 'lib/door.js', 'lib/gates/andGate.js', 9 | 'lib/gates/orGate.js', 'lib/gates/notGate.js', 'lib/block.js', 10 | 'lib/goal.js', 'lib/tooltip.js', 'lib/platform.js']); 11 | function world() 12 | { 13 | //The maximum X velocity a player can trvel (heading right) 14 | const MAX_X_VELOCITY = 200; 15 | 16 | //The maximum Y velocity a player can trvel (heading down) 17 | const MAX_Y_VELOCITY = 400; 18 | 19 | //The minimum X velocity a player can trvel (heading left) 20 | const MIN_X_VELOCITY = -250; 21 | 22 | //The minimum Y velocity a player can trvel (heading up) 23 | const MIN_Y_VELOCITY = -350; 24 | 25 | /** 26 | * @var boolean Whether the world has fully loaded 27 | */ 28 | var _hasLoaded; 29 | 30 | var _levelComplete; 31 | 32 | var _gameTime; 33 | 34 | var _scorecard; 35 | 36 | /** 37 | * @var camera The camera to use 38 | */ 39 | var _camera; 40 | 41 | /** 42 | * @var gamejs.sprite.Sprite Represents the bounding box (and background) 43 | * of a level 44 | */ 45 | var _levelSprite; 46 | 47 | var _levelRect; 48 | 49 | /** 50 | * @var player Represents the player 51 | */ 52 | var _p; 53 | 54 | /** 55 | * @var gamejs.sprite.Group Represents all objects a player can 56 | * interact with 57 | */ 58 | var _objects; 59 | 60 | var _goals; 61 | 62 | _levelSpriteCollission = function(playable){ 63 | var rightEdge = [ 64 | [this.rect.right, this.rect.top], 65 | [this.rect.right, this.rect.bottom] 66 | ]; 67 | 68 | var leftEdge = [ 69 | [this.rect.left, this.rect.top], 70 | [this.rect.left, this.rect.bottom] 71 | ]; 72 | 73 | var topEdge = [ 74 | [this.rect.left, this.rect.top], 75 | [this.rect.right, this.rect.top] 76 | ]; 77 | 78 | var bottomEdge = [ 79 | [this.rect.left, this.rect.bottom], 80 | [this.rect.right, this.rect.bottom] 81 | ]; 82 | 83 | //Test the left and right edges, setting as appropriate 84 | if ( playable.rect.collideLine(rightEdge[0], rightEdge[1]) ) 85 | { 86 | playable.rect.right = this.rect.right; 87 | } 88 | else if ( playable.rect.collideLine(leftEdge[0], leftEdge[1]) ) 89 | { 90 | playable.rect.left = this.rect.left; 91 | } 92 | 93 | //Test the top and bottom edges, setting as appropriate 94 | if ( playable.rect.collideLine( topEdge[0], topEdge[1]) ) 95 | { 96 | playable.rect.top = this.rect.top; 97 | } 98 | else if ( playable.rect.collideLine(bottomEdge[0], bottomEdge[1]) ) 99 | { 100 | playable.rect.bottom = this.rect.bottom; 101 | } 102 | } 103 | 104 | /** 105 | * Main initiation method. Must be called before using the object 106 | * 107 | * @param object mainSurface the surface that everything will be drawn to 108 | * 109 | * @return world 110 | */ 111 | this.init = function( levelNum, mainSurface ) 112 | { 113 | this.clear(); 114 | 115 | _levelSprite.image = gamejs.image.load('img/bg.png'); 116 | 117 | var _size = _levelSprite.image.getSize(); 118 | _levelSprite.rect = new gamejs.Rect([0, 0], [_size[0], _size[1]]); 119 | _levelRect = new gamejs.Rect([0, 0], [_size[0], _size[1]]); 120 | 121 | _objects.add(_levelSprite); 122 | 123 | _camera.setWidth( mainSurface.getSize()[0] ); 124 | _camera.setHeight( mainSurface.getSize()[1] ); 125 | 126 | $.ajax({ 127 | "url": "js/lib/levels/level_"+levelNum+".js", 128 | "dataType": "json", 129 | "success": function(data){ 130 | _loadLevel(data); 131 | _camera.focusOn(_p.getCurrentPlayable().rect, true); 132 | _hasLoaded = true; 133 | }, 134 | "error": function(jqXHR, textStatus, errorThrown){ 135 | throw errorThrown; 136 | } 137 | }); 138 | 139 | return this; 140 | } 141 | 142 | this.clear = function(){ 143 | if ( _objects instanceof gamejs.sprite.Group ) 144 | { 145 | while( _objects.sprites().length ) 146 | { 147 | var sprite = _objects.sprites()[0]; 148 | 149 | if ( typeof(sprite.hide) == 'function' ) 150 | { 151 | sprite.hide(); 152 | } 153 | 154 | sprite.kill(); 155 | sprite.remove(_objects); 156 | }; 157 | } 158 | 159 | if ( _scorecard instanceof scorecard ) 160 | { 161 | _scorecard.hide(); 162 | } 163 | 164 | _hasLoaded = false; 165 | _levelComplete = false; 166 | _gameTime = 0; 167 | _scorecard = new scorecard(); 168 | _camera = new camera( this ); 169 | _levelSprite = new gamejs.sprite.Sprite(); 170 | _levelRect = new gamejs.Rect([0,0]); 171 | _p = new player(); 172 | _objects = new gamejs.sprite.Group(); 173 | _goals = []; 174 | 175 | _levelSprite.handleCollision = _levelSpriteCollission; 176 | } 177 | 178 | 179 | this.getBoundingRect = function() 180 | { 181 | return _levelRect; 182 | } 183 | 184 | this.getObjects = function() 185 | { 186 | return [ _p.getPlayables(), _objects ] 187 | } 188 | 189 | this.getPlayer = function() 190 | { 191 | return _p; 192 | } 193 | 194 | /** 195 | * Handles user input and modifies the world objects accordingly 196 | * 197 | * @return world 198 | */ 199 | this.handleInput = function() 200 | { 201 | if ( _hasLoaded && !_camera.isAnimating()) 202 | { 203 | var self = this; 204 | 205 | //Loop through each game event (key presses mouse movements etc) 206 | gamejs.event.get().forEach(function(event){ 207 | if ( !_levelComplete ) 208 | { 209 | var _currentPlayer = _p.getCurrentPlayable(); 210 | 211 | _p.handleInput(event); 212 | 213 | if ( _p.getCurrentPlayable() != _currentPlayer ) 214 | { 215 | _camera.focusOn( 216 | _p.getCurrentPlayable().rect, true, true 217 | ); 218 | } 219 | 220 | _objects.forEach(function(obj){ 221 | if ( typeof(obj.handleInput) == 'function' ) 222 | { 223 | obj.handleInput(self, event); 224 | } 225 | }); 226 | } 227 | }); 228 | } 229 | 230 | return this; 231 | } 232 | 233 | /** 234 | * Updates all objects within the world 235 | * 236 | * @param int msDuration The amount of time since the last update 237 | * 238 | * @return world 239 | */ 240 | this.update = function( msDuration ) 241 | { 242 | if ( _hasLoaded && !_levelComplete ) 243 | { 244 | _gameTime += msDuration; 245 | 246 | //Apply the gravitational pull of the world 247 | _applyGravity( msDuration ); 248 | 249 | //Apply updates to the player and any objects in the world 250 | _p.update( msDuration ); 251 | _objects.update( msDuration ); 252 | 253 | var colliders = 254 | gamejs.sprite.groupCollide(_p.getPlayables(), _objects); 255 | 256 | for( var i = 0; i < colliders.length; i++ ) 257 | { 258 | if ( typeof(colliders[i].b.handleCollision) == 'function' ) 259 | { 260 | var isCurrentPlayer = false; 261 | if ( colliders[i].a === _p.getCurrentPlayable() ) 262 | { 263 | isCurrentPlayer = true; 264 | } 265 | 266 | colliders[i].b.handleCollision( 267 | colliders[i].a, isCurrentPlayer 268 | ); 269 | } 270 | } 271 | 272 | //Modify the camera position 273 | _camera.update ( msDuration ); 274 | 275 | _levelComplete = true; 276 | for( var i = 0; i < _goals.length; i++ ) 277 | { 278 | if ( !_goals[i].isActive() ) 279 | { 280 | _levelComplete = false; 281 | break; 282 | } 283 | } 284 | 285 | if ( _levelComplete ) 286 | { 287 | for( var i = 0; i < _objects.sprites().length; i++ ) 288 | { 289 | var obj = _objects.sprites()[i]; 290 | if ( obj instanceof tooltip ) 291 | { 292 | obj.hide(false); 293 | } 294 | } 295 | 296 | _scorecard.setTimeTaken(_gameTime / 1000); 297 | _scorecard.setClonesUsed(_p.getNumClones()); 298 | _scorecard.show(); 299 | } 300 | } 301 | 302 | return this; 303 | } 304 | 305 | /** 306 | * Draws all objects within the world 307 | * 308 | * @return this 309 | */ 310 | this.draw = function ( mainSurface ) 311 | { 312 | if ( _hasLoaded ) 313 | { 314 | //Draw the level, collidables and non collidables, as these need 315 | //to be behind the player 316 | _objects.draw( mainSurface ); 317 | 318 | //Draw the player at the forefront of the level 319 | _p.draw( mainSurface ); 320 | } 321 | 322 | return this; 323 | } 324 | 325 | /** 326 | * Loads the level data from an array, filling the worls with collidables 327 | * and ensuring it's playable 328 | */ 329 | var _loadLevel = function( data, input, output ) 330 | { 331 | var _hasPlayer = false; 332 | for ( var i = 0; i < data.length; i ++) 333 | { 334 | if ( data[i]['type'] == 'stats' ) 335 | { 336 | _scorecard.setParForClones(data[i]['clonePar']); 337 | _scorecard.setParForTime(data[i]['timePar']); 338 | 339 | if ( typeof(data[i]['lastLevel']) == 'boolean' ) 340 | { 341 | _scorecard.setLastLevel(data[i]['lastLevel']); 342 | } 343 | else 344 | { 345 | _scorecard.setLastLevel(false); 346 | } 347 | } 348 | else 349 | { 350 | var xAmount = 1; 351 | var yAmount = 1; 352 | 353 | if ( typeof(data[i]['repeat-x']) != 'undefined') 354 | { 355 | xAmount = data[i]['repeat-x']; 356 | } 357 | 358 | if ( typeof(data[i]['repeat-y']) != 'undefined') 359 | { 360 | yAmount = data[i]['repeat-y']; 361 | } 362 | 363 | for ( var x = 0; x < xAmount; x++ ) 364 | { 365 | for ( var y = 0; y < yAmount; y++ ) 366 | { 367 | _addObjectToWorld(data[i], x, y, input, output); 368 | } 369 | } 370 | } 371 | } 372 | } 373 | 374 | var _addObjectToWorld = function( data, x, y, input, output ){ 375 | var obj = null; 376 | 377 | if ( 'tooltip' == data['type'] ) 378 | { 379 | obj = new tooltip(); 380 | obj.setText(data['text']); 381 | _objects.add( obj ); 382 | } 383 | else 384 | { 385 | switch ( data['type'] ) 386 | { 387 | case 'block': 388 | case 'wall': 389 | data['type'] = 'block'; 390 | case 'andGate': 391 | case 'orGate': 392 | case 'notGate': 393 | case 'lever': 394 | case 'door': 395 | case 'goal': 396 | case 'platform': 397 | var type = data['type']; 398 | obj = new window[type](); 399 | _objects.add( obj ); 400 | 401 | if ( 'goal' === type ) 402 | { 403 | _goals.push( obj ); 404 | } 405 | break; 406 | 407 | case 'player': 408 | obj = _p.getCurrentPlayable(); 409 | } 410 | } 411 | 412 | if ( null != obj ) 413 | { 414 | if ( obj instanceof io ) 415 | { 416 | if ( typeof(data['outputs']) !== 'undefined' ) 417 | { 418 | _loadLevel(data['outputs'], obj); 419 | } 420 | 421 | if ( typeof(data['inputs']) !== 'undefined' ) 422 | { 423 | _loadLevel(data['inputs'], null, obj) 424 | } 425 | 426 | if ( typeof(input) !== 'undefined' && input != null ) 427 | { 428 | obj.addInput(input); 429 | } 430 | 431 | if ( typeof(output) !== 'undefined' && output != null ) 432 | { 433 | obj.addOutput(output); 434 | } 435 | } 436 | 437 | if ( typeof(obj.setDimensions) != 'undefined' ) 438 | { 439 | if ( typeof(data['width']) != 'undefined' 440 | && typeof(data['height']) != 'undefined' ) 441 | { 442 | obj.setDimensions(data['width'], data['height']); 443 | } 444 | } 445 | 446 | var width = obj.rect.width; 447 | var height = obj.rect.height; 448 | 449 | var xPos = (data['x'] + (width * x)); 450 | var yPos = (data['y'] + (height * y)); 451 | 452 | obj.setPosition( xPos, yPos ); 453 | } 454 | } 455 | 456 | /** 457 | * Applies the gravitational pull of the world on all playables 458 | */ 459 | var _applyGravity = function( msDuration ) 460 | { 461 | //Loop through each player and increase the Y velocity downward. 462 | //If the player is jumping, this has the affect of slowing the 463 | //player down. Otherwise the player is falling. 464 | _p.getPlayables().forEach(function(obj){ 465 | var newVelocityY = obj.getVelocity().y 466 | newVelocityY += Math.round(0.3 * msDuration); 467 | 468 | obj.setVelocity( obj.getVelocity().x, newVelocityY ) 469 | 470 | //the velocity cannot exceed the maximums, so ensuer that the player 471 | //is not falling too fast 472 | _sanatiseVelocity(obj); 473 | }); 474 | } 475 | 476 | /** 477 | * Ensures that the player is not travelling too fast ion any direction 478 | */ 479 | var _sanatiseVelocity = function(obj) 480 | { 481 | //Get the current Velocity 482 | var velocity = obj.getVelocity(); 483 | 484 | //Make sure that the X velocity is not too slow or fast 485 | velocity.x = Math.max( MIN_X_VELOCITY, velocity.x ); 486 | velocity.x = Math.min( MAX_X_VELOCITY, velocity.x ); 487 | 488 | //Make sure that the Y velocity is not too slow or fast 489 | velocity.y = Math.max( MIN_Y_VELOCITY, velocity.y ); 490 | velocity.y = Math.min( MAX_Y_VELOCITY, velocity.y ); 491 | 492 | //Update the players velocity 493 | obj.setVelocity( velocity.x, velocity.y ); 494 | } 495 | } -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | gamejs = require('gamejs'); 2 | font = require('gamejs/font'); 3 | 4 | //Preload all the required images 5 | gamejs.preload([ 6 | 'img/splash-screen.png','img/new-game.png', 'img/bg.png','img/player.png', 7 | 'img/blank.png','img/switch.png','img/door.png','img/goal.png', 8 | 'img/platform-left.png','img/platform-right.png', 9 | 'img/platform-middle.png', 'img/selected.png', 10 | 'img/scorecard-background.png','img/game-window.png', 11 | 'img/reset.png', 'img/star-off.png', 'img/star-on.png', 12 | 'img/menu-button.png', 'img/next-button.png', 'img/end.png' 13 | ]); 14 | 15 | gamejs.ready(function() { 16 | 17 | var display = gamejs.display.setMode([800, 600]); 18 | 19 | //Ensure that all required files are included 20 | include_once([ 21 | 'lib/startMenu.js' 22 | ]); 23 | 24 | var mainSurface = gamejs.display.getSurface(); 25 | var mainWindow = new startMenu(); 26 | var self = this; 27 | 28 | // msDuration = time since last tick() call 29 | var tick = function(msDuration){ 30 | mainSurface.fill("#FFFFFF"); 31 | 32 | //Handle user input 33 | mainWindow.handleInput( mainSurface ); 34 | 35 | //Update the worlds objects 36 | mainWindow.update( msDuration ); 37 | 38 | //Draw the new objects 39 | mainWindow.draw( mainSurface ); 40 | }; 41 | 42 | //Set up listeners for the body when a scorecard is present 43 | $('body').keydown(function(event){ 44 | //Only check key presses if the scorcard is shown 45 | if ( $('#game_scorecard').is(":visible") ) 46 | { 47 | switch ( event.keyCode ) 48 | { 49 | //Enter and e will all move to the next level 50 | case 13: 51 | case 69: 52 | $('#game_scorecard .nextLevel').click(); 53 | event.preventDefault(); 54 | break; 55 | //Escape will quit to the menu 56 | case 27: 57 | $('#game_scorecard .mainMenu').click(); 58 | event.preventDefault(); 59 | break; 60 | //r will reset the level 61 | case 82: 62 | $('#game_scorecard .resetLevel').click(); 63 | event.preventDefault(); 64 | break; 65 | } 66 | } 67 | }); 68 | 69 | //Remove the loading bar 70 | $('#preload').remove(); 71 | $('#gameWindow').show(); 72 | 73 | //Set up the tick function, run at 60fps 74 | gamejs.time.fpsCallback(tick, self, 60); 75 | }); 76 | --------------------------------------------------------------------------------