├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── css ├── gauntlet.css └── normalize.css ├── images ├── backgrounds.png ├── booting.gif ├── entities.png ├── key.png ├── logo.jpg ├── mute.png ├── potion.png └── splash.jpg ├── index.html ├── js ├── game.js ├── game │ ├── base.js │ ├── dom.js │ ├── game.js │ ├── key.js │ ├── math.js │ ├── pubsub.js │ └── vendor │ │ ├── animator.js │ │ ├── audio-fx.js │ │ ├── sizzle.js │ │ ├── state-machine.js │ │ └── stats.js ├── gauntlet.js └── vendor.js ├── levels ├── level1.png ├── level10.png ├── level2.png ├── level3.png ├── level4.png ├── level5.png ├── level6.png ├── level7.png ├── level8.png ├── level9.png ├── reference.png ├── testlevel.png ├── trainer1.png ├── trainer2.png ├── trainer3.png ├── trainer4.png ├── trainer5.png ├── trainer6.png └── trainer7.png └── sounds ├── collectfood.mp3 ├── collectfood.ogg ├── collectgold.mp3 ├── collectgold.ogg ├── collectkey.mp3 ├── collectkey.ogg ├── collectpotion.mp3 ├── collectpotion.ogg ├── exitlevel.mp3 ├── exitlevel.ogg ├── femalepain1.mp3 ├── femalepain1.ogg ├── femalepain2.mp3 ├── femalepain2.ogg ├── fireelf.mp3 ├── fireelf.ogg ├── firevalkyrie.mp3 ├── firevalkyrie.ogg ├── firewarrior.mp3 ├── firewarrior.ogg ├── firewizard.mp3 ├── firewizard.ogg ├── gameover.mp3 ├── gameover.ogg ├── generatordeath.mp3 ├── generatordeath.ogg ├── highscore.mp3 ├── highscore.ogg ├── malepain1.mp3 ├── malepain1.ogg ├── malepain2.mp3 ├── malepain2.ogg ├── monsterdeath1.mp3 ├── monsterdeath1.ogg ├── monsterdeath2.mp3 ├── monsterdeath2.ogg ├── monsterdeath3.mp3 ├── monsterdeath3.ogg ├── music.bloodyhalo.mp3 ├── music.bloodyhalo.ogg ├── music.citrinitas.mp3 ├── music.citrinitas.ogg ├── music.fleshandsteel.mp3 ├── music.fleshandsteel.ogg ├── music.lostcorridors.mp3 ├── music.lostcorridors.ogg ├── music.mountingassault.mp3 ├── music.mountingassault.ogg ├── music.phantomdrone.mp3 ├── music.phantomdrone.ogg ├── music.thebeginning.mp3 ├── music.thebeginning.ogg ├── music.warbringer.mp3 ├── music.warbringer.ogg ├── opendoor.mp3 ├── opendoor.ogg ├── victory.mp3 ├── victory.ogg ├── weak.mp3 └── weak.ogg /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # for running rake assets:server 4 | group :server do 5 | gem "unified-assets" 6 | gem "rack" 7 | gem "thin" 8 | end 9 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | daemons (1.1.9) 5 | eventmachine (1.0.0) 6 | rack (1.5.2) 7 | thin (1.5.0) 8 | daemons (>= 1.0.9) 9 | eventmachine (>= 0.12.6) 10 | rack (>= 1.0.0) 11 | unified-assets (0.0.1) 12 | rack 13 | 14 | PLATFORMS 15 | ruby 16 | 17 | DEPENDENCIES 18 | rack 19 | thin 20 | unified-assets 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, 2014, 2015, 2016 Jake Gordon and contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Javascript Gauntlet 2 | =================== 3 | 4 | An HTML5 Gauntlet-style Game 5 | 6 | * [play the game](https://jakesgordon.com/games/gauntlet/) 7 | * read a [blog article](https://jakesgordon.com/writing/javascript-gauntlet/) 8 | * view the [source](https://github.com/jakesgordon/javascript-gauntlet) 9 | 10 | >> NOTE: single player only at this time, multiplayer coming later 11 | 12 | SUPPORTED BROWSERS 13 | ================== 14 | 15 | - Chrome 24+ 16 | - Firefox 18+ 17 | - IE 9+ 18 | 19 | KNOWN ISSUES 20 | ============ 21 | 22 | - No support for touch/mobile devices 23 | - Startup (and level transition) can be slow over a slow network connection (duh) 24 | 25 | TODO 26 | ==== 27 | 28 | * POLISH - better scoreboard fonts 29 | * POLISH - let doors have corners (and choose more wisely between horz/vert when up against odd walls) 30 | * POLISH - render monsters top down with slight overlap for pseudo-3d 31 | 32 | DEVELOPMENT 33 | =========== 34 | 35 | The game is 100% client side javascript and css. It should run when served up by any web server. 36 | 37 | Any changes to the following files will be reflected immediately on refresh of the browser 38 | 39 | - js/gauntlet.js 40 | - css/gauntlet.css 41 | - images/ 42 | - sounds/ 43 | - levels/ 44 | 45 | However, if you modify the js/game/ or js/vendor/ javascript files, the unified versions need to be regenerated: 46 | 47 | js/vendor.js # the unified 3rd party vendor scripts (sizzle, animator, audio-fx, stats, state-machine) 48 | js/game.js # the unified general purpose game engine 49 | 50 | If you have the Ruby language available, Rake tasks can be used to auto generate these unified files: 51 | 52 | rake assets:create # re-create unified javascript/css asset files on demand 53 | rake assets:server # run a simple rack server that automatically regenerates the unified files when the underlying source is modified 54 | 55 | Attributions 56 | ============= 57 | 58 | All music is licensed, royalty-free, from [Lucky Lion Studios](http://luckylionstudios.com/) for this project only. If you re-use this 59 | project for your own purposes you must license your own music please. 60 | 61 | All sound effects are licensed, royalty-free from [Premium Beat](http://www.premiumbeat.com/sfx) for this project only. If you re-use this 62 | project for your own purposes you must license your own sound effects please. 63 | 64 | Background tilesets (walls, floors, doors) are provided by 65 | 66 | - [Ricardo Chirino](ricardochirino.com) 67 | - [Open Game Art](http://opengameart.org/content/gauntlet-like-tiles) 68 | 69 | Entity sprites (players, monsters, treasure, etc) are almost certainly ripped from an old (s)NES console ? 70 | 71 | - [Open Game Art](http://opengameart.org/forumtopic/request-for-tileset-spritesheet-similar-to-gauntlet-ii) 72 | 73 | License 74 | ======= 75 | 76 | [MIT](http://en.wikipedia.org/wiki/MIT_License) license. 77 | 78 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'unified_assets/tasks' 3 | 4 | UnifiedAssets::Tasks.new do |t| 5 | t.minify = false 6 | t.assets = { 7 | 8 | "js/vendor.js" => [ 9 | 'js/game/vendor/stats.js', # https://github.com/mrdoob/stats.js 10 | 'js/game/vendor/sizzle.js', # http://sizzlejs.com/ 11 | 'js/game/vendor/animator.js', # http://berniesumption.com/software/animator/ 12 | 'js/game/vendor/audio-fx.js', # https://github.com/jakesgordon/javascript-audio-fx 13 | 'js/game/vendor/state-machine.js', # https://github.com/jakesgordon/javascript-state-machine 14 | ], 15 | 16 | "js/game.js" => [ 17 | 'js/game/base.js', 18 | 'js/game/game.js', 19 | 'js/game/pubsub.js', 20 | 'js/game/dom.js', 21 | 'js/game/key.js', 22 | 'js/game/math.js', 23 | ] 24 | 25 | } 26 | end 27 | 28 | -------------------------------------------------------------------------------- /css/gauntlet.css: -------------------------------------------------------------------------------- 1 | /*******************************/ 2 | /* global document body styles */ 3 | /*******************************/ 4 | 5 | body { 6 | background: black; 7 | color: #CCC; 8 | user-select: none; 9 | } 10 | 11 | /**************************/ 12 | /* unique id-based styles */ 13 | /**************************/ 14 | 15 | #gauntlet { margin: 1em auto; position: relative; background: black; } 16 | #booting { position: absolute; width: 6%; left: 30%; top: 40%; } 17 | #splash { position: absolute; display: none; } 18 | #canvas { display: inline-block; vertical-align: top; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } 19 | #help { position: absolute; width: 33%; left: 22%; top: 30%; text-align: center; font-size: 1.5em; font-weight: bold; background-color: rgba(255, 255, 255, 0.75); border: 6px solid black; color: black; padding: 1em; display: inline-block; border-radius: 0.5em; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } 20 | #scoreboard { display: inline-block; vertical-align: top; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } 21 | #stats { position: absolute; right: -90px; bottom: 0; color: white; } 22 | #instructions { font-style: italic; color: #999; font-size: 9pt; margin-left: 1em; margin-top: 0.5em; } 23 | #instructions b { color: #DDD; } 24 | 25 | #gauntlet.menu #splash { display: inline-block; } 26 | #gauntlet.help #canvas { opacity: 0.5; } 27 | 28 | /**************/ 29 | /* scoreboard */ 30 | /**************/ 31 | 32 | #scoreboard { text-align: center; } 33 | 34 | #scoreboard #logo { width: 100%; } 35 | #scoreboard .level { margin-top: 0px; margin-bottom: 0px; } 36 | #scoreboard hr { width: 50%; margin: 1em auto; border: 1px solid #111; border-top: 0px; border-left: 0px; border-right: 0px; } 37 | #scoreboard .high { margin-bottom: 2em; font-size: 82.5%; opacity: 0.5; } 38 | #scoreboard .high .value { font-weight: bold; } 39 | 40 | #scoreboard .player { height: 6em; border: 2px solid #222; margin: 1em 0.5em; padding: 0.5em 0; opacity: 0.5; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } 41 | #scoreboard .player .name { font-weight: bold; } 42 | #scoreboard .player .score { display: none; } 43 | #scoreboard .player .health { display: none; } 44 | #scoreboard .player .treasure { display: none; } 45 | #scoreboard .player .press { color: #999; margin-top: 1em; } 46 | #scoreboard .player .press b { color: white; } 47 | #scoreboard .player .multi { color: #666; font-style: italic; margin-top: 1em; font-size: 82.5%; } 48 | 49 | #scoreboard .player.active { opacity: 1.0; } 50 | #scoreboard .player.active .score { display: inline-block; text-align: right; margin-right: 1em; } 51 | #scoreboard .player.active .health { display: inline-block; text-align: left; margin-left: 1em; } 52 | #scoreboard .player.active .treasure { display: block; text-align: left; margin: 0 0.5em; margin-top: 0.25em; } 53 | #scoreboard .player.active .label { opacity: 0.7; font-size: smaller; } 54 | #scoreboard .player.active .value { opacity: 1.0; font-weight: bold; } 55 | 56 | #scoreboard .player .treasure img.key { margin-right: 2px; width: 1em; height: 1em; } 57 | #scoreboard .player .treasure img.potion { margin-left: 0px; float: right; width: 1em; height: 1em; } 58 | 59 | #scoreboard #warrior { color: #F90503; } 60 | #scoreboard #valkyrie { color: #08B4F0; } 61 | #scoreboard #wizard { color: #F5FC00; } 62 | #scoreboard #elf { color: #00FF03; } 63 | 64 | #scoreboard #warrior .press b { color: #F90503; } 65 | #scoreboard #valkyrie .press b { color: #08B4F0; } 66 | #scoreboard #wizard .press b { color: #F5FC00; } 67 | #scoreboard #elf .press b { color: #00FF03; } 68 | 69 | #scoreboard #warrior.active { border-color: #F90503; } 70 | #scoreboard #valkyrie.active { border-color: #08B4F0; } 71 | #scoreboard #wizard.active { border-color: #F5FC00; } 72 | #scoreboard #elf.active { border-color: #00FF03; } 73 | 74 | #scoreboard #warrior.weak { background-color: #F90503; } 75 | #scoreboard #valkyrie.weak { background-color: #08B4F0; } 76 | #scoreboard #wizard.weak { background-color: #F5FC00; } 77 | #scoreboard #elf.weak { background-color: #00FF03; } 78 | 79 | #scoreboard .high.warrior .value { color: #F90503; } 80 | #scoreboard .high.valkyrie .value { color: #08B4F0; } 81 | #scoreboard .high.wizard .value { color: #F5FC00; } 82 | #scoreboard .high.elf .value { color: #00FF03; } 83 | 84 | #gauntlet #scoreboard .player .press { display: none; } 85 | #gauntlet.menu #scoreboard .player .press { display: block; } 86 | #gauntlet #scoreboard .player .multi { display: block; } 87 | #gauntlet.booting #scoreboard .player .multi { display: none; } 88 | #gauntlet.menu #scoreboard .player .multi { display: none; } 89 | #gauntlet #scoreboard .player.active .multi { display: none; } 90 | 91 | #sound { width: 24px; height: 24px; background: url(../images/mute.png); display: inline-block; cursor: pointer; position: absolute; right: 1em; } 92 | #sound.on { background-position: 0 0; } 93 | #sound.off { background-position: -24px 0; } 94 | 95 | /*****************************/ 96 | /* @media query based layout */ 97 | /*****************************/ 98 | 99 | @media screen and (min-width: 0px) and (min-height: 0px) { #gauntlet { width: 512px; height: 384px; font-size: 7pt; } #canvas { width: 384px; height: 384px; } #splash { width: 384px; height: 384px; } #scoreboard { width: /* 512 - 384 - 8 = */ 120px; height: 384px; } } /* block = 16 */ 100 | @media screen and (min-width: 840px) and (min-height: 530px) { #gauntlet { width: 640px; height: 480px; font-size: 9pt; } #canvas { width: 480px; height: 480px; } #splash { width: 480px; height: 480px; } #scoreboard { width: /* 640 - 480 - 8 = */ 152px; height: 480px; } } /* block = 20 */ 101 | @media screen and (min-width: 968px) and (min-height: 626px) { #gauntlet { width: 768px; height: 576px; font-size: 11pt; } #canvas { width: 576px; height: 576px; } #splash { width: 576px; height: 576px; } #scoreboard { width: /* 768 - 576 - 8 = */ 184px; height: 576px; } } /* block = 24 */ 102 | @media screen and (min-width: 1096px) and (min-height: 722px) { #gauntlet { width: 896px; height: 672px; font-size: 13pt; } #canvas { width: 672px; height: 672px; } #splash { width: 672px; height: 672px; } #scoreboard { width: /* 896 - 672 - 8 = */ 216px; height: 672px; } } /* block = 28 */ 103 | @media screen and (min-width: 1224px) and (min-height: 818px) { #gauntlet { width: 1024px; height: 768px; font-size: 15pt; } #canvas { width: 768px; height: 768px; } #splash { width: 768px; height: 768px; } #scoreboard { width: /* 1024 - 768 - 8 = */ 248px; height: 768px; } } /* block = 32 */ 104 | 105 | -------------------------------------------------------------------------------- /css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css 2011-07-12T10:51 UTC · http://github.com/necolas/normalize.css */ 2 | 3 | /* ============================================================================= 4 | HTML5 element display 5 | ========================================================================== */ 6 | 7 | /* 8 | * Corrects block display not defined in IE6/7/8/9 & FF3 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | nav, 20 | section { 21 | display: block; 22 | } 23 | 24 | /* 25 | * Corrects inline-block display not defined in IE6/7/8/9 & FF3 26 | * Known limitation: IE6 will not apply style for 'audio[controls]' 27 | */ 28 | 29 | audio[controls], 30 | canvas, 31 | video { 32 | display: inline-block; 33 | *display: inline; 34 | *zoom: 1; 35 | } 36 | 37 | 38 | /* ============================================================================= 39 | Base 40 | ========================================================================== */ 41 | 42 | /* 43 | * 1. Corrects text resizing oddly in IE6/7 when body font-size is set using em units 44 | * http://clagnut.com/blog/348/#c790 45 | * 2. Keeps page centred in all browsers regardless of content height 46 | * 3. Removes Android and iOS tap highlight color to prevent entire container being highlighted 47 | * www.yuiblog.com/blog/2010/10/01/quick-tip-customizing-the-mobile-safari-tap-highlight-color/ 48 | * 4. Prevents iOS text size adjust after orientation change, without disabling user zoom 49 | * www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/ 50 | */ 51 | 52 | html { 53 | font-size: 100%; /* 1 */ 54 | overflow-y: scroll; /* 2 */ 55 | -webkit-tap-highlight-color: rgba(0,0,0,0); /* 3 */ 56 | -webkit-text-size-adjust: 100%; /* 4 */ 57 | -ms-text-size-adjust: 100%; /* 4 */ 58 | } 59 | 60 | /* 61 | * Addresses margins handled incorrectly in IE6/7 62 | */ 63 | 64 | body { 65 | margin: 0; 66 | } 67 | 68 | /* 69 | * Addresses font-family inconsistency between 'textarea' and other form elements. 70 | */ 71 | 72 | body, 73 | button, 74 | input, 75 | select, 76 | textarea { 77 | font-family: sans-serif; 78 | } 79 | 80 | 81 | /* ============================================================================= 82 | Links 83 | ========================================================================== */ 84 | 85 | a { 86 | color: #00e; 87 | } 88 | 89 | a:visited { 90 | color: #551a8b; 91 | } 92 | 93 | /* 94 | * Addresses outline displayed oddly in Chrome 95 | */ 96 | 97 | a:focus { 98 | outline: thin dotted; 99 | } 100 | 101 | /* 102 | * Improves readability when focused and also mouse hovered in all browsers 103 | * people.opera.com/patrickl/experiments/keyboard/test 104 | */ 105 | 106 | a:hover, 107 | a:active { 108 | outline: 0; 109 | } 110 | 111 | 112 | /* ============================================================================= 113 | Typography 114 | ========================================================================== */ 115 | 116 | /* 117 | * Addresses styling not present in IE7/8/9, S5, Chrome 118 | */ 119 | 120 | abbr[title] { 121 | border-bottom: 1px dotted; 122 | } 123 | 124 | /* 125 | * Addresses style set to 'bolder' in FF3/4, S4/5, Chrome 126 | */ 127 | 128 | b, 129 | strong { 130 | font-weight: bold; 131 | } 132 | 133 | blockquote { 134 | margin: 1em 40px; 135 | } 136 | 137 | /* 138 | * Addresses styling not present in S5, Chrome 139 | */ 140 | 141 | dfn { 142 | font-style: italic; 143 | } 144 | 145 | /* 146 | * Addresses styling not present in IE6/7/8/9 147 | */ 148 | 149 | mark { 150 | background: #ff0; 151 | color: #000; 152 | } 153 | 154 | /* 155 | * Corrects font family set oddly in IE6, S5, Chrome 156 | * en.wikipedia.org/wiki/User:Davidgothberg/Test59 157 | */ 158 | 159 | pre, 160 | code, 161 | kbd, 162 | samp { 163 | font-family: monospace, monospace; 164 | _font-family: 'courier new', monospace; 165 | font-size: 1em; 166 | } 167 | 168 | /* 169 | * Improves readability of pre-formatted text in all browsers 170 | */ 171 | 172 | pre { 173 | white-space: pre; 174 | white-space: pre-wrap; 175 | word-wrap: break-word; 176 | } 177 | 178 | /* 179 | * 1. Addresses CSS quotes not supported in IE6/7 180 | * 2. Addresses quote property not supported in S4 181 | */ 182 | 183 | /* 1 */ 184 | 185 | q { 186 | quotes: none; 187 | } 188 | 189 | /* 2 */ 190 | 191 | q:before, 192 | q:after { 193 | content: ''; 194 | content: none; 195 | } 196 | 197 | small { 198 | font-size: 75%; 199 | } 200 | 201 | /* 202 | * Prevents sub and sup affecting line-height in all browsers 203 | * gist.github.com/413930 204 | */ 205 | 206 | sub, 207 | sup { 208 | font-size: 75%; 209 | line-height: 0; 210 | position: relative; 211 | vertical-align: baseline; 212 | } 213 | 214 | sup { 215 | top: -0.5em; 216 | } 217 | 218 | sub { 219 | bottom: -0.25em; 220 | } 221 | 222 | 223 | /* ============================================================================= 224 | Lists 225 | ========================================================================== */ 226 | 227 | ul, 228 | ol { 229 | margin: 1em 0; 230 | padding: 0 0 0 40px; 231 | } 232 | 233 | dd { 234 | margin: 0 0 0 40px; 235 | } 236 | 237 | nav ul, 238 | nav ol { 239 | list-style: none; 240 | } 241 | 242 | 243 | /* ============================================================================= 244 | Embedded content 245 | ========================================================================== */ 246 | 247 | /* 248 | * 1. Removes border when inside 'a' element in IE6/7/8/9 249 | * 2. Improves image quality when scaled in IE7 250 | * code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ 251 | */ 252 | 253 | img { 254 | border: 0; /* 1 */ 255 | -ms-interpolation-mode: bicubic; /* 2 */ 256 | } 257 | 258 | /* 259 | * Corrects overflow displayed oddly in IE9 260 | */ 261 | 262 | svg:not(:root) { 263 | overflow: hidden; 264 | } 265 | 266 | 267 | /* ============================================================================= 268 | Figures 269 | ========================================================================== */ 270 | 271 | /* 272 | * Addresses margin not present in IE6/7/8/9, S5, O11 273 | */ 274 | 275 | figure { 276 | margin: 0; 277 | } 278 | 279 | 280 | /* ============================================================================= 281 | Forms 282 | ========================================================================== */ 283 | 284 | /* 285 | * Corrects margin displayed oddly in IE6/7 286 | */ 287 | 288 | form { 289 | margin: 0; 290 | } 291 | 292 | /* 293 | * Define consistent margin and padding 294 | */ 295 | 296 | fieldset { 297 | margin: 0 2px; 298 | padding: 0.35em 0.625em 0.75em; 299 | } 300 | 301 | /* 302 | * 1. Corrects color not being inherited in IE6/7/8/9 303 | * 2. Corrects alignment displayed oddly in IE6/7 304 | */ 305 | 306 | legend { 307 | border: 0; /* 1 */ 308 | *margin-left: -7px; /* 2 */ 309 | } 310 | 311 | /* 312 | * 1. Corrects font size not being inherited in all browsers 313 | * 2. Addresses margins set differently in IE6/7, F3/4, S5, Chrome 314 | * 3. Improves appearance and consistency in all browsers 315 | */ 316 | 317 | button, 318 | input, 319 | select, 320 | textarea { 321 | font-size: 100%; /* 1 */ 322 | margin: 0; /* 2 */ 323 | vertical-align: baseline; /* 3 */ 324 | *vertical-align: middle; /* 3 */ 325 | } 326 | 327 | /* 328 | * 1. Addresses FF3/4 setting line-height using !important in the UA stylesheet 329 | * 2. Corrects inner spacing displayed oddly in IE6/7 330 | */ 331 | 332 | button, 333 | input { 334 | line-height: normal; /* 1 */ 335 | *overflow: visible; /* 2 */ 336 | } 337 | 338 | /* 339 | * Corrects overlap and whitespace issue for buttons and inputs in IE6/7 340 | * Known issue: reintroduces inner spacing 341 | */ 342 | 343 | table button, 344 | table input { 345 | *overflow: auto; 346 | } 347 | 348 | /* 349 | * 1. Improves usability and consistency of cursor style between image-type 'input' and others 350 | * 2. Corrects inability to style clickable 'input' types in iOS 351 | */ 352 | 353 | button, 354 | html input[type="button"], 355 | input[type="reset"], 356 | input[type="submit"] { 357 | cursor: pointer; /* 1 */ 358 | -webkit-appearance: button; /* 2 */ 359 | } 360 | 361 | /* 362 | * Addresses box sizing set to content-box in IE8/9 363 | */ 364 | 365 | input[type="checkbox"], 366 | input[type="radio"] { 367 | box-sizing: border-box; 368 | } 369 | 370 | /* 371 | * 1. Addresses appearance set to searchfield in S5, Chrome 372 | * 2. Addresses box sizing set to border-box in S5, Chrome (include -moz to future-proof) 373 | */ 374 | 375 | input[type="search"] { 376 | -webkit-appearance: textfield; /* 1 */ 377 | -moz-box-sizing: content-box; 378 | -webkit-box-sizing: content-box; /* 2 */ 379 | box-sizing: content-box; 380 | } 381 | 382 | /* 383 | * Corrects inner padding displayed oddly in S5, Chrome on OSX 384 | */ 385 | 386 | input[type="search"]::-webkit-search-decoration { 387 | -webkit-appearance: none; 388 | } 389 | 390 | /* 391 | * Corrects inner padding and border displayed oddly in FF3/4 392 | * www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/ 393 | */ 394 | 395 | button::-moz-focus-inner, 396 | input::-moz-focus-inner { 397 | border: 0; 398 | padding: 0; 399 | } 400 | 401 | /* 402 | * 1. Removes default vertical scrollbar in IE6/7/8/9 403 | * 2. Improves readability and alignment in all browsers 404 | */ 405 | 406 | textarea { 407 | overflow: auto; /* 1 */ 408 | vertical-align: top; /* 2 */ 409 | } 410 | 411 | 412 | /* ============================================================================= 413 | Tables 414 | ========================================================================== */ 415 | 416 | /* 417 | * Remove most spacing between table cells 418 | */ 419 | 420 | table { 421 | border-collapse: collapse; 422 | border-spacing: 0; 423 | } 424 | -------------------------------------------------------------------------------- /images/backgrounds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/images/backgrounds.png -------------------------------------------------------------------------------- /images/booting.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/images/booting.gif -------------------------------------------------------------------------------- /images/entities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/images/entities.png -------------------------------------------------------------------------------- /images/key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/images/key.png -------------------------------------------------------------------------------- /images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/images/logo.jpg -------------------------------------------------------------------------------- /images/mute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/images/mute.png -------------------------------------------------------------------------------- /images/potion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/images/potion.png -------------------------------------------------------------------------------- /images/splash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/images/splash.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Javascript Gauntlet 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 |
20 | Sorry, this example cannot be run because your browser does not support the <canvas> element 21 |
22 |
23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |

 

31 | 32 |
33 | 34 |
High Score: 000000
35 | 36 |
37 |
Warrior
38 |
Score
000000
39 |
Health
000
40 |
41 | 42 | 43 |
44 |
PRESS 1 TO START
45 |
multiplayer coming soon
46 |
47 | 48 |
49 |
Valkyrie
50 |
Score
000000
51 |
Health
000
52 |
53 | 54 | 55 |
56 |
PRESS 2 TO START
57 |
multiplayer coming soon
58 |
59 | 60 |
61 |
Wizard
62 |
Score
000000
63 |
Health
000
64 |
65 | 66 | 67 |
68 |
PRESS 3 TO START
69 |
multiplayer coming soon
70 |
71 | 72 |
73 |
Elf
74 |
Score
000000
75 |
Health
000
76 |
77 | 78 | 79 |
80 |
PRESS 4 TO START
81 |
multiplayer coming soon
82 |
83 | 84 | 85 | 86 |
87 | 88 |
89 | ARROW keys to move, HOLD SPACE to fire, ENTER to use potion 90 |
91 | 92 |
93 | 94 | 95 | 96 | 97 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /js/game.js: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // feature detection 3 | //============================================================================= 4 | ua = function() { 5 | 6 | var ua = navigator.userAgent.toLowerCase(); // should avoid user agent sniffing... but sometimes you just gotta do what you gotta do 7 | var key = ((ua.indexOf("opera") > -1) ? "opera" : null); 8 | key = key || ((ua.indexOf("firefox") > -1) ? "firefox" : null); 9 | key = key || ((ua.indexOf("chrome") > -1) ? "chrome" : null); 10 | key = key || ((ua.indexOf("safari") > -1) ? "safari" : null); 11 | key = key || ((ua.indexOf("msie") > -1) ? "ie" : null); 12 | 13 | try { 14 | var re = (key == "ie") ? "msie ([\\d\\.]*)" : key + "\\/([\\d\\.]*)" 15 | var matches = ua.match(new RegExp(re, "i")); 16 | var version = matches ? matches[1] : null; 17 | } catch (e) {} 18 | 19 | return { 20 | full: ua, 21 | name: key + (version ? " " + version : ""), 22 | version: version, 23 | major: version ? parseInt(version) : null, 24 | is: { 25 | firefox: (key == "firefox"), 26 | chrome: (key == "chrome"), 27 | safari: (key == "safari"), 28 | opera: (key == "opera"), 29 | ie: (key == "ie") 30 | }, 31 | has: { 32 | audio: AudioFX && AudioFX.enabled, 33 | canvas: (document.createElement('canvas').getContext), 34 | touch: ('ontouchstart' in window) 35 | } 36 | } 37 | }(); 38 | 39 | //============================================================================= 40 | // type detection 41 | //============================================================================= 42 | 43 | is = { 44 | 'string': function(obj) { return (typeof obj === 'string'); }, 45 | 'number': function(obj) { return (typeof obj === 'number'); }, 46 | 'bool': function(obj) { return (typeof obj === 'boolean'); }, 47 | 'array': function(obj) { return (obj instanceof Array); }, 48 | 'undefined': function(obj) { return (typeof obj === 'undefined'); }, 49 | 'func': function(obj) { return (typeof obj === 'function'); }, 50 | 'null': function(obj) { return (obj === null); }, 51 | 'notNull': function(obj) { return (obj !== null); }, 52 | 'invalid': function(obj) { return ( is['null'](obj) || is.undefined(obj)); }, 53 | 'valid': function(obj) { return (!is['null'](obj) && !is.undefined(obj)); }, 54 | 'emptyString': function(obj) { return (is.string(obj) && (obj.length == 0)); }, 55 | 'nonEmptyString': function(obj) { return (is.string(obj) && (obj.length > 0)); }, 56 | 'emptyArray': function(obj) { return (is.array(obj) && (obj.length == 0)); }, 57 | 'nonEmptyArray': function(obj) { return (is.array(obj) && (obj.length > 0)); }, 58 | 'document': function(obj) { return (obj === document); }, 59 | 'window': function(obj) { return (obj === window); }, 60 | 'element': function(obj) { return (obj instanceof HTMLElement); }, 61 | 'event': function(obj) { return (obj instanceof Event); }, 62 | 'link': function(obj) { return (is.element(obj) && (obj.tagName == 'A')); } 63 | } 64 | 65 | //============================================================================= 66 | // type coersion 67 | //============================================================================= 68 | 69 | to = { 70 | 'bool': function(obj, def) { if (is.valid(obj)) return ((obj == 1) || (obj == true) || (obj == "1") || (obj == "y") || (obj == "Y") || (obj.toString().toLowerCase() == "true") || (obj.toString().toLowerCase() == 'yes')); else return (is.bool(def) ? def : false); }, 71 | 'number': function(obj, def) { if (is.valid(obj)) { var x = parseFloat(obj); if (!isNaN(x)) return x; } return (is.number(def) ? def : 0); }, 72 | 'string': function(obj, def) { if (is.valid(obj)) return obj.toString(); return (is.string(def) ? def : ''); } 73 | } 74 | 75 | //============================================================================= 76 | // 77 | // Compatibility for older browsers (compatibility: http://kangax.github.com/es5-compat-table/) 78 | // 79 | // Function.bind: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind 80 | // Object.create: http://javascript.crockford.com/prototypal.html 81 | // Object.extend: (defacto standard like jquery $.extend or prototype's Object.extend) 82 | // Class.create: create a simple javascript 'class' (a constructor function with a prototype and optional class methods) 83 | // 84 | //============================================================================= 85 | 86 | if (!Function.prototype.bind) { 87 | Function.prototype.bind = function(obj) { 88 | var slice = [].slice, 89 | args = slice.call(arguments, 1), 90 | self = this, 91 | nop = function () {}, 92 | bound = function () { 93 | return self.apply(this instanceof nop ? this : (obj || {}), args.concat(slice.call(arguments))); 94 | }; 95 | nop.prototype = self.prototype; 96 | bound.prototype = new nop(); 97 | return bound; 98 | }; 99 | } 100 | 101 | if (!Object.create) { 102 | Object.create = function(base) { 103 | function F() {}; 104 | F.prototype = base; 105 | return new F(); 106 | } 107 | } 108 | 109 | if (!Object.extend) { 110 | Object.extend = function(destination, source) { 111 | for (var property in source) { 112 | if (source.hasOwnProperty(property)) 113 | destination[property] = source[property]; 114 | } 115 | return destination; 116 | }; 117 | } 118 | 119 | var Class = { 120 | create: function(prototype, extensions) { 121 | var ctor = function() { if (this.initialize) return this.initialize.apply(this, arguments); } 122 | ctor.prototype = prototype || {}; // instance methods 123 | Object.extend(ctor, extensions || {}); // class methods 124 | return ctor; 125 | } 126 | } 127 | 128 | if (!window.requestAnimationFrame) {// http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 129 | window.requestAnimationFrame = window.webkitRequestAnimationFrame || 130 | window.mozRequestAnimationFrame || 131 | window.oRequestAnimationFrame || 132 | window.msRequestAnimationFrame || 133 | function(callback, element) { 134 | window.setTimeout(callback, 1000 / 60); 135 | } 136 | } 137 | 138 | Game = { 139 | 140 | run: function(gameFactory, cfg) { 141 | document.addEventListener('DOMContentLoaded', function() { 142 | window.game = gameFactory(); 143 | window.runner = new Game.Runner(window.game, cfg); 144 | }, false); 145 | }, 146 | 147 | //=========================================================================== 148 | // GAME RUNNER 149 | //=========================================================================== 150 | 151 | Runner: Class.create({ 152 | 153 | initialize: function(game, cfg) { 154 | this.game = game; 155 | this.cfg = (cfg = Object.extend(cfg || {}, (game.cfg && game.cfg.runner) || {})); 156 | this.fps = cfg.fps || 30; 157 | this.dstep = 1.0 / cfg.fps; 158 | this.frame = 0; 159 | this.canvas = $(cfg.id || 'canvas'); 160 | this.bounds = this.canvas.getBoundingClientRect(); 161 | this.width = cfg.width || this.canvas.offsetWidth; 162 | this.height = cfg.height || this.canvas.offsetHeight; 163 | this.canvas = this.canvas; 164 | this.canvas.width = this.width; 165 | this.canvas.height = this.height; 166 | this.ctx = this.canvas.getContext('2d'); 167 | game.run(this); 168 | if (to.bool(this.cfg.start)) 169 | this.start(); 170 | return this.game; 171 | }, 172 | 173 | timestamp: function() { return new Date().getTime(); }, 174 | 175 | start: function() { 176 | 177 | this.addEvents(); 178 | this.resetStats(); 179 | 180 | if (this.cfg.manual) 181 | return this.draw(); 182 | 183 | var timestamp = this.timestamp, 184 | start, middle, end, last = timestamp(), 185 | dt = 0.0, // time elapsed (seconds) 186 | stopping = false, // flag for stopping game loop 187 | self = this; // closure over this 188 | 189 | var step = function() { 190 | start = timestamp(); dt = self.update(dt + Math.min(1, (start - last)/1000.0)); // send dt as seconds, MAX of 1s (to avoid huge delta's when requestAnimationFrame put us in the background) 191 | middle = timestamp(); self.draw(); 192 | end = timestamp(); 193 | self.updateStats(middle - start, end - middle); 194 | last = start; 195 | if (!stopping) 196 | requestAnimationFrame(step); 197 | } 198 | this.stop = function() { stopping = true; } 199 | step(); 200 | 201 | }, 202 | 203 | update: function(dt) { 204 | while (dt >= this.dstep) { 205 | this.game.update(this.frame); 206 | this.frame = this.frame + 1; 207 | dt = dt - this.dstep; 208 | } 209 | return dt; 210 | }, 211 | 212 | manual: function() { 213 | if (this.cfg.manual) { 214 | var start = this.timestamp(); this.update(this.dstep); 215 | var middle = this.timestamp(); this.draw(); 216 | var end = this.timestamp(); 217 | this.updateStats(middle - start, end - middle); 218 | } 219 | }, 220 | 221 | draw: function() { 222 | this.ctx.save(); 223 | this.game.draw(this.ctx, this.frame); 224 | this.ctx.restore(); 225 | this.drawStats(); 226 | }, 227 | 228 | resetStats: function() { 229 | if (this.cfg.stats) { 230 | this.stats = new Stats(); 231 | this.stats.extra = { 232 | update: 0, 233 | draw: 0 234 | }; 235 | this.stats.domElement.id = 'stats'; 236 | this.canvas.parentNode.appendChild(this.stats.domElement); 237 | this.stats.domElement.appendChild($({ 238 | tag: 'div', 239 | 'class': 'extra', 240 | 'style': 'font-size: 8pt; position: absolute; top: -50px;', 241 | html: "
" 242 | })); 243 | this.stats.updateCounter = $(this.stats.domElement).down('div.extra span.update'); 244 | this.stats.drawCounter = $(this.stats.domElement).down('div.extra span.draw'); 245 | } 246 | }, 247 | 248 | updateStats: function(update, draw) { 249 | if (this.cfg.stats) { 250 | this.stats.update(); 251 | this.stats.extra.update = update ? Math.max(1, update) : this.stats.extra.update; 252 | this.stats.extra.draw = draw ? Math.max(1, draw) : this.stats.extra.draw; 253 | } 254 | }, 255 | 256 | drawStats: function() { 257 | if (this.cfg.stats) { 258 | this.stats.updateCounter.update("update: " + Math.round(this.stats.extra.update) + "ms"); 259 | this.stats.drawCounter.update( "draw: " + Math.round(this.stats.extra.draw) + "ms"); 260 | } 261 | }, 262 | 263 | addEvents: function() { 264 | var game = this.game; 265 | 266 | if (game.onfocus) { 267 | document.body.tabIndex = to.number(document.body.tabIndex, 0); // body needs tabIndex to recieve focus 268 | $(document.body).on('focus', function(ev) { game.onfocus(ev); }); 269 | } 270 | 271 | if (game.onclick) { 272 | this.canvas.on('click', function(ev) { game.onclick(ev); }); 273 | } 274 | 275 | if (game.onwheel) { 276 | this.canvas.on(ua.is.firefox ? "DOMMouseScroll" : "mousewheel", function(ev) { game.onwheel(Game.Event.mouseWheelDelta(ev), ev); }); 277 | } 278 | 279 | }, 280 | 281 | setSize: function(width, height) { 282 | this.width = this.canvas.width = width; 283 | this.height = this.canvas.height = height; 284 | } 285 | 286 | }), 287 | 288 | //=========================================================================== 289 | // UTILS 290 | //=========================================================================== 291 | 292 | qsValue: function(name, format) { 293 | var pattern = name + "=(" + (format || "\\w+") + ")", 294 | re = new RegExp(pattern), 295 | match = re.exec(location.search); 296 | return match ? match[1] : null; 297 | }, 298 | 299 | qsNumber: function(name, def) { 300 | var value = this.qsValue(name); 301 | return value ? to.number(value, def) : null; 302 | }, 303 | 304 | qsBool: function(name, def) { 305 | return to.bool(this.qsValue(name), def); 306 | }, 307 | 308 | storage: function() { 309 | try { 310 | return this.localStorage = this.localStorage || window.localStorage || {}; 311 | } 312 | catch(e) { // IE localStorage throws exceptions when using non-standard port (e.g. during development) 313 | return this.localStorage = {}; 314 | } 315 | }, 316 | 317 | createCanvas: function(width, height) { 318 | var canvas = document.createElement('canvas'); 319 | canvas.width = width; 320 | canvas.height = height; 321 | return canvas; 322 | }, 323 | 324 | renderToCanvas: function(width, height, render, canvas) { // http://kaioa.com/node/103 325 | canvas = canvas || this.createCanvas(width, height, canvas); 326 | render(canvas.getContext('2d')); 327 | return canvas; 328 | }, 329 | 330 | parseImage: function(image, callback) { 331 | var tx, ty, index, pixel, 332 | tw = image.width, 333 | th = image.height, 334 | canvas = Game.renderToCanvas(tw, th, function(ctx) { ctx.drawImage(image, 0, 0); }), 335 | ctx = canvas.getContext('2d'), 336 | data = ctx.getImageData(0, 0, tw, th).data, 337 | helpers = { 338 | valid: function(tx,ty) { return (tx >= 0) && (tx < tw) && (ty >= 0) && (ty < th); }, 339 | index: function(tx,ty) { return (tx + (ty*tw)) * 4; }, 340 | pixel: function(tx,ty) { var i = this.index(tx,ty); return this.valid(tx,ty) ? (data[i]<<16)+(data[i+1]<<8)+(data[i+2]) : null; } 341 | } 342 | 343 | for(ty = 0 ; ty < th ; ty++) 344 | for(tx = 0 ; tx < tw ; tx++) 345 | callback(tx, ty, helpers.pixel(tx,ty), helpers); 346 | }, 347 | 348 | createImage: function(url, options) { 349 | options = options || {}; 350 | var image = $({tag: 'img'}); 351 | if (options.onload) 352 | image.on('load', options.onload); 353 | image.src = url; 354 | return image; 355 | }, 356 | 357 | loadResources: function(images, sounds, callback) { /* load multiple images and sounds and callback when ALL have finished loading */ 358 | images = images || []; 359 | sounds = sounds || []; 360 | var count = images.length + sounds.length; 361 | var resources = { images: {}, sounds: {} }; 362 | if (count == 0) { 363 | callback(resources); 364 | } 365 | else { 366 | 367 | var done = false; 368 | var loaded = function() { 369 | if (!done) { 370 | done = true; // ensure only called once, either by onload, or by setTimeout 371 | callback(resources); 372 | } 373 | } 374 | 375 | var onload = function() { 376 | if (--count == 0) 377 | loaded(); 378 | }; 379 | 380 | for(var n = 0 ; n < images.length ; n++) { 381 | var image = images[n]; 382 | image = is.string(image) ? { id: image, url: image } : image; 383 | resources.images[image.id] = Game.createImage(image.url, { onload: onload }); 384 | } 385 | 386 | for(var n = 0 ; n < sounds.length ; n++) { 387 | var sound = sounds[n]; 388 | sound = is.string(sound) ? { id: sound, name: sound } : sound; 389 | resources.sounds[sound.id] = AudioFX(sound.name, sound, onload); 390 | } 391 | 392 | setTimeout(loaded, 15000); // need a timeout because HTML5 audio canplay event is VERY VERY FLAKEY (especially on slow connections) 393 | 394 | } 395 | } 396 | 397 | }; 398 | 399 | Game.PubSub = { 400 | 401 | enable: function(cfg, on) { 402 | 403 | var n, max; 404 | 405 | on.subscribe = function(event, callback) { 406 | this.subscribers = this.subscribers || {}; 407 | this.subscribers[event] = this.subscribers[event] || []; 408 | this.subscribers[event].push(callback); 409 | }, 410 | 411 | on.publish = function(event) { 412 | if (this.subscribers && this.subscribers[event]) { 413 | var subs = this.subscribers[event], 414 | args = [].slice.call(arguments, 1), 415 | n, max; 416 | for(n = 0, max = subs.length ; n < max ; n++) 417 | subs[n].apply(on, args); 418 | } 419 | } 420 | 421 | if (cfg) { 422 | for(n = 0, max = cfg.length ; n < max ; n++) 423 | on.subscribe(cfg[n].event, cfg[n].action); 424 | } 425 | 426 | } 427 | 428 | } 429 | //============================================================================= 430 | // Minimal DOM Library ($) 431 | //============================================================================= 432 | 433 | Game.Element = function() { 434 | 435 | var query = function(selector, context) { 436 | if (is.array(context)) 437 | return Sizzle.matches(selector, context); 438 | else 439 | return Sizzle(selector, context); 440 | }; 441 | 442 | var extend = function(obj) { 443 | if (is.array(obj)) { 444 | for(var n = 0, l = obj.length ; n < l ; n++) 445 | obj[n] = extend(obj[n]); 446 | } 447 | else if (!obj._extended) { 448 | Object.extend(obj, Game.Element.instanceMethods); 449 | } 450 | return obj; 451 | }; 452 | 453 | var on = function(ele, type, fn, capture) { ele.addEventListener(type, fn, capture); }; 454 | var un = function(ele, type, fn, capture) { ele.removeEventListener(type, fn, capture); }; 455 | 456 | var create = function(attributes) { 457 | var ele = document.createElement(attributes.tag); 458 | for (var name in attributes) { 459 | if (attributes.hasOwnProperty(name) && is.valid(attributes[name])) { 460 | switch(name) { 461 | case 'tag' : break; 462 | case 'html' : ele.innerHTML = attributes[name]; break; 463 | case 'text' : ele.appendChild(document.createTextNode(attributes[name])); break; 464 | case 'dom' : 465 | attributes[name] = is.array(attributes[name]) ? attributes[name] : [attributes[name]]; 466 | for (var n = 0 ; n < attributes[name].length ; n++) 467 | ele.appendChild(attributes[name][n]); 468 | break; 469 | case 'class': 470 | case 'klass': 471 | case 'className': 472 | ele.className = attributes[name]; 473 | break; 474 | case 'on': 475 | for(var ename in attributes[name]) 476 | on(ele, ename, attributes[name][ename]); 477 | break; 478 | default: 479 | ele.setAttribute(name, attributes[name]); 480 | break; 481 | } 482 | } 483 | } 484 | return ele; 485 | }; 486 | 487 | return { 488 | 489 | all: function(selector, context) { 490 | return extend(query(selector, context)); 491 | }, 492 | 493 | get: function(obj, context) { 494 | if (is.string(obj)) 495 | return extend(query("#" + obj, context)[0]); 496 | else if (is.element(obj) || is.window(obj) || is.document(obj)) 497 | return extend(obj); 498 | else if (is.event(obj)) 499 | return extend(obj.target || obj.srcElement); 500 | else if ((typeof obj == 'object') && obj.tag) 501 | return extend(create(obj)); 502 | else 503 | throw "not an appropriate type for DOM wrapping: " + ele; 504 | }, 505 | 506 | instanceMethods: { 507 | 508 | _extended: true, 509 | 510 | on: function(type, fn, capture) { on(this, type, fn, capture); return this; }, 511 | un: function(type, fn, capture) { un(this, type, fn, capture); return this; }, 512 | 513 | showIf: function(on) { if (on) this.show(); else this.hide(); }, 514 | show: function() { this.style.display = '' }, 515 | hide: function() { this.style.display = 'none'; }, 516 | visible: function() { return (this.style.display != 'none') && !this.fading; }, 517 | fade: function(amount) { this.style.opacity = amount; }, 518 | 519 | relations: function(property, includeSelf) { 520 | var result = includeSelf ? [this] : [], ele = this; 521 | while(ele = ele[property]) 522 | if (ele.nodeType == 1) 523 | result.push(ele); 524 | return extend(result); 525 | }, 526 | 527 | parent: function() { return extend(this.parentNode); }, 528 | ancestors: function(includeSelf) { return this.relations('parentNode', includeSelf); }, 529 | previousSiblings: function() { return this.relations('previousSibling'); }, 530 | nextSiblings: function() { return this.relations('nextSibling'); }, 531 | 532 | select: function(selector) { return Game.Element.all(selector, this); }, 533 | down: function(selector) { return Game.Element.all(selector, this)[0]; }, 534 | up: function(selector, includeSelf) { return Game.Element.all(selector, this.ancestors(includeSelf))[0]; }, 535 | prev: function(selector) { return Game.Element.all(selector, this.previousSiblings())[0]; }, 536 | next: function(selector) { return Game.Element.all(selector, this.nextSiblings())[0]; }, 537 | 538 | remove: function() { 539 | if (this.parentNode) 540 | this.parentNode.removeChild(this); 541 | return this; 542 | }, 543 | 544 | removeChildren: function() { // NOTE: can't use :clear because its in DOM level-1 and IE bitches if we try to provide our own 545 | while (this.childNodes.length > 0) 546 | this.removeChild(this.childNodes[0]); 547 | return this; 548 | }, 549 | 550 | update: function(content) { 551 | this.innerHTML = ""; 552 | this.append(content); 553 | return this; 554 | }, 555 | 556 | append: function(content) { 557 | if (is.string(content)) 558 | this.innerHTML += content; 559 | else if (is.array(content)) 560 | for(var n = 0 ; n < content.length ; n++) 561 | this.append(content[n]); 562 | else 563 | this.appendChild(Game.Element.get(content)); 564 | }, 565 | 566 | setClassName: function(name) { this.className = name; }, 567 | hasClassName: function(name) { return (new RegExp("(^|\s*)" + name + "(\s*|$)")).test(this.className) }, 568 | addClassName: function(name) { this.toggleClassName(name, true); }, 569 | removeClassName: function(name) { this.toggleClassName(name, false); }, 570 | toggleClassName: function(name, on) { 571 | var classes = this.className.split(' '); 572 | var n = classes.indexOf(name); 573 | on = (typeof on == 'undefined') ? (n < 0) : on; 574 | if (on && (n < 0)) 575 | classes.push(name); 576 | else if (!on && (n >= 0)) 577 | classes.splice(n, 1); 578 | this.className = classes.join(' '); 579 | }, 580 | 581 | fadeout: function(options) { 582 | options = options || {}; 583 | this.cancelFade(); 584 | this.fading = Animator.apply(this, 'opacity: 0', { duration: options.duration, onComplete: function() { 585 | this.hide(); 586 | this.fading = null; 587 | this.style.opacity = is.ie ? 1 : null; /* clear opacity after hiding, but IE doesn't allow clear so set to 1.0 for IE */ 588 | if (options.onComplete) 589 | options.onComplete(); 590 | }.bind(this) }); 591 | this.fading.play(); 592 | }, 593 | 594 | fadein: function(options) { 595 | options = options || {}; 596 | this.cancelFade(); 597 | this.style.opacity = 0; 598 | this.show(); 599 | this.fading = Animator.apply(this, 'opacity: 1', { duration: options.duration, onComplete: function() { 600 | this.fading = null; 601 | this.style.opacity = is.ie ? 1 : null; /* clear opacity after hiding, but IE doesn't allow clear so set to 1.0 for IE */ 602 | if (options.onComplete) 603 | options.onComplete(); 604 | }.bind(this) }); 605 | this.fading.play(); 606 | }, 607 | 608 | cancelFade: function() { 609 | if (this.fading) { 610 | this.fading.stop(); 611 | delete this.fading; 612 | } 613 | } 614 | 615 | } 616 | }; 617 | 618 | }(); 619 | 620 | $ = Game.Element.get; 621 | $$ = Game.Element.all; 622 | 623 | Game.Event = { 624 | 625 | stop: function(ev) { 626 | ev.preventDefault(); 627 | ev.cancelBubble = true; 628 | ev.returnValue = false; 629 | return false; 630 | }, 631 | 632 | canvasPos: function(ev, canvas) { 633 | var bbox = canvas.getBoundingClientRect(), 634 | x = (ev.clientX - bbox.left) * (canvas.width / bbox.width), 635 | y = (ev.clientY - bbox.top) * (canvas.height / bbox.height); 636 | return { x: x, y: y }; 637 | }, 638 | 639 | mouseWheelDelta: function(ev) { 640 | if (is.valid(ev.wheelDelta)) 641 | return ev.wheelDelta/120; 642 | else if (is.valid(ev.detail)) 643 | return -ev.detail/3; 644 | else 645 | return 0; 646 | } 647 | 648 | } 649 | 650 | Game.Key = { 651 | BACKSPACE: 8, 652 | TAB: 9, 653 | RETURN: 13, 654 | ESC: 27, 655 | SPACE: 32, 656 | END: 35, 657 | HOME: 36, 658 | LEFT: 37, 659 | UP: 38, 660 | RIGHT: 39, 661 | DOWN: 40, 662 | PAGEUP: 33, 663 | PAGEDOWN: 34, 664 | INSERT: 45, 665 | DELETE: 46, 666 | ZERO: 48, ONE: 49, TWO: 50, THREE: 51, FOUR: 52, FIVE: 53, SIX: 54, SEVEN: 55, EIGHT: 56, NINE: 57, 667 | A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71, H: 72, I: 73, J: 74, K: 75, L: 76, M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82, S: 83, T: 84, U: 85, V: 86, W: 87, X: 88, Y: 89, Z: 90, 668 | TILDA: 192, 669 | 670 | map: function(map, context, cfg) { 671 | cfg = cfg || {}; 672 | var ele = $(cfg.ele || document); 673 | var onkey = function(ev, keyCode, mode) { 674 | var n, k, i; 675 | if ((ele === document) || ele.visible()) { 676 | for(n = 0 ; n < map.length ; ++n) { 677 | k = map[n]; 678 | k.mode = k.mode || 'up'; 679 | if (Game.Key.match(k, keyCode, mode, context)) { 680 | k.action.call(context, keyCode, ev.shiftKey); 681 | return Game.Event.stop(ev); 682 | } 683 | } 684 | } 685 | }; 686 | ele.on('keydown', function(ev) { return onkey(ev, ev.keyCode, 'down'); }); 687 | ele.on('keyup', function(ev) { return onkey(ev, ev.keyCode, 'up'); }); 688 | }, 689 | 690 | match: function(map, keyCode, mode, context) { 691 | if (map.mode === mode) { 692 | if (!map.state || !context || (map.state === context.current) || (is.array(map.state) && map.state.indexOf(context.current) >= 0)) { 693 | if ((map.key === keyCode) || (map.keys && (map.keys.indexOf(keyCode) >= 0))) { 694 | return true; 695 | } 696 | } 697 | } 698 | return false; 699 | } 700 | 701 | }; 702 | 703 | 704 | Game.Math = { 705 | 706 | minmax: function(x, min, max) { 707 | return Math.max(min, Math.min(max, x)); 708 | }, 709 | 710 | random: function(min, max) { 711 | return (min + (Math.random() * (max - min))); 712 | }, 713 | 714 | randomInt: function(min, max) { 715 | return Math.round(Game.Math.random(min, max)); 716 | }, 717 | 718 | randomChoice: function(choices) { 719 | return choices[Math.round(Game.Math.random(0, choices.length-1))]; 720 | }, 721 | 722 | randomBool: function() { 723 | return Game.randomChoice([true, false]); 724 | }, 725 | 726 | between: function(x, from, to) { 727 | return (is.valid(x) && (from <= x) && (x <= to)); 728 | }, 729 | 730 | overlap: function(x1, y1, w1, h1, x2, y2, w2, h2) { 731 | return !(((x1 + w1 - 1) < x2) || 732 | ((x2 + w2 - 1) < x1) || 733 | ((y1 + h1 - 1) < y2) || 734 | ((y2 + h2 - 1) < y1)) 735 | } 736 | 737 | } 738 | 739 | -------------------------------------------------------------------------------- /js/game/base.js: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // feature detection 3 | //============================================================================= 4 | ua = function() { 5 | 6 | var ua = navigator.userAgent.toLowerCase(); // should avoid user agent sniffing... but sometimes you just gotta do what you gotta do 7 | var key = ((ua.indexOf("opera") > -1) ? "opera" : null); 8 | key = key || ((ua.indexOf("firefox") > -1) ? "firefox" : null); 9 | key = key || ((ua.indexOf("chrome") > -1) ? "chrome" : null); 10 | key = key || ((ua.indexOf("safari") > -1) ? "safari" : null); 11 | key = key || ((ua.indexOf("msie") > -1) ? "ie" : null); 12 | 13 | try { 14 | var re = (key == "ie") ? "msie ([\\d\\.]*)" : key + "\\/([\\d\\.]*)" 15 | var matches = ua.match(new RegExp(re, "i")); 16 | var version = matches ? matches[1] : null; 17 | } catch (e) {} 18 | 19 | return { 20 | full: ua, 21 | name: key + (version ? " " + version : ""), 22 | version: version, 23 | major: version ? parseInt(version) : null, 24 | is: { 25 | firefox: (key == "firefox"), 26 | chrome: (key == "chrome"), 27 | safari: (key == "safari"), 28 | opera: (key == "opera"), 29 | ie: (key == "ie") 30 | }, 31 | has: { 32 | audio: AudioFX && AudioFX.enabled, 33 | canvas: (document.createElement('canvas').getContext), 34 | touch: ('ontouchstart' in window) 35 | } 36 | } 37 | }(); 38 | 39 | //============================================================================= 40 | // type detection 41 | //============================================================================= 42 | 43 | is = { 44 | 'string': function(obj) { return (typeof obj === 'string'); }, 45 | 'number': function(obj) { return (typeof obj === 'number'); }, 46 | 'bool': function(obj) { return (typeof obj === 'boolean'); }, 47 | 'array': function(obj) { return (obj instanceof Array); }, 48 | 'undefined': function(obj) { return (typeof obj === 'undefined'); }, 49 | 'func': function(obj) { return (typeof obj === 'function'); }, 50 | 'null': function(obj) { return (obj === null); }, 51 | 'notNull': function(obj) { return (obj !== null); }, 52 | 'invalid': function(obj) { return ( is['null'](obj) || is.undefined(obj)); }, 53 | 'valid': function(obj) { return (!is['null'](obj) && !is.undefined(obj)); }, 54 | 'emptyString': function(obj) { return (is.string(obj) && (obj.length == 0)); }, 55 | 'nonEmptyString': function(obj) { return (is.string(obj) && (obj.length > 0)); }, 56 | 'emptyArray': function(obj) { return (is.array(obj) && (obj.length == 0)); }, 57 | 'nonEmptyArray': function(obj) { return (is.array(obj) && (obj.length > 0)); }, 58 | 'document': function(obj) { return (obj === document); }, 59 | 'window': function(obj) { return (obj === window); }, 60 | 'element': function(obj) { return (obj instanceof HTMLElement); }, 61 | 'event': function(obj) { return (obj instanceof Event); }, 62 | 'link': function(obj) { return (is.element(obj) && (obj.tagName == 'A')); } 63 | } 64 | 65 | //============================================================================= 66 | // type coersion 67 | //============================================================================= 68 | 69 | to = { 70 | 'bool': function(obj, def) { if (is.valid(obj)) return ((obj == 1) || (obj == true) || (obj == "1") || (obj == "y") || (obj == "Y") || (obj.toString().toLowerCase() == "true") || (obj.toString().toLowerCase() == 'yes')); else return (is.bool(def) ? def : false); }, 71 | 'number': function(obj, def) { if (is.valid(obj)) { var x = parseFloat(obj); if (!isNaN(x)) return x; } return (is.number(def) ? def : 0); }, 72 | 'string': function(obj, def) { if (is.valid(obj)) return obj.toString(); return (is.string(def) ? def : ''); } 73 | } 74 | 75 | //============================================================================= 76 | // 77 | // Compatibility for older browsers (compatibility: http://kangax.github.com/es5-compat-table/) 78 | // 79 | // Function.bind: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind 80 | // Object.create: http://javascript.crockford.com/prototypal.html 81 | // Object.extend: (defacto standard like jquery $.extend or prototype's Object.extend) 82 | // Class.create: create a simple javascript 'class' (a constructor function with a prototype and optional class methods) 83 | // 84 | //============================================================================= 85 | 86 | if (!Function.prototype.bind) { 87 | Function.prototype.bind = function(obj) { 88 | var slice = [].slice, 89 | args = slice.call(arguments, 1), 90 | self = this, 91 | nop = function () {}, 92 | bound = function () { 93 | return self.apply(this instanceof nop ? this : (obj || {}), args.concat(slice.call(arguments))); 94 | }; 95 | nop.prototype = self.prototype; 96 | bound.prototype = new nop(); 97 | return bound; 98 | }; 99 | } 100 | 101 | if (!Object.create) { 102 | Object.create = function(base) { 103 | function F() {}; 104 | F.prototype = base; 105 | return new F(); 106 | } 107 | } 108 | 109 | if (!Object.extend) { 110 | Object.extend = function(destination, source) { 111 | for (var property in source) { 112 | if (source.hasOwnProperty(property)) 113 | destination[property] = source[property]; 114 | } 115 | return destination; 116 | }; 117 | } 118 | 119 | var Class = { 120 | create: function(prototype, extensions) { 121 | var ctor = function() { if (this.initialize) return this.initialize.apply(this, arguments); } 122 | ctor.prototype = prototype || {}; // instance methods 123 | Object.extend(ctor, extensions || {}); // class methods 124 | return ctor; 125 | } 126 | } 127 | 128 | if (!window.requestAnimationFrame) {// http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 129 | window.requestAnimationFrame = window.webkitRequestAnimationFrame || 130 | window.mozRequestAnimationFrame || 131 | window.oRequestAnimationFrame || 132 | window.msRequestAnimationFrame || 133 | function(callback, element) { 134 | window.setTimeout(callback, 1000 / 60); 135 | } 136 | } 137 | 138 | -------------------------------------------------------------------------------- /js/game/dom.js: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // Minimal DOM Library ($) 3 | //============================================================================= 4 | 5 | Game.Element = function() { 6 | 7 | var query = function(selector, context) { 8 | if (is.array(context)) 9 | return Sizzle.matches(selector, context); 10 | else 11 | return Sizzle(selector, context); 12 | }; 13 | 14 | var extend = function(obj) { 15 | if (is.array(obj)) { 16 | for(var n = 0, l = obj.length ; n < l ; n++) 17 | obj[n] = extend(obj[n]); 18 | } 19 | else if (!obj._extended) { 20 | Object.extend(obj, Game.Element.instanceMethods); 21 | } 22 | return obj; 23 | }; 24 | 25 | var on = function(ele, type, fn, capture) { ele.addEventListener(type, fn, capture); }; 26 | var un = function(ele, type, fn, capture) { ele.removeEventListener(type, fn, capture); }; 27 | 28 | var create = function(attributes) { 29 | var ele = document.createElement(attributes.tag); 30 | for (var name in attributes) { 31 | if (attributes.hasOwnProperty(name) && is.valid(attributes[name])) { 32 | switch(name) { 33 | case 'tag' : break; 34 | case 'html' : ele.innerHTML = attributes[name]; break; 35 | case 'text' : ele.appendChild(document.createTextNode(attributes[name])); break; 36 | case 'dom' : 37 | attributes[name] = is.array(attributes[name]) ? attributes[name] : [attributes[name]]; 38 | for (var n = 0 ; n < attributes[name].length ; n++) 39 | ele.appendChild(attributes[name][n]); 40 | break; 41 | case 'class': 42 | case 'klass': 43 | case 'className': 44 | ele.className = attributes[name]; 45 | break; 46 | case 'on': 47 | for(var ename in attributes[name]) 48 | on(ele, ename, attributes[name][ename]); 49 | break; 50 | default: 51 | ele.setAttribute(name, attributes[name]); 52 | break; 53 | } 54 | } 55 | } 56 | return ele; 57 | }; 58 | 59 | return { 60 | 61 | all: function(selector, context) { 62 | return extend(query(selector, context)); 63 | }, 64 | 65 | get: function(obj, context) { 66 | if (is.string(obj)) 67 | return extend(query("#" + obj, context)[0]); 68 | else if (is.element(obj) || is.window(obj) || is.document(obj)) 69 | return extend(obj); 70 | else if (is.event(obj)) 71 | return extend(obj.target || obj.srcElement); 72 | else if ((typeof obj == 'object') && obj.tag) 73 | return extend(create(obj)); 74 | else 75 | throw "not an appropriate type for DOM wrapping: " + ele; 76 | }, 77 | 78 | instanceMethods: { 79 | 80 | _extended: true, 81 | 82 | on: function(type, fn, capture) { on(this, type, fn, capture); return this; }, 83 | un: function(type, fn, capture) { un(this, type, fn, capture); return this; }, 84 | 85 | showIf: function(on) { if (on) this.show(); else this.hide(); }, 86 | show: function() { this.style.display = '' }, 87 | hide: function() { this.style.display = 'none'; }, 88 | visible: function() { return (this.style.display != 'none') && !this.fading; }, 89 | fade: function(amount) { this.style.opacity = amount; }, 90 | 91 | relations: function(property, includeSelf) { 92 | var result = includeSelf ? [this] : [], ele = this; 93 | while(ele = ele[property]) 94 | if (ele.nodeType == 1) 95 | result.push(ele); 96 | return extend(result); 97 | }, 98 | 99 | parent: function() { return extend(this.parentNode); }, 100 | ancestors: function(includeSelf) { return this.relations('parentNode', includeSelf); }, 101 | previousSiblings: function() { return this.relations('previousSibling'); }, 102 | nextSiblings: function() { return this.relations('nextSibling'); }, 103 | 104 | select: function(selector) { return Game.Element.all(selector, this); }, 105 | down: function(selector) { return Game.Element.all(selector, this)[0]; }, 106 | up: function(selector, includeSelf) { return Game.Element.all(selector, this.ancestors(includeSelf))[0]; }, 107 | prev: function(selector) { return Game.Element.all(selector, this.previousSiblings())[0]; }, 108 | next: function(selector) { return Game.Element.all(selector, this.nextSiblings())[0]; }, 109 | 110 | remove: function() { 111 | if (this.parentNode) 112 | this.parentNode.removeChild(this); 113 | return this; 114 | }, 115 | 116 | removeChildren: function() { // NOTE: can't use :clear because its in DOM level-1 and IE bitches if we try to provide our own 117 | while (this.childNodes.length > 0) 118 | this.removeChild(this.childNodes[0]); 119 | return this; 120 | }, 121 | 122 | update: function(content) { 123 | this.innerHTML = ""; 124 | this.append(content); 125 | return this; 126 | }, 127 | 128 | append: function(content) { 129 | if (is.string(content)) 130 | this.innerHTML += content; 131 | else if (is.array(content)) 132 | for(var n = 0 ; n < content.length ; n++) 133 | this.append(content[n]); 134 | else 135 | this.appendChild(Game.Element.get(content)); 136 | }, 137 | 138 | setClassName: function(name) { this.className = name; }, 139 | hasClassName: function(name) { return (new RegExp("(^|\s*)" + name + "(\s*|$)")).test(this.className) }, 140 | addClassName: function(name) { this.toggleClassName(name, true); }, 141 | removeClassName: function(name) { this.toggleClassName(name, false); }, 142 | toggleClassName: function(name, on) { 143 | var classes = this.className.split(' '); 144 | var n = classes.indexOf(name); 145 | on = (typeof on == 'undefined') ? (n < 0) : on; 146 | if (on && (n < 0)) 147 | classes.push(name); 148 | else if (!on && (n >= 0)) 149 | classes.splice(n, 1); 150 | this.className = classes.join(' '); 151 | }, 152 | 153 | fadeout: function(options) { 154 | options = options || {}; 155 | this.cancelFade(); 156 | this.fading = Animator.apply(this, 'opacity: 0', { duration: options.duration, onComplete: function() { 157 | this.hide(); 158 | this.fading = null; 159 | this.style.opacity = is.ie ? 1 : null; /* clear opacity after hiding, but IE doesn't allow clear so set to 1.0 for IE */ 160 | if (options.onComplete) 161 | options.onComplete(); 162 | }.bind(this) }); 163 | this.fading.play(); 164 | }, 165 | 166 | fadein: function(options) { 167 | options = options || {}; 168 | this.cancelFade(); 169 | this.style.opacity = 0; 170 | this.show(); 171 | this.fading = Animator.apply(this, 'opacity: 1', { duration: options.duration, onComplete: function() { 172 | this.fading = null; 173 | this.style.opacity = is.ie ? 1 : null; /* clear opacity after hiding, but IE doesn't allow clear so set to 1.0 for IE */ 174 | if (options.onComplete) 175 | options.onComplete(); 176 | }.bind(this) }); 177 | this.fading.play(); 178 | }, 179 | 180 | cancelFade: function() { 181 | if (this.fading) { 182 | this.fading.stop(); 183 | delete this.fading; 184 | } 185 | } 186 | 187 | } 188 | }; 189 | 190 | }(); 191 | 192 | $ = Game.Element.get; 193 | $$ = Game.Element.all; 194 | 195 | Game.Event = { 196 | 197 | stop: function(ev) { 198 | ev.preventDefault(); 199 | ev.cancelBubble = true; 200 | ev.returnValue = false; 201 | return false; 202 | }, 203 | 204 | canvasPos: function(ev, canvas) { 205 | var bbox = canvas.getBoundingClientRect(), 206 | x = (ev.clientX - bbox.left) * (canvas.width / bbox.width), 207 | y = (ev.clientY - bbox.top) * (canvas.height / bbox.height); 208 | return { x: x, y: y }; 209 | }, 210 | 211 | mouseWheelDelta: function(ev) { 212 | if (is.valid(ev.wheelDelta)) 213 | return ev.wheelDelta/120; 214 | else if (is.valid(ev.detail)) 215 | return -ev.detail/3; 216 | else 217 | return 0; 218 | } 219 | 220 | } 221 | 222 | -------------------------------------------------------------------------------- /js/game/game.js: -------------------------------------------------------------------------------- 1 | Game = { 2 | 3 | run: function(gameFactory, cfg) { 4 | document.addEventListener('DOMContentLoaded', function() { 5 | window.game = gameFactory(); 6 | window.runner = new Game.Runner(window.game, cfg); 7 | }, false); 8 | }, 9 | 10 | //=========================================================================== 11 | // GAME RUNNER 12 | //=========================================================================== 13 | 14 | Runner: Class.create({ 15 | 16 | initialize: function(game, cfg) { 17 | this.game = game; 18 | this.cfg = (cfg = Object.extend(cfg || {}, (game.cfg && game.cfg.runner) || {})); 19 | this.fps = cfg.fps || 30; 20 | this.dstep = 1.0 / cfg.fps; 21 | this.frame = 0; 22 | this.canvas = $(cfg.id || 'canvas'); 23 | this.bounds = this.canvas.getBoundingClientRect(); 24 | this.width = cfg.width || this.canvas.offsetWidth; 25 | this.height = cfg.height || this.canvas.offsetHeight; 26 | this.canvas = this.canvas; 27 | this.canvas.width = this.width; 28 | this.canvas.height = this.height; 29 | this.ctx = this.canvas.getContext('2d'); 30 | game.run(this); 31 | if (to.bool(this.cfg.start)) 32 | this.start(); 33 | return this.game; 34 | }, 35 | 36 | timestamp: function() { return new Date().getTime(); }, 37 | 38 | start: function() { 39 | 40 | this.addEvents(); 41 | this.resetStats(); 42 | 43 | if (this.cfg.manual) 44 | return this.draw(); 45 | 46 | var timestamp = this.timestamp, 47 | start, middle, end, last = timestamp(), 48 | dt = 0.0, // time elapsed (seconds) 49 | stopping = false, // flag for stopping game loop 50 | self = this; // closure over this 51 | 52 | var step = function() { 53 | start = timestamp(); dt = self.update(dt + Math.min(1, (start - last)/1000.0)); // send dt as seconds, MAX of 1s (to avoid huge delta's when requestAnimationFrame put us in the background) 54 | middle = timestamp(); self.draw(); 55 | end = timestamp(); 56 | self.updateStats(middle - start, end - middle); 57 | last = start; 58 | if (!stopping) 59 | requestAnimationFrame(step); 60 | } 61 | this.stop = function() { stopping = true; } 62 | step(); 63 | 64 | }, 65 | 66 | update: function(dt) { 67 | while (dt >= this.dstep) { 68 | this.game.update(this.frame); 69 | this.frame = this.frame + 1; 70 | dt = dt - this.dstep; 71 | } 72 | return dt; 73 | }, 74 | 75 | manual: function() { 76 | if (this.cfg.manual) { 77 | var start = this.timestamp(); this.update(this.dstep); 78 | var middle = this.timestamp(); this.draw(); 79 | var end = this.timestamp(); 80 | this.updateStats(middle - start, end - middle); 81 | } 82 | }, 83 | 84 | draw: function() { 85 | this.ctx.save(); 86 | this.game.draw(this.ctx, this.frame); 87 | this.ctx.restore(); 88 | this.drawStats(); 89 | }, 90 | 91 | resetStats: function() { 92 | if (this.cfg.stats) { 93 | this.stats = new Stats(); 94 | this.stats.extra = { 95 | update: 0, 96 | draw: 0 97 | }; 98 | this.stats.domElement.id = 'stats'; 99 | this.canvas.parentNode.appendChild(this.stats.domElement); 100 | this.stats.domElement.appendChild($({ 101 | tag: 'div', 102 | 'class': 'extra', 103 | 'style': 'font-size: 8pt; position: absolute; top: -50px;', 104 | html: "
" 105 | })); 106 | this.stats.updateCounter = $(this.stats.domElement).down('div.extra span.update'); 107 | this.stats.drawCounter = $(this.stats.domElement).down('div.extra span.draw'); 108 | } 109 | }, 110 | 111 | updateStats: function(update, draw) { 112 | if (this.cfg.stats) { 113 | this.stats.update(); 114 | this.stats.extra.update = update ? Math.max(1, update) : this.stats.extra.update; 115 | this.stats.extra.draw = draw ? Math.max(1, draw) : this.stats.extra.draw; 116 | } 117 | }, 118 | 119 | drawStats: function() { 120 | if (this.cfg.stats) { 121 | this.stats.updateCounter.update("update: " + Math.round(this.stats.extra.update) + "ms"); 122 | this.stats.drawCounter.update( "draw: " + Math.round(this.stats.extra.draw) + "ms"); 123 | } 124 | }, 125 | 126 | addEvents: function() { 127 | var game = this.game; 128 | 129 | if (game.onfocus) { 130 | document.body.tabIndex = to.number(document.body.tabIndex, 0); // body needs tabIndex to recieve focus 131 | $(document.body).on('focus', function(ev) { game.onfocus(ev); }); 132 | } 133 | 134 | if (game.onclick) { 135 | this.canvas.on('click', function(ev) { game.onclick(ev); }); 136 | } 137 | 138 | if (game.onwheel) { 139 | this.canvas.on(ua.is.firefox ? "DOMMouseScroll" : "mousewheel", function(ev) { game.onwheel(Game.Event.mouseWheelDelta(ev), ev); }); 140 | } 141 | 142 | }, 143 | 144 | setSize: function(width, height) { 145 | this.width = this.canvas.width = width; 146 | this.height = this.canvas.height = height; 147 | } 148 | 149 | }), 150 | 151 | //=========================================================================== 152 | // UTILS 153 | //=========================================================================== 154 | 155 | qsValue: function(name, format) { 156 | var pattern = name + "=(" + (format || "\\w+") + ")", 157 | re = new RegExp(pattern), 158 | match = re.exec(location.search); 159 | return match ? match[1] : null; 160 | }, 161 | 162 | qsNumber: function(name, def) { 163 | var value = this.qsValue(name); 164 | return value ? to.number(value, def) : null; 165 | }, 166 | 167 | qsBool: function(name, def) { 168 | return to.bool(this.qsValue(name), def); 169 | }, 170 | 171 | storage: function() { 172 | try { 173 | return this.localStorage = this.localStorage || window.localStorage || {}; 174 | } 175 | catch(e) { // IE localStorage throws exceptions when using non-standard port (e.g. during development) 176 | return this.localStorage = {}; 177 | } 178 | }, 179 | 180 | createCanvas: function(width, height) { 181 | var canvas = document.createElement('canvas'); 182 | canvas.width = width; 183 | canvas.height = height; 184 | return canvas; 185 | }, 186 | 187 | renderToCanvas: function(width, height, render, canvas) { // http://kaioa.com/node/103 188 | canvas = canvas || this.createCanvas(width, height, canvas); 189 | render(canvas.getContext('2d')); 190 | return canvas; 191 | }, 192 | 193 | parseImage: function(image, callback) { 194 | var tx, ty, index, pixel, 195 | tw = image.width, 196 | th = image.height, 197 | canvas = Game.renderToCanvas(tw, th, function(ctx) { ctx.drawImage(image, 0, 0); }), 198 | ctx = canvas.getContext('2d'), 199 | data = ctx.getImageData(0, 0, tw, th).data, 200 | helpers = { 201 | valid: function(tx,ty) { return (tx >= 0) && (tx < tw) && (ty >= 0) && (ty < th); }, 202 | index: function(tx,ty) { return (tx + (ty*tw)) * 4; }, 203 | pixel: function(tx,ty) { var i = this.index(tx,ty); return this.valid(tx,ty) ? (data[i]<<16)+(data[i+1]<<8)+(data[i+2]) : null; } 204 | } 205 | 206 | for(ty = 0 ; ty < th ; ty++) 207 | for(tx = 0 ; tx < tw ; tx++) 208 | callback(tx, ty, helpers.pixel(tx,ty), helpers); 209 | }, 210 | 211 | createImage: function(url, options) { 212 | options = options || {}; 213 | var image = $({tag: 'img'}); 214 | if (options.onload) 215 | image.on('load', options.onload); 216 | image.src = url; 217 | return image; 218 | }, 219 | 220 | loadResources: function(images, sounds, callback) { /* load multiple images and sounds and callback when ALL have finished loading */ 221 | images = images || []; 222 | sounds = sounds || []; 223 | var count = images.length + sounds.length; 224 | var resources = { images: {}, sounds: {} }; 225 | if (count == 0) { 226 | callback(resources); 227 | } 228 | else { 229 | 230 | var done = false; 231 | var loaded = function() { 232 | if (!done) { 233 | done = true; // ensure only called once, either by onload, or by setTimeout 234 | callback(resources); 235 | } 236 | } 237 | 238 | var onload = function() { 239 | if (--count == 0) 240 | loaded(); 241 | }; 242 | 243 | for(var n = 0 ; n < images.length ; n++) { 244 | var image = images[n]; 245 | image = is.string(image) ? { id: image, url: image } : image; 246 | resources.images[image.id] = Game.createImage(image.url, { onload: onload }); 247 | } 248 | 249 | for(var n = 0 ; n < sounds.length ; n++) { 250 | var sound = sounds[n]; 251 | sound = is.string(sound) ? { id: sound, name: sound } : sound; 252 | resources.sounds[sound.id] = AudioFX(sound.name, sound, onload); 253 | } 254 | 255 | setTimeout(loaded, 15000); // need a timeout because HTML5 audio canplay event is VERY VERY FLAKEY (especially on slow connections) 256 | 257 | } 258 | } 259 | 260 | }; 261 | 262 | -------------------------------------------------------------------------------- /js/game/key.js: -------------------------------------------------------------------------------- 1 | Game.Key = { 2 | BACKSPACE: 8, 3 | TAB: 9, 4 | RETURN: 13, 5 | ESC: 27, 6 | SPACE: 32, 7 | END: 35, 8 | HOME: 36, 9 | LEFT: 37, 10 | UP: 38, 11 | RIGHT: 39, 12 | DOWN: 40, 13 | PAGEUP: 33, 14 | PAGEDOWN: 34, 15 | INSERT: 45, 16 | DELETE: 46, 17 | ZERO: 48, ONE: 49, TWO: 50, THREE: 51, FOUR: 52, FIVE: 53, SIX: 54, SEVEN: 55, EIGHT: 56, NINE: 57, 18 | A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71, H: 72, I: 73, J: 74, K: 75, L: 76, M: 77, N: 78, O: 79, P: 80, Q: 81, R: 82, S: 83, T: 84, U: 85, V: 86, W: 87, X: 88, Y: 89, Z: 90, 19 | TILDA: 192, 20 | 21 | map: function(map, context, cfg) { 22 | cfg = cfg || {}; 23 | var ele = $(cfg.ele || document); 24 | var onkey = function(ev, keyCode, mode) { 25 | var n, k, i; 26 | if ((ele === document) || ele.visible()) { 27 | for(n = 0 ; n < map.length ; ++n) { 28 | k = map[n]; 29 | k.mode = k.mode || 'up'; 30 | if (Game.Key.match(k, keyCode, mode, context)) { 31 | k.action.call(context, keyCode, ev.shiftKey); 32 | return Game.Event.stop(ev); 33 | } 34 | } 35 | } 36 | }; 37 | ele.on('keydown', function(ev) { return onkey(ev, ev.keyCode, 'down'); }); 38 | ele.on('keyup', function(ev) { return onkey(ev, ev.keyCode, 'up'); }); 39 | }, 40 | 41 | match: function(map, keyCode, mode, context) { 42 | if (map.mode === mode) { 43 | if (!map.state || !context || (map.state === context.current) || (is.array(map.state) && map.state.indexOf(context.current) >= 0)) { 44 | if ((map.key === keyCode) || (map.keys && (map.keys.indexOf(keyCode) >= 0))) { 45 | return true; 46 | } 47 | } 48 | } 49 | return false; 50 | } 51 | 52 | }; 53 | 54 | 55 | -------------------------------------------------------------------------------- /js/game/math.js: -------------------------------------------------------------------------------- 1 | Game.Math = { 2 | 3 | minmax: function(x, min, max) { 4 | return Math.max(min, Math.min(max, x)); 5 | }, 6 | 7 | random: function(min, max) { 8 | return (min + (Math.random() * (max - min))); 9 | }, 10 | 11 | randomInt: function(min, max) { 12 | return Math.round(Game.Math.random(min, max)); 13 | }, 14 | 15 | randomChoice: function(choices) { 16 | return choices[Math.round(Game.Math.random(0, choices.length-1))]; 17 | }, 18 | 19 | randomBool: function() { 20 | return Game.randomChoice([true, false]); 21 | }, 22 | 23 | between: function(x, from, to) { 24 | return (is.valid(x) && (from <= x) && (x <= to)); 25 | }, 26 | 27 | overlap: function(x1, y1, w1, h1, x2, y2, w2, h2) { 28 | return !(((x1 + w1 - 1) < x2) || 29 | ((x2 + w2 - 1) < x1) || 30 | ((y1 + h1 - 1) < y2) || 31 | ((y2 + h2 - 1) < y1)) 32 | } 33 | 34 | } 35 | 36 | -------------------------------------------------------------------------------- /js/game/pubsub.js: -------------------------------------------------------------------------------- 1 | Game.PubSub = { 2 | 3 | enable: function(cfg, on) { 4 | 5 | var n, max; 6 | 7 | on.subscribe = function(event, callback) { 8 | this.subscribers = this.subscribers || {}; 9 | this.subscribers[event] = this.subscribers[event] || []; 10 | this.subscribers[event].push(callback); 11 | }, 12 | 13 | on.publish = function(event) { 14 | if (this.subscribers && this.subscribers[event]) { 15 | var subs = this.subscribers[event], 16 | args = [].slice.call(arguments, 1), 17 | n, max; 18 | for(n = 0, max = subs.length ; n < max ; n++) 19 | subs[n].apply(on, args); 20 | } 21 | } 22 | 23 | if (cfg) { 24 | for(n = 0, max = cfg.length ; n < max ; n++) 25 | on.subscribe(cfg[n].event, cfg[n].action); 26 | } 27 | 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /js/game/vendor/animator.js: -------------------------------------------------------------------------------- 1 | /* 2 | Animator.js 1.1.11 3 | 4 | This library is released under the BSD license: 5 | 6 | Copyright (c) 2006, Bernard Sumption. All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are met: 10 | 11 | Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. Redistributions in binary 13 | form must reproduce the above copyright notice, this list of conditions and 14 | the following disclaimer in the documentation and/or other materials 15 | provided with the distribution. Neither the name BernieCode nor 16 | the names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 29 | DAMAGE. 30 | 31 | */ 32 | 33 | 34 | // Applies a sequence of numbers between 0 and 1 to a number of subjects 35 | // construct - see setOptions for parameters 36 | function Animator(options) { 37 | this.setOptions(options); 38 | var _this = this; 39 | this.timerDelegate = function(){_this.onTimerEvent()}; 40 | this.subjects = []; 41 | this.target = 0; 42 | this.state = 0; 43 | this.lastTime = null; 44 | }; 45 | Animator.prototype = { 46 | // apply defaults 47 | setOptions: function(options) { 48 | this.options = Animator.applyDefaults({ 49 | repeat: false, 50 | interval: 20, // time between animation frames 51 | duration: 400, // length of animation 52 | onComplete: function(){}, 53 | onStep: function(){}, 54 | transition: Animator.tx.easeInOut 55 | }, options); 56 | }, 57 | // animate from the current state to provided value 58 | seekTo: function(to) { 59 | this.seekFromTo(this.state, to); 60 | }, 61 | // animate from the current state to provided value 62 | seekFromTo: function(from, to) { 63 | this.target = Math.max(0, Math.min(1, to)); 64 | this.state = Math.max(0, Math.min(1, from)); 65 | this.lastTime = new Date().getTime(); 66 | if (!this.intervalId) { 67 | this.intervalId = window.setInterval(this.timerDelegate, this.options.interval); 68 | } 69 | }, 70 | // animate from the current state to provided value 71 | jumpTo: function(to) { 72 | this.target = this.state = Math.max(0, Math.min(1, to)); 73 | this.propagate(); 74 | }, 75 | // seek to the opposite of the current target 76 | toggle: function() { 77 | this.seekTo(1 - this.target); 78 | }, 79 | // add a function or an object with a method setState(state) that will be called with a number 80 | // between 0 and 1 on each frame of the animation 81 | addSubject: function(subject) { 82 | this.subjects[this.subjects.length] = subject; 83 | return this; 84 | }, 85 | // remove all subjects 86 | clearSubjects: function() { 87 | this.subjects = []; 88 | }, 89 | // forward the current state to the animation subjects 90 | propagate: function() { 91 | var value = this.options.transition(this.state); 92 | for (var i=0; i= Math.abs(this.state - this.target)) { 107 | this.state = this.target; 108 | } else { 109 | this.state += movement; 110 | } 111 | 112 | try { 113 | this.propagate(); 114 | } finally { 115 | this.options.onStep.call(this); 116 | if (this.target == this.state) { 117 | if (this.options.repeat === 'toggle') 118 | this.toggle(); 119 | else if (this.options.repeat) 120 | this.play(); 121 | else 122 | this.stop(); 123 | } 124 | } 125 | }, 126 | stop: function() { 127 | if (this.intervalId) { 128 | window.clearInterval(this.intervalId); 129 | this.intervalId = null; 130 | this.options.onComplete.call(this); 131 | } 132 | }, 133 | // shortcuts 134 | play: function() {this.seekFromTo(0, 1)}, 135 | reverse: function() {this.seekFromTo(1, 0)}, 136 | // return a string describing this Animator, for debugging 137 | inspect: function() { 138 | var str = "# 20) return; 279 | } 280 | }, 281 | getStyle: function(state) { 282 | state = this.from + ((this.to - this.from) * state); 283 | if (this.property == 'opacity') return state; 284 | return Math.round(state) + this.units; 285 | }, 286 | inspect: function() { 287 | return "\t" + this.property + "(" + this.from + this.units + " to " + this.to + this.units + ")\n"; 288 | } 289 | } 290 | 291 | // animates a colour based style property between two hex values 292 | function ColorStyleSubject(els, property, from, to) { 293 | this.els = Animator.makeArrayOfElements(els); 294 | this.property = Animator.camelize(property); 295 | this.to = this.expandColor(to); 296 | this.from = this.expandColor(from); 297 | this.origFrom = from; 298 | this.origTo = to; 299 | } 300 | 301 | ColorStyleSubject.prototype = { 302 | // parse "#FFFF00" to [256, 256, 0] 303 | expandColor: function(color) { 304 | var hexColor, red, green, blue; 305 | hexColor = ColorStyleSubject.parseColor(color); 306 | if (hexColor) { 307 | red = parseInt(hexColor.slice(1, 3), 16); 308 | green = parseInt(hexColor.slice(3, 5), 16); 309 | blue = parseInt(hexColor.slice(5, 7), 16); 310 | return [red,green,blue] 311 | } 312 | if (window.ANIMATOR_DEBUG) { 313 | alert("Invalid colour: '" + color + "'"); 314 | } 315 | }, 316 | getValueForState: function(color, state) { 317 | return Math.round(this.from[color] + ((this.to[color] - this.from[color]) * state)); 318 | }, 319 | setState: function(state) { 320 | var color = '#' 321 | + ColorStyleSubject.toColorPart(this.getValueForState(0, state)) 322 | + ColorStyleSubject.toColorPart(this.getValueForState(1, state)) 323 | + ColorStyleSubject.toColorPart(this.getValueForState(2, state)); 324 | for (var i=0; i 255) number = 255; 358 | var digits = number.toString(16); 359 | if (number < 16) return '0' + digits; 360 | return digits; 361 | } 362 | ColorStyleSubject.parseColor.rgbRe = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i; 363 | ColorStyleSubject.parseColor.hexRe = /^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/; 364 | 365 | // Animates discrete styles, i.e. ones that do not scale but have discrete values 366 | // that can't be interpolated 367 | function DiscreteStyleSubject(els, property, from, to, threshold) { 368 | this.els = Animator.makeArrayOfElements(els); 369 | this.property = Animator.camelize(property); 370 | this.from = from; 371 | this.to = to; 372 | this.threshold = threshold || 0.5; 373 | } 374 | 375 | DiscreteStyleSubject.prototype = { 376 | setState: function(state) { 377 | var j=0; 378 | for (var i=0; i+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, 10 | done = 0, 11 | toString = Object.prototype.toString, 12 | hasDuplicate = false, 13 | baseHasDuplicate = true, 14 | rBackslash = /\\/g, 15 | rNonWord = /\W/; 16 | 17 | // Here we check if the JavaScript engine is using some sort of 18 | // optimization where it does not always call our comparision 19 | // function. If that is the case, discard the hasDuplicate value. 20 | // Thus far that includes Google Chrome. 21 | [0, 0].sort(function() { 22 | baseHasDuplicate = false; 23 | return 0; 24 | }); 25 | 26 | var Sizzle = function( selector, context, results, seed ) { 27 | results = results || []; 28 | context = context || document; 29 | 30 | var origContext = context; 31 | 32 | if ( context.nodeType !== 1 && context.nodeType !== 9 ) { 33 | return []; 34 | } 35 | 36 | if ( !selector || typeof selector !== "string" ) { 37 | return results; 38 | } 39 | 40 | var m, set, checkSet, extra, ret, cur, pop, i, 41 | prune = true, 42 | contextXML = Sizzle.isXML( context ), 43 | parts = [], 44 | soFar = selector; 45 | 46 | // Reset the position of the chunker regexp (start from head) 47 | do { 48 | chunker.exec( "" ); 49 | m = chunker.exec( soFar ); 50 | 51 | if ( m ) { 52 | soFar = m[3]; 53 | 54 | parts.push( m[1] ); 55 | 56 | if ( m[2] ) { 57 | extra = m[3]; 58 | break; 59 | } 60 | } 61 | } while ( m ); 62 | 63 | if ( parts.length > 1 && origPOS.exec( selector ) ) { 64 | 65 | if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { 66 | set = posProcess( parts[0] + parts[1], context ); 67 | 68 | } else { 69 | set = Expr.relative[ parts[0] ] ? 70 | [ context ] : 71 | Sizzle( parts.shift(), context ); 72 | 73 | while ( parts.length ) { 74 | selector = parts.shift(); 75 | 76 | if ( Expr.relative[ selector ] ) { 77 | selector += parts.shift(); 78 | } 79 | 80 | set = posProcess( selector, set ); 81 | } 82 | } 83 | 84 | } else { 85 | // Take a shortcut and set the context if the root selector is an ID 86 | // (but not if it'll be faster if the inner selector is an ID) 87 | if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && 88 | Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { 89 | 90 | ret = Sizzle.find( parts.shift(), context, contextXML ); 91 | context = ret.expr ? 92 | Sizzle.filter( ret.expr, ret.set )[0] : 93 | ret.set[0]; 94 | } 95 | 96 | if ( context ) { 97 | ret = seed ? 98 | { expr: parts.pop(), set: makeArray(seed) } : 99 | Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); 100 | 101 | set = ret.expr ? 102 | Sizzle.filter( ret.expr, ret.set ) : 103 | ret.set; 104 | 105 | if ( parts.length > 0 ) { 106 | checkSet = makeArray( set ); 107 | 108 | } else { 109 | prune = false; 110 | } 111 | 112 | while ( parts.length ) { 113 | cur = parts.pop(); 114 | pop = cur; 115 | 116 | if ( !Expr.relative[ cur ] ) { 117 | cur = ""; 118 | } else { 119 | pop = parts.pop(); 120 | } 121 | 122 | if ( pop == null ) { 123 | pop = context; 124 | } 125 | 126 | Expr.relative[ cur ]( checkSet, pop, contextXML ); 127 | } 128 | 129 | } else { 130 | checkSet = parts = []; 131 | } 132 | } 133 | 134 | if ( !checkSet ) { 135 | checkSet = set; 136 | } 137 | 138 | if ( !checkSet ) { 139 | Sizzle.error( cur || selector ); 140 | } 141 | 142 | if ( toString.call(checkSet) === "[object Array]" ) { 143 | if ( !prune ) { 144 | results.push.apply( results, checkSet ); 145 | 146 | } else if ( context && context.nodeType === 1 ) { 147 | for ( i = 0; checkSet[i] != null; i++ ) { 148 | if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { 149 | results.push( set[i] ); 150 | } 151 | } 152 | 153 | } else { 154 | for ( i = 0; checkSet[i] != null; i++ ) { 155 | if ( checkSet[i] && checkSet[i].nodeType === 1 ) { 156 | results.push( set[i] ); 157 | } 158 | } 159 | } 160 | 161 | } else { 162 | makeArray( checkSet, results ); 163 | } 164 | 165 | if ( extra ) { 166 | Sizzle( extra, origContext, results, seed ); 167 | Sizzle.uniqueSort( results ); 168 | } 169 | 170 | return results; 171 | }; 172 | 173 | Sizzle.uniqueSort = function( results ) { 174 | if ( sortOrder ) { 175 | hasDuplicate = baseHasDuplicate; 176 | results.sort( sortOrder ); 177 | 178 | if ( hasDuplicate ) { 179 | for ( var i = 1; i < results.length; i++ ) { 180 | if ( results[i] === results[ i - 1 ] ) { 181 | results.splice( i--, 1 ); 182 | } 183 | } 184 | } 185 | } 186 | 187 | return results; 188 | }; 189 | 190 | Sizzle.matches = function( expr, set ) { 191 | return Sizzle( expr, null, null, set ); 192 | }; 193 | 194 | Sizzle.matchesSelector = function( node, expr ) { 195 | return Sizzle( expr, null, null, [node] ).length > 0; 196 | }; 197 | 198 | Sizzle.find = function( expr, context, isXML ) { 199 | var set; 200 | 201 | if ( !expr ) { 202 | return []; 203 | } 204 | 205 | for ( var i = 0, l = Expr.order.length; i < l; i++ ) { 206 | var match, 207 | type = Expr.order[i]; 208 | 209 | if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { 210 | var left = match[1]; 211 | match.splice( 1, 1 ); 212 | 213 | if ( left.substr( left.length - 1 ) !== "\\" ) { 214 | match[1] = (match[1] || "").replace( rBackslash, "" ); 215 | set = Expr.find[ type ]( match, context, isXML ); 216 | 217 | if ( set != null ) { 218 | expr = expr.replace( Expr.match[ type ], "" ); 219 | break; 220 | } 221 | } 222 | } 223 | } 224 | 225 | if ( !set ) { 226 | set = typeof context.getElementsByTagName !== "undefined" ? 227 | context.getElementsByTagName( "*" ) : 228 | []; 229 | } 230 | 231 | return { set: set, expr: expr }; 232 | }; 233 | 234 | Sizzle.filter = function( expr, set, inplace, not ) { 235 | var match, anyFound, 236 | old = expr, 237 | result = [], 238 | curLoop = set, 239 | isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); 240 | 241 | while ( expr && set.length ) { 242 | for ( var type in Expr.filter ) { 243 | if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { 244 | var found, item, 245 | filter = Expr.filter[ type ], 246 | left = match[1]; 247 | 248 | anyFound = false; 249 | 250 | match.splice(1,1); 251 | 252 | if ( left.substr( left.length - 1 ) === "\\" ) { 253 | continue; 254 | } 255 | 256 | if ( curLoop === result ) { 257 | result = []; 258 | } 259 | 260 | if ( Expr.preFilter[ type ] ) { 261 | match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); 262 | 263 | if ( !match ) { 264 | anyFound = found = true; 265 | 266 | } else if ( match === true ) { 267 | continue; 268 | } 269 | } 270 | 271 | if ( match ) { 272 | for ( var i = 0; (item = curLoop[i]) != null; i++ ) { 273 | if ( item ) { 274 | found = filter( item, match, i, curLoop ); 275 | var pass = not ^ !!found; 276 | 277 | if ( inplace && found != null ) { 278 | if ( pass ) { 279 | anyFound = true; 280 | 281 | } else { 282 | curLoop[i] = false; 283 | } 284 | 285 | } else if ( pass ) { 286 | result.push( item ); 287 | anyFound = true; 288 | } 289 | } 290 | } 291 | } 292 | 293 | if ( found !== undefined ) { 294 | if ( !inplace ) { 295 | curLoop = result; 296 | } 297 | 298 | expr = expr.replace( Expr.match[ type ], "" ); 299 | 300 | if ( !anyFound ) { 301 | return []; 302 | } 303 | 304 | break; 305 | } 306 | } 307 | } 308 | 309 | // Improper expression 310 | if ( expr === old ) { 311 | if ( anyFound == null ) { 312 | Sizzle.error( expr ); 313 | 314 | } else { 315 | break; 316 | } 317 | } 318 | 319 | old = expr; 320 | } 321 | 322 | return curLoop; 323 | }; 324 | 325 | Sizzle.error = function( msg ) { 326 | throw "Syntax error, unrecognized expression: " + msg; 327 | }; 328 | 329 | var Expr = Sizzle.selectors = { 330 | order: [ "ID", "NAME", "TAG" ], 331 | 332 | match: { 333 | ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 334 | CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 335 | NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, 336 | ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, 337 | TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, 338 | CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, 339 | POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, 340 | PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ 341 | }, 342 | 343 | leftMatch: {}, 344 | 345 | attrMap: { 346 | "class": "className", 347 | "for": "htmlFor" 348 | }, 349 | 350 | attrHandle: { 351 | href: function( elem ) { 352 | return elem.getAttribute( "href" ); 353 | }, 354 | type: function( elem ) { 355 | return elem.getAttribute( "type" ); 356 | } 357 | }, 358 | 359 | relative: { 360 | "+": function(checkSet, part){ 361 | var isPartStr = typeof part === "string", 362 | isTag = isPartStr && !rNonWord.test( part ), 363 | isPartStrNotTag = isPartStr && !isTag; 364 | 365 | if ( isTag ) { 366 | part = part.toLowerCase(); 367 | } 368 | 369 | for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { 370 | if ( (elem = checkSet[i]) ) { 371 | while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} 372 | 373 | checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? 374 | elem || false : 375 | elem === part; 376 | } 377 | } 378 | 379 | if ( isPartStrNotTag ) { 380 | Sizzle.filter( part, checkSet, true ); 381 | } 382 | }, 383 | 384 | ">": function( checkSet, part ) { 385 | var elem, 386 | isPartStr = typeof part === "string", 387 | i = 0, 388 | l = checkSet.length; 389 | 390 | if ( isPartStr && !rNonWord.test( part ) ) { 391 | part = part.toLowerCase(); 392 | 393 | for ( ; i < l; i++ ) { 394 | elem = checkSet[i]; 395 | 396 | if ( elem ) { 397 | var parent = elem.parentNode; 398 | checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; 399 | } 400 | } 401 | 402 | } else { 403 | for ( ; i < l; i++ ) { 404 | elem = checkSet[i]; 405 | 406 | if ( elem ) { 407 | checkSet[i] = isPartStr ? 408 | elem.parentNode : 409 | elem.parentNode === part; 410 | } 411 | } 412 | 413 | if ( isPartStr ) { 414 | Sizzle.filter( part, checkSet, true ); 415 | } 416 | } 417 | }, 418 | 419 | "": function(checkSet, part, isXML){ 420 | var nodeCheck, 421 | doneName = done++, 422 | checkFn = dirCheck; 423 | 424 | if ( typeof part === "string" && !rNonWord.test( part ) ) { 425 | part = part.toLowerCase(); 426 | nodeCheck = part; 427 | checkFn = dirNodeCheck; 428 | } 429 | 430 | checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); 431 | }, 432 | 433 | "~": function( checkSet, part, isXML ) { 434 | var nodeCheck, 435 | doneName = done++, 436 | checkFn = dirCheck; 437 | 438 | if ( typeof part === "string" && !rNonWord.test( part ) ) { 439 | part = part.toLowerCase(); 440 | nodeCheck = part; 441 | checkFn = dirNodeCheck; 442 | } 443 | 444 | checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); 445 | } 446 | }, 447 | 448 | find: { 449 | ID: function( match, context, isXML ) { 450 | if ( typeof context.getElementById !== "undefined" && !isXML ) { 451 | var m = context.getElementById(match[1]); 452 | // Check parentNode to catch when Blackberry 4.6 returns 453 | // nodes that are no longer in the document #6963 454 | return m && m.parentNode ? [m] : []; 455 | } 456 | }, 457 | 458 | NAME: function( match, context ) { 459 | if ( typeof context.getElementsByName !== "undefined" ) { 460 | var ret = [], 461 | results = context.getElementsByName( match[1] ); 462 | 463 | for ( var i = 0, l = results.length; i < l; i++ ) { 464 | if ( results[i].getAttribute("name") === match[1] ) { 465 | ret.push( results[i] ); 466 | } 467 | } 468 | 469 | return ret.length === 0 ? null : ret; 470 | } 471 | }, 472 | 473 | TAG: function( match, context ) { 474 | if ( typeof context.getElementsByTagName !== "undefined" ) { 475 | return context.getElementsByTagName( match[1] ); 476 | } 477 | } 478 | }, 479 | preFilter: { 480 | CLASS: function( match, curLoop, inplace, result, not, isXML ) { 481 | match = " " + match[1].replace( rBackslash, "" ) + " "; 482 | 483 | if ( isXML ) { 484 | return match; 485 | } 486 | 487 | for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { 488 | if ( elem ) { 489 | if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { 490 | if ( !inplace ) { 491 | result.push( elem ); 492 | } 493 | 494 | } else if ( inplace ) { 495 | curLoop[i] = false; 496 | } 497 | } 498 | } 499 | 500 | return false; 501 | }, 502 | 503 | ID: function( match ) { 504 | return match[1].replace( rBackslash, "" ); 505 | }, 506 | 507 | TAG: function( match, curLoop ) { 508 | return match[1].replace( rBackslash, "" ).toLowerCase(); 509 | }, 510 | 511 | CHILD: function( match ) { 512 | if ( match[1] === "nth" ) { 513 | if ( !match[2] ) { 514 | Sizzle.error( match[0] ); 515 | } 516 | 517 | match[2] = match[2].replace(/^\+|\s*/g, ''); 518 | 519 | // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' 520 | var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( 521 | match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || 522 | !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); 523 | 524 | // calculate the numbers (first)n+(last) including if they are negative 525 | match[2] = (test[1] + (test[2] || 1)) - 0; 526 | match[3] = test[3] - 0; 527 | } 528 | else if ( match[2] ) { 529 | Sizzle.error( match[0] ); 530 | } 531 | 532 | // TODO: Move to normal caching system 533 | match[0] = done++; 534 | 535 | return match; 536 | }, 537 | 538 | ATTR: function( match, curLoop, inplace, result, not, isXML ) { 539 | var name = match[1] = match[1].replace( rBackslash, "" ); 540 | 541 | if ( !isXML && Expr.attrMap[name] ) { 542 | match[1] = Expr.attrMap[name]; 543 | } 544 | 545 | // Handle if an un-quoted value was used 546 | match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); 547 | 548 | if ( match[2] === "~=" ) { 549 | match[4] = " " + match[4] + " "; 550 | } 551 | 552 | return match; 553 | }, 554 | 555 | PSEUDO: function( match, curLoop, inplace, result, not ) { 556 | if ( match[1] === "not" ) { 557 | // If we're dealing with a complex expression, or a simple one 558 | if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { 559 | match[3] = Sizzle(match[3], null, null, curLoop); 560 | 561 | } else { 562 | var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); 563 | 564 | if ( !inplace ) { 565 | result.push.apply( result, ret ); 566 | } 567 | 568 | return false; 569 | } 570 | 571 | } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { 572 | return true; 573 | } 574 | 575 | return match; 576 | }, 577 | 578 | POS: function( match ) { 579 | match.unshift( true ); 580 | 581 | return match; 582 | } 583 | }, 584 | 585 | filters: { 586 | enabled: function( elem ) { 587 | return elem.disabled === false && elem.type !== "hidden"; 588 | }, 589 | 590 | disabled: function( elem ) { 591 | return elem.disabled === true; 592 | }, 593 | 594 | checked: function( elem ) { 595 | return elem.checked === true; 596 | }, 597 | 598 | selected: function( elem ) { 599 | // Accessing this property makes selected-by-default 600 | // options in Safari work properly 601 | if ( elem.parentNode ) { 602 | elem.parentNode.selectedIndex; 603 | } 604 | 605 | return elem.selected === true; 606 | }, 607 | 608 | parent: function( elem ) { 609 | return !!elem.firstChild; 610 | }, 611 | 612 | empty: function( elem ) { 613 | return !elem.firstChild; 614 | }, 615 | 616 | has: function( elem, i, match ) { 617 | return !!Sizzle( match[3], elem ).length; 618 | }, 619 | 620 | header: function( elem ) { 621 | return (/h\d/i).test( elem.nodeName ); 622 | }, 623 | 624 | text: function( elem ) { 625 | var attr = elem.getAttribute( "type" ), type = elem.type; 626 | // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) 627 | // use getAttribute instead to test this case 628 | return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); 629 | }, 630 | 631 | radio: function( elem ) { 632 | return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; 633 | }, 634 | 635 | checkbox: function( elem ) { 636 | return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; 637 | }, 638 | 639 | file: function( elem ) { 640 | return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; 641 | }, 642 | 643 | password: function( elem ) { 644 | return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; 645 | }, 646 | 647 | submit: function( elem ) { 648 | var name = elem.nodeName.toLowerCase(); 649 | return (name === "input" || name === "button") && "submit" === elem.type; 650 | }, 651 | 652 | image: function( elem ) { 653 | return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; 654 | }, 655 | 656 | reset: function( elem ) { 657 | var name = elem.nodeName.toLowerCase(); 658 | return (name === "input" || name === "button") && "reset" === elem.type; 659 | }, 660 | 661 | button: function( elem ) { 662 | var name = elem.nodeName.toLowerCase(); 663 | return name === "input" && "button" === elem.type || name === "button"; 664 | }, 665 | 666 | input: function( elem ) { 667 | return (/input|select|textarea|button/i).test( elem.nodeName ); 668 | }, 669 | 670 | focus: function( elem ) { 671 | return elem === elem.ownerDocument.activeElement; 672 | } 673 | }, 674 | setFilters: { 675 | first: function( elem, i ) { 676 | return i === 0; 677 | }, 678 | 679 | last: function( elem, i, match, array ) { 680 | return i === array.length - 1; 681 | }, 682 | 683 | even: function( elem, i ) { 684 | return i % 2 === 0; 685 | }, 686 | 687 | odd: function( elem, i ) { 688 | return i % 2 === 1; 689 | }, 690 | 691 | lt: function( elem, i, match ) { 692 | return i < match[3] - 0; 693 | }, 694 | 695 | gt: function( elem, i, match ) { 696 | return i > match[3] - 0; 697 | }, 698 | 699 | nth: function( elem, i, match ) { 700 | return match[3] - 0 === i; 701 | }, 702 | 703 | eq: function( elem, i, match ) { 704 | return match[3] - 0 === i; 705 | } 706 | }, 707 | filter: { 708 | PSEUDO: function( elem, match, i, array ) { 709 | var name = match[1], 710 | filter = Expr.filters[ name ]; 711 | 712 | if ( filter ) { 713 | return filter( elem, i, match, array ); 714 | 715 | } else if ( name === "contains" ) { 716 | return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; 717 | 718 | } else if ( name === "not" ) { 719 | var not = match[3]; 720 | 721 | for ( var j = 0, l = not.length; j < l; j++ ) { 722 | if ( not[j] === elem ) { 723 | return false; 724 | } 725 | } 726 | 727 | return true; 728 | 729 | } else { 730 | Sizzle.error( name ); 731 | } 732 | }, 733 | 734 | CHILD: function( elem, match ) { 735 | var type = match[1], 736 | node = elem; 737 | 738 | switch ( type ) { 739 | case "only": 740 | case "first": 741 | while ( (node = node.previousSibling) ) { 742 | if ( node.nodeType === 1 ) { 743 | return false; 744 | } 745 | } 746 | 747 | if ( type === "first" ) { 748 | return true; 749 | } 750 | 751 | node = elem; 752 | 753 | case "last": 754 | while ( (node = node.nextSibling) ) { 755 | if ( node.nodeType === 1 ) { 756 | return false; 757 | } 758 | } 759 | 760 | return true; 761 | 762 | case "nth": 763 | var first = match[2], 764 | last = match[3]; 765 | 766 | if ( first === 1 && last === 0 ) { 767 | return true; 768 | } 769 | 770 | var doneName = match[0], 771 | parent = elem.parentNode; 772 | 773 | if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { 774 | var count = 0; 775 | 776 | for ( node = parent.firstChild; node; node = node.nextSibling ) { 777 | if ( node.nodeType === 1 ) { 778 | node.nodeIndex = ++count; 779 | } 780 | } 781 | 782 | parent.sizcache = doneName; 783 | } 784 | 785 | var diff = elem.nodeIndex - last; 786 | 787 | if ( first === 0 ) { 788 | return diff === 0; 789 | 790 | } else { 791 | return ( diff % first === 0 && diff / first >= 0 ); 792 | } 793 | } 794 | }, 795 | 796 | ID: function( elem, match ) { 797 | return elem.nodeType === 1 && elem.getAttribute("id") === match; 798 | }, 799 | 800 | TAG: function( elem, match ) { 801 | return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; 802 | }, 803 | 804 | CLASS: function( elem, match ) { 805 | return (" " + (elem.className || elem.getAttribute("class")) + " ") 806 | .indexOf( match ) > -1; 807 | }, 808 | 809 | ATTR: function( elem, match ) { 810 | var name = match[1], 811 | result = Expr.attrHandle[ name ] ? 812 | Expr.attrHandle[ name ]( elem ) : 813 | elem[ name ] != null ? 814 | elem[ name ] : 815 | elem.getAttribute( name ), 816 | value = result + "", 817 | type = match[2], 818 | check = match[4]; 819 | 820 | return result == null ? 821 | type === "!=" : 822 | type === "=" ? 823 | value === check : 824 | type === "*=" ? 825 | value.indexOf(check) >= 0 : 826 | type === "~=" ? 827 | (" " + value + " ").indexOf(check) >= 0 : 828 | !check ? 829 | value && result !== false : 830 | type === "!=" ? 831 | value !== check : 832 | type === "^=" ? 833 | value.indexOf(check) === 0 : 834 | type === "$=" ? 835 | value.substr(value.length - check.length) === check : 836 | type === "|=" ? 837 | value === check || value.substr(0, check.length + 1) === check + "-" : 838 | false; 839 | }, 840 | 841 | POS: function( elem, match, i, array ) { 842 | var name = match[2], 843 | filter = Expr.setFilters[ name ]; 844 | 845 | if ( filter ) { 846 | return filter( elem, i, match, array ); 847 | } 848 | } 849 | } 850 | }; 851 | 852 | var origPOS = Expr.match.POS, 853 | fescape = function(all, num){ 854 | return "\\" + (num - 0 + 1); 855 | }; 856 | 857 | for ( var type in Expr.match ) { 858 | Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); 859 | Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); 860 | } 861 | 862 | var makeArray = function( array, results ) { 863 | array = Array.prototype.slice.call( array, 0 ); 864 | 865 | if ( results ) { 866 | results.push.apply( results, array ); 867 | return results; 868 | } 869 | 870 | return array; 871 | }; 872 | 873 | // Perform a simple check to determine if the browser is capable of 874 | // converting a NodeList to an array using builtin methods. 875 | // Also verifies that the returned array holds DOM nodes 876 | // (which is not the case in the Blackberry browser) 877 | try { 878 | Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; 879 | 880 | // Provide a fallback method if it does not work 881 | } catch( e ) { 882 | makeArray = function( array, results ) { 883 | var i = 0, 884 | ret = results || []; 885 | 886 | if ( toString.call(array) === "[object Array]" ) { 887 | Array.prototype.push.apply( ret, array ); 888 | 889 | } else { 890 | if ( typeof array.length === "number" ) { 891 | for ( var l = array.length; i < l; i++ ) { 892 | ret.push( array[i] ); 893 | } 894 | 895 | } else { 896 | for ( ; array[i]; i++ ) { 897 | ret.push( array[i] ); 898 | } 899 | } 900 | } 901 | 902 | return ret; 903 | }; 904 | } 905 | 906 | var sortOrder, siblingCheck; 907 | 908 | if ( document.documentElement.compareDocumentPosition ) { 909 | sortOrder = function( a, b ) { 910 | if ( a === b ) { 911 | hasDuplicate = true; 912 | return 0; 913 | } 914 | 915 | if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { 916 | return a.compareDocumentPosition ? -1 : 1; 917 | } 918 | 919 | return a.compareDocumentPosition(b) & 4 ? -1 : 1; 920 | }; 921 | 922 | } else { 923 | sortOrder = function( a, b ) { 924 | // The nodes are identical, we can exit early 925 | if ( a === b ) { 926 | hasDuplicate = true; 927 | return 0; 928 | 929 | // Fallback to using sourceIndex (in IE) if it's available on both nodes 930 | } else if ( a.sourceIndex && b.sourceIndex ) { 931 | return a.sourceIndex - b.sourceIndex; 932 | } 933 | 934 | var al, bl, 935 | ap = [], 936 | bp = [], 937 | aup = a.parentNode, 938 | bup = b.parentNode, 939 | cur = aup; 940 | 941 | // If the nodes are siblings (or identical) we can do a quick check 942 | if ( aup === bup ) { 943 | return siblingCheck( a, b ); 944 | 945 | // If no parents were found then the nodes are disconnected 946 | } else if ( !aup ) { 947 | return -1; 948 | 949 | } else if ( !bup ) { 950 | return 1; 951 | } 952 | 953 | // Otherwise they're somewhere else in the tree so we need 954 | // to build up a full list of the parentNodes for comparison 955 | while ( cur ) { 956 | ap.unshift( cur ); 957 | cur = cur.parentNode; 958 | } 959 | 960 | cur = bup; 961 | 962 | while ( cur ) { 963 | bp.unshift( cur ); 964 | cur = cur.parentNode; 965 | } 966 | 967 | al = ap.length; 968 | bl = bp.length; 969 | 970 | // Start walking down the tree looking for a discrepancy 971 | for ( var i = 0; i < al && i < bl; i++ ) { 972 | if ( ap[i] !== bp[i] ) { 973 | return siblingCheck( ap[i], bp[i] ); 974 | } 975 | } 976 | 977 | // We ended someplace up the tree so do a sibling check 978 | return i === al ? 979 | siblingCheck( a, bp[i], -1 ) : 980 | siblingCheck( ap[i], b, 1 ); 981 | }; 982 | 983 | siblingCheck = function( a, b, ret ) { 984 | if ( a === b ) { 985 | return ret; 986 | } 987 | 988 | var cur = a.nextSibling; 989 | 990 | while ( cur ) { 991 | if ( cur === b ) { 992 | return -1; 993 | } 994 | 995 | cur = cur.nextSibling; 996 | } 997 | 998 | return 1; 999 | }; 1000 | } 1001 | 1002 | // Utility function for retreiving the text value of an array of DOM nodes 1003 | Sizzle.getText = function( elems ) { 1004 | var ret = "", elem; 1005 | 1006 | for ( var i = 0; elems[i]; i++ ) { 1007 | elem = elems[i]; 1008 | 1009 | // Get the text from text nodes and CDATA nodes 1010 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) { 1011 | ret += elem.nodeValue; 1012 | 1013 | // Traverse everything else, except comment nodes 1014 | } else if ( elem.nodeType !== 8 ) { 1015 | ret += Sizzle.getText( elem.childNodes ); 1016 | } 1017 | } 1018 | 1019 | return ret; 1020 | }; 1021 | 1022 | // Check to see if the browser returns elements by name when 1023 | // querying by getElementById (and provide a workaround) 1024 | (function(){ 1025 | // We're going to inject a fake input element with a specified name 1026 | var form = document.createElement("div"), 1027 | id = "script" + (new Date()).getTime(), 1028 | root = document.documentElement; 1029 | 1030 | form.innerHTML = ""; 1031 | 1032 | // Inject it into the root element, check its status, and remove it quickly 1033 | root.insertBefore( form, root.firstChild ); 1034 | 1035 | // The workaround has to do additional checks after a getElementById 1036 | // Which slows things down for other browsers (hence the branching) 1037 | if ( document.getElementById( id ) ) { 1038 | Expr.find.ID = function( match, context, isXML ) { 1039 | if ( typeof context.getElementById !== "undefined" && !isXML ) { 1040 | var m = context.getElementById(match[1]); 1041 | 1042 | return m ? 1043 | m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? 1044 | [m] : 1045 | undefined : 1046 | []; 1047 | } 1048 | }; 1049 | 1050 | Expr.filter.ID = function( elem, match ) { 1051 | var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); 1052 | 1053 | return elem.nodeType === 1 && node && node.nodeValue === match; 1054 | }; 1055 | } 1056 | 1057 | root.removeChild( form ); 1058 | 1059 | // release memory in IE 1060 | root = form = null; 1061 | })(); 1062 | 1063 | (function(){ 1064 | // Check to see if the browser returns only elements 1065 | // when doing getElementsByTagName("*") 1066 | 1067 | // Create a fake element 1068 | var div = document.createElement("div"); 1069 | div.appendChild( document.createComment("") ); 1070 | 1071 | // Make sure no comments are found 1072 | if ( div.getElementsByTagName("*").length > 0 ) { 1073 | Expr.find.TAG = function( match, context ) { 1074 | var results = context.getElementsByTagName( match[1] ); 1075 | 1076 | // Filter out possible comments 1077 | if ( match[1] === "*" ) { 1078 | var tmp = []; 1079 | 1080 | for ( var i = 0; results[i]; i++ ) { 1081 | if ( results[i].nodeType === 1 ) { 1082 | tmp.push( results[i] ); 1083 | } 1084 | } 1085 | 1086 | results = tmp; 1087 | } 1088 | 1089 | return results; 1090 | }; 1091 | } 1092 | 1093 | // Check to see if an attribute returns normalized href attributes 1094 | div.innerHTML = ""; 1095 | 1096 | if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && 1097 | div.firstChild.getAttribute("href") !== "#" ) { 1098 | 1099 | Expr.attrHandle.href = function( elem ) { 1100 | return elem.getAttribute( "href", 2 ); 1101 | }; 1102 | } 1103 | 1104 | // release memory in IE 1105 | div = null; 1106 | })(); 1107 | 1108 | if ( document.querySelectorAll ) { 1109 | (function(){ 1110 | var oldSizzle = Sizzle, 1111 | div = document.createElement("div"), 1112 | id = "__sizzle__"; 1113 | 1114 | div.innerHTML = "

"; 1115 | 1116 | // Safari can't handle uppercase or unicode characters when 1117 | // in quirks mode. 1118 | if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { 1119 | return; 1120 | } 1121 | 1122 | Sizzle = function( query, context, extra, seed ) { 1123 | context = context || document; 1124 | 1125 | // Only use querySelectorAll on non-XML documents 1126 | // (ID selectors don't work in non-HTML documents) 1127 | if ( !seed && !Sizzle.isXML(context) ) { 1128 | // See if we find a selector to speed up 1129 | var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); 1130 | 1131 | if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { 1132 | // Speed-up: Sizzle("TAG") 1133 | if ( match[1] ) { 1134 | return makeArray( context.getElementsByTagName( query ), extra ); 1135 | 1136 | // Speed-up: Sizzle(".CLASS") 1137 | } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { 1138 | return makeArray( context.getElementsByClassName( match[2] ), extra ); 1139 | } 1140 | } 1141 | 1142 | if ( context.nodeType === 9 ) { 1143 | // Speed-up: Sizzle("body") 1144 | // The body element only exists once, optimize finding it 1145 | if ( query === "body" && context.body ) { 1146 | return makeArray( [ context.body ], extra ); 1147 | 1148 | // Speed-up: Sizzle("#ID") 1149 | } else if ( match && match[3] ) { 1150 | var elem = context.getElementById( match[3] ); 1151 | 1152 | // Check parentNode to catch when Blackberry 4.6 returns 1153 | // nodes that are no longer in the document #6963 1154 | if ( elem && elem.parentNode ) { 1155 | // Handle the case where IE and Opera return items 1156 | // by name instead of ID 1157 | if ( elem.id === match[3] ) { 1158 | return makeArray( [ elem ], extra ); 1159 | } 1160 | 1161 | } else { 1162 | return makeArray( [], extra ); 1163 | } 1164 | } 1165 | 1166 | try { 1167 | return makeArray( context.querySelectorAll(query), extra ); 1168 | } catch(qsaError) {} 1169 | 1170 | // qSA works strangely on Element-rooted queries 1171 | // We can work around this by specifying an extra ID on the root 1172 | // and working up from there (Thanks to Andrew Dupont for the technique) 1173 | // IE 8 doesn't work on object elements 1174 | } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { 1175 | var oldContext = context, 1176 | old = context.getAttribute( "id" ), 1177 | nid = old || id, 1178 | hasParent = context.parentNode, 1179 | relativeHierarchySelector = /^\s*[+~]/.test( query ); 1180 | 1181 | if ( !old ) { 1182 | context.setAttribute( "id", nid ); 1183 | } else { 1184 | nid = nid.replace( /'/g, "\\$&" ); 1185 | } 1186 | if ( relativeHierarchySelector && hasParent ) { 1187 | context = context.parentNode; 1188 | } 1189 | 1190 | try { 1191 | if ( !relativeHierarchySelector || hasParent ) { 1192 | return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); 1193 | } 1194 | 1195 | } catch(pseudoError) { 1196 | } finally { 1197 | if ( !old ) { 1198 | oldContext.removeAttribute( "id" ); 1199 | } 1200 | } 1201 | } 1202 | } 1203 | 1204 | return oldSizzle(query, context, extra, seed); 1205 | }; 1206 | 1207 | for ( var prop in oldSizzle ) { 1208 | Sizzle[ prop ] = oldSizzle[ prop ]; 1209 | } 1210 | 1211 | // release memory in IE 1212 | div = null; 1213 | })(); 1214 | } 1215 | 1216 | (function(){ 1217 | var html = document.documentElement, 1218 | matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; 1219 | 1220 | if ( matches ) { 1221 | // Check to see if it's possible to do matchesSelector 1222 | // on a disconnected node (IE 9 fails this) 1223 | var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), 1224 | pseudoWorks = false; 1225 | 1226 | try { 1227 | // This should fail with an exception 1228 | // Gecko does not error, returns false instead 1229 | matches.call( document.documentElement, "[test!='']:sizzle" ); 1230 | 1231 | } catch( pseudoError ) { 1232 | pseudoWorks = true; 1233 | } 1234 | 1235 | Sizzle.matchesSelector = function( node, expr ) { 1236 | // Make sure that attribute selectors are quoted 1237 | expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); 1238 | 1239 | if ( !Sizzle.isXML( node ) ) { 1240 | try { 1241 | if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { 1242 | var ret = matches.call( node, expr ); 1243 | 1244 | // IE 9's matchesSelector returns false on disconnected nodes 1245 | if ( ret || !disconnectedMatch || 1246 | // As well, disconnected nodes are said to be in a document 1247 | // fragment in IE 9, so check for that 1248 | node.document && node.document.nodeType !== 11 ) { 1249 | return ret; 1250 | } 1251 | } 1252 | } catch(e) {} 1253 | } 1254 | 1255 | return Sizzle(expr, null, null, [node]).length > 0; 1256 | }; 1257 | } 1258 | })(); 1259 | 1260 | (function(){ 1261 | var div = document.createElement("div"); 1262 | 1263 | div.innerHTML = "
"; 1264 | 1265 | // Opera can't find a second classname (in 9.6) 1266 | // Also, make sure that getElementsByClassName actually exists 1267 | if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { 1268 | return; 1269 | } 1270 | 1271 | // Safari caches class attributes, doesn't catch changes (in 3.2) 1272 | div.lastChild.className = "e"; 1273 | 1274 | if ( div.getElementsByClassName("e").length === 1 ) { 1275 | return; 1276 | } 1277 | 1278 | Expr.order.splice(1, 0, "CLASS"); 1279 | Expr.find.CLASS = function( match, context, isXML ) { 1280 | if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { 1281 | return context.getElementsByClassName(match[1]); 1282 | } 1283 | }; 1284 | 1285 | // release memory in IE 1286 | div = null; 1287 | })(); 1288 | 1289 | function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 1290 | for ( var i = 0, l = checkSet.length; i < l; i++ ) { 1291 | var elem = checkSet[i]; 1292 | 1293 | if ( elem ) { 1294 | var match = false; 1295 | 1296 | elem = elem[dir]; 1297 | 1298 | while ( elem ) { 1299 | if ( elem.sizcache === doneName ) { 1300 | match = checkSet[elem.sizset]; 1301 | break; 1302 | } 1303 | 1304 | if ( elem.nodeType === 1 && !isXML ){ 1305 | elem.sizcache = doneName; 1306 | elem.sizset = i; 1307 | } 1308 | 1309 | if ( elem.nodeName.toLowerCase() === cur ) { 1310 | match = elem; 1311 | break; 1312 | } 1313 | 1314 | elem = elem[dir]; 1315 | } 1316 | 1317 | checkSet[i] = match; 1318 | } 1319 | } 1320 | } 1321 | 1322 | function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 1323 | for ( var i = 0, l = checkSet.length; i < l; i++ ) { 1324 | var elem = checkSet[i]; 1325 | 1326 | if ( elem ) { 1327 | var match = false; 1328 | 1329 | elem = elem[dir]; 1330 | 1331 | while ( elem ) { 1332 | if ( elem.sizcache === doneName ) { 1333 | match = checkSet[elem.sizset]; 1334 | break; 1335 | } 1336 | 1337 | if ( elem.nodeType === 1 ) { 1338 | if ( !isXML ) { 1339 | elem.sizcache = doneName; 1340 | elem.sizset = i; 1341 | } 1342 | 1343 | if ( typeof cur !== "string" ) { 1344 | if ( elem === cur ) { 1345 | match = true; 1346 | break; 1347 | } 1348 | 1349 | } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { 1350 | match = elem; 1351 | break; 1352 | } 1353 | } 1354 | 1355 | elem = elem[dir]; 1356 | } 1357 | 1358 | checkSet[i] = match; 1359 | } 1360 | } 1361 | } 1362 | 1363 | if ( document.documentElement.contains ) { 1364 | Sizzle.contains = function( a, b ) { 1365 | return a !== b && (a.contains ? a.contains(b) : true); 1366 | }; 1367 | 1368 | } else if ( document.documentElement.compareDocumentPosition ) { 1369 | Sizzle.contains = function( a, b ) { 1370 | return !!(a.compareDocumentPosition(b) & 16); 1371 | }; 1372 | 1373 | } else { 1374 | Sizzle.contains = function() { 1375 | return false; 1376 | }; 1377 | } 1378 | 1379 | Sizzle.isXML = function( elem ) { 1380 | // documentElement is verified for cases where it doesn't yet exist 1381 | // (such as loading iframes in IE - #4833) 1382 | var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; 1383 | 1384 | return documentElement ? documentElement.nodeName !== "HTML" : false; 1385 | }; 1386 | 1387 | var posProcess = function( selector, context ) { 1388 | var match, 1389 | tmpSet = [], 1390 | later = "", 1391 | root = context.nodeType ? [context] : context; 1392 | 1393 | // Position selectors must be done after the filter 1394 | // And so must :not(positional) so we move all PSEUDOs to the end 1395 | while ( (match = Expr.match.PSEUDO.exec( selector )) ) { 1396 | later += match[0]; 1397 | selector = selector.replace( Expr.match.PSEUDO, "" ); 1398 | } 1399 | 1400 | selector = Expr.relative[selector] ? selector + "*" : selector; 1401 | 1402 | for ( var i = 0, l = root.length; i < l; i++ ) { 1403 | Sizzle( selector, root[i], tmpSet ); 1404 | } 1405 | 1406 | return Sizzle.filter( later, tmpSet ); 1407 | }; 1408 | 1409 | // EXPOSE 1410 | 1411 | window.Sizzle = Sizzle; 1412 | 1413 | })(); 1414 | -------------------------------------------------------------------------------- /js/game/vendor/state-machine.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Javascript State Machine Library - https://github.com/jakesgordon/javascript-state-machine 4 | 5 | Copyright (c) 2012, 2013 Jake Gordon and contributors 6 | Released under the MIT license - https://github.com/jakesgordon/javascript-state-machine/blob/master/LICENSE 7 | 8 | */ 9 | 10 | (function (window) { 11 | 12 | var StateMachine = { 13 | 14 | //--------------------------------------------------------------------------- 15 | 16 | VERSION: "2.2.0", 17 | 18 | //--------------------------------------------------------------------------- 19 | 20 | Result: { 21 | SUCCEEDED: 1, // the event transitioned successfully from one state to another 22 | NOTRANSITION: 2, // the event was successfull but no state transition was necessary 23 | CANCELLED: 3, // the event was cancelled by the caller in a beforeEvent callback 24 | PENDING: 4 // the event is asynchronous and the caller is in control of when the transition occurs 25 | }, 26 | 27 | Error: { 28 | INVALID_TRANSITION: 100, // caller tried to fire an event that was innapropriate in the current state 29 | PENDING_TRANSITION: 200, // caller tried to fire an event while an async transition was still pending 30 | INVALID_CALLBACK: 300 // caller provided callback function threw an exception 31 | }, 32 | 33 | WILDCARD: '*', 34 | ASYNC: 'async', 35 | 36 | //--------------------------------------------------------------------------- 37 | 38 | create: function(cfg, target) { 39 | 40 | var initial = (typeof cfg.initial == 'string') ? { state: cfg.initial } : cfg.initial; // allow for a simple string, or an object with { state: 'foo', event: 'setup', defer: true|false } 41 | var terminal = cfg.terminal || cfg['final']; 42 | var fsm = target || cfg.target || {}; 43 | var events = cfg.events || []; 44 | var callbacks = cfg.callbacks || {}; 45 | var map = {}; 46 | 47 | var add = function(e) { 48 | var from = (e.from instanceof Array) ? e.from : (e.from ? [e.from] : [StateMachine.WILDCARD]); // allow 'wildcard' transition if 'from' is not specified 49 | map[e.name] = map[e.name] || {}; 50 | for (var n = 0 ; n < from.length ; n++) 51 | map[e.name][from[n]] = e.to || from[n]; // allow no-op transition if 'to' is not specified 52 | }; 53 | 54 | if (initial) { 55 | initial.event = initial.event || 'startup'; 56 | add({ name: initial.event, from: 'none', to: initial.state }); 57 | } 58 | 59 | for(var n = 0 ; n < events.length ; n++) 60 | add(events[n]); 61 | 62 | for(var name in map) { 63 | if (map.hasOwnProperty(name)) 64 | fsm[name] = StateMachine.buildEvent(name, map[name]); 65 | } 66 | 67 | for(var name in callbacks) { 68 | if (callbacks.hasOwnProperty(name)) 69 | fsm[name] = callbacks[name] 70 | } 71 | 72 | fsm.current = 'none'; 73 | fsm.is = function(state) { return (state instanceof Array) ? (state.indexOf(this.current) >= 0) : (this.current === state); }; 74 | fsm.can = function(event) { return !this.transition && (map[event].hasOwnProperty(this.current) || map[event].hasOwnProperty(StateMachine.WILDCARD)); } 75 | fsm.cannot = function(event) { return !this.can(event); }; 76 | fsm.error = cfg.error || function(name, from, to, args, error, msg, e) { throw e || msg; }; // default behavior when something unexpected happens is to throw an exception, but caller can override this behavior if desired (see github issue #3 and #17) 77 | 78 | fsm.isFinished = function() { return this.is(terminal); }; 79 | 80 | if (initial && !initial.defer) 81 | fsm[initial.event](); 82 | 83 | return fsm; 84 | 85 | }, 86 | 87 | //=========================================================================== 88 | 89 | doCallback: function(fsm, func, name, from, to, args) { 90 | if (func) { 91 | try { 92 | return func.apply(fsm, [name, from, to].concat(args)); 93 | } 94 | catch(e) { 95 | return fsm.error(name, from, to, args, StateMachine.Error.INVALID_CALLBACK, "an exception occurred in a caller-provided callback function", e); 96 | } 97 | } 98 | }, 99 | 100 | beforeAnyEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbeforeevent'], name, from, to, args); }, 101 | afterAnyEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafterevent'] || fsm['onevent'], name, from, to, args); }, 102 | leaveAnyState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleavestate'], name, from, to, args); }, 103 | enterAnyState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenterstate'] || fsm['onstate'], name, from, to, args); }, 104 | changeState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onchangestate'], name, from, to, args); }, 105 | 106 | beforeThisEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbefore' + name], name, from, to, args); }, 107 | afterThisEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafter' + name] || fsm['on' + name], name, from, to, args); }, 108 | leaveThisState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleave' + from], name, from, to, args); }, 109 | enterThisState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenter' + to] || fsm['on' + to], name, from, to, args); }, 110 | 111 | beforeEvent: function(fsm, name, from, to, args) { 112 | if ((false === StateMachine.beforeThisEvent(fsm, name, from, to, args)) || 113 | (false === StateMachine.beforeAnyEvent( fsm, name, from, to, args))) 114 | return false; 115 | }, 116 | 117 | afterEvent: function(fsm, name, from, to, args) { 118 | StateMachine.afterThisEvent(fsm, name, from, to, args); 119 | StateMachine.afterAnyEvent( fsm, name, from, to, args); 120 | }, 121 | 122 | leaveState: function(fsm, name, from, to, args) { 123 | var specific = StateMachine.leaveThisState(fsm, name, from, to, args), 124 | general = StateMachine.leaveAnyState( fsm, name, from, to, args); 125 | if ((false === specific) || (false === general)) 126 | return false; 127 | else if ((StateMachine.ASYNC === specific) || (StateMachine.ASYNC === general)) 128 | return StateMachine.ASYNC; 129 | }, 130 | 131 | enterState: function(fsm, name, from, to, args) { 132 | StateMachine.enterThisState(fsm, name, from, to, args); 133 | StateMachine.enterAnyState( fsm, name, from, to, args); 134 | }, 135 | 136 | //=========================================================================== 137 | 138 | buildEvent: function(name, map) { 139 | return function() { 140 | 141 | var from = this.current; 142 | var to = map[from] || map[StateMachine.WILDCARD] || from; 143 | var args = Array.prototype.slice.call(arguments); // turn arguments into pure array 144 | 145 | if (this.transition) 146 | return this.error(name, from, to, args, StateMachine.Error.PENDING_TRANSITION, "event " + name + " inappropriate because previous transition did not complete"); 147 | 148 | if (this.cannot(name)) 149 | return this.error(name, from, to, args, StateMachine.Error.INVALID_TRANSITION, "event " + name + " inappropriate in current state " + this.current); 150 | 151 | if (false === StateMachine.beforeEvent(this, name, from, to, args)) 152 | return StateMachine.Result.CANCELLED; 153 | 154 | if (from === to) { 155 | StateMachine.afterEvent(this, name, from, to, args); 156 | return StateMachine.Result.NOTRANSITION; 157 | } 158 | 159 | // prepare a transition method for use EITHER lower down, or by caller if they want an async transition (indicated by an ASYNC return value from leaveState) 160 | var fsm = this; 161 | this.transition = function() { 162 | fsm.transition = null; // this method should only ever be called once 163 | fsm.current = to; 164 | StateMachine.enterState( fsm, name, from, to, args); 165 | StateMachine.changeState(fsm, name, from, to, args); 166 | StateMachine.afterEvent( fsm, name, from, to, args); 167 | return StateMachine.Result.SUCCEEDED; 168 | }; 169 | this.transition.cancel = function() { // provide a way for caller to cancel async transition if desired (issue #22) 170 | fsm.transition = null; 171 | StateMachine.afterEvent(fsm, name, from, to, args); 172 | } 173 | 174 | var leave = StateMachine.leaveState(this, name, from, to, args); 175 | if (false === leave) { 176 | this.transition = null; 177 | return StateMachine.Result.CANCELLED; 178 | } 179 | else if (StateMachine.ASYNC === leave) { 180 | return StateMachine.Result.PENDING; 181 | } 182 | else { 183 | if (this.transition) // need to check in case user manually called transition() but forgot to return StateMachine.ASYNC 184 | return this.transition(); 185 | } 186 | 187 | }; 188 | } 189 | 190 | }; // StateMachine 191 | 192 | //=========================================================================== 193 | 194 | if ("function" === typeof define) { 195 | define(function(require) { return StateMachine; }); 196 | } 197 | else { 198 | window.StateMachine = StateMachine; 199 | } 200 | 201 | }(this)); 202 | 203 | -------------------------------------------------------------------------------- /js/game/vendor/stats.js: -------------------------------------------------------------------------------- 1 | // stats.js r6 - http://github.com/mrdoob/stats.js 2 | var Stats=function(){function s(a,g,d){var f,c,e;for(c=0;c<30;c++)for(f=0;f<73;f++)e=(f+c*74)*4,a[e]=a[e+4],a[e+1]=a[e+5],a[e+2]=a[e+6];for(c=0;c<30;c++)e=(73+c*74)*4,c'+n+" MS ("+z+"-"+A+")";o.putImageData(B,0,0);F=j;if(j> 9 | v+1E3){l=Math.round(u*1E3/(j-v));w=Math.min(w,l);x=Math.max(x,l);s(y.data,Math.min(30,30-l/100*30),"fps");d.innerHTML=''+l+" FPS ("+w+"-"+x+")";m.putImageData(y,0,0);if(t==3)p=performance.memory.usedJSHeapSize*9.54E-7,C=Math.min(C,p),D=Math.max(D,p),s(E.data,Math.min(30,30-p/2),"mb"),i.innerHTML=''+Math.round(p)+" MB ("+Math.round(C)+"-"+Math.round(D)+")",q.putImageData(E,0,0);v=j;u=0}}}}; 10 | 11 | -------------------------------------------------------------------------------- /levels/level1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/levels/level1.png -------------------------------------------------------------------------------- /levels/level10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/levels/level10.png -------------------------------------------------------------------------------- /levels/level2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/levels/level2.png -------------------------------------------------------------------------------- /levels/level3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/levels/level3.png -------------------------------------------------------------------------------- /levels/level4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/levels/level4.png -------------------------------------------------------------------------------- /levels/level5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/levels/level5.png -------------------------------------------------------------------------------- /levels/level6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/levels/level6.png -------------------------------------------------------------------------------- /levels/level7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/levels/level7.png -------------------------------------------------------------------------------- /levels/level8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/levels/level8.png -------------------------------------------------------------------------------- /levels/level9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/levels/level9.png -------------------------------------------------------------------------------- /levels/reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/levels/reference.png -------------------------------------------------------------------------------- /levels/testlevel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/levels/testlevel.png -------------------------------------------------------------------------------- /levels/trainer1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/levels/trainer1.png -------------------------------------------------------------------------------- /levels/trainer2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/levels/trainer2.png -------------------------------------------------------------------------------- /levels/trainer3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/levels/trainer3.png -------------------------------------------------------------------------------- /levels/trainer4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/levels/trainer4.png -------------------------------------------------------------------------------- /levels/trainer5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/levels/trainer5.png -------------------------------------------------------------------------------- /levels/trainer6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/levels/trainer6.png -------------------------------------------------------------------------------- /levels/trainer7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/levels/trainer7.png -------------------------------------------------------------------------------- /sounds/collectfood.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/collectfood.mp3 -------------------------------------------------------------------------------- /sounds/collectfood.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/collectfood.ogg -------------------------------------------------------------------------------- /sounds/collectgold.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/collectgold.mp3 -------------------------------------------------------------------------------- /sounds/collectgold.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/collectgold.ogg -------------------------------------------------------------------------------- /sounds/collectkey.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/collectkey.mp3 -------------------------------------------------------------------------------- /sounds/collectkey.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/collectkey.ogg -------------------------------------------------------------------------------- /sounds/collectpotion.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/collectpotion.mp3 -------------------------------------------------------------------------------- /sounds/collectpotion.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/collectpotion.ogg -------------------------------------------------------------------------------- /sounds/exitlevel.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/exitlevel.mp3 -------------------------------------------------------------------------------- /sounds/exitlevel.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/exitlevel.ogg -------------------------------------------------------------------------------- /sounds/femalepain1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/femalepain1.mp3 -------------------------------------------------------------------------------- /sounds/femalepain1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/femalepain1.ogg -------------------------------------------------------------------------------- /sounds/femalepain2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/femalepain2.mp3 -------------------------------------------------------------------------------- /sounds/femalepain2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/femalepain2.ogg -------------------------------------------------------------------------------- /sounds/fireelf.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/fireelf.mp3 -------------------------------------------------------------------------------- /sounds/fireelf.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/fireelf.ogg -------------------------------------------------------------------------------- /sounds/firevalkyrie.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/firevalkyrie.mp3 -------------------------------------------------------------------------------- /sounds/firevalkyrie.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/firevalkyrie.ogg -------------------------------------------------------------------------------- /sounds/firewarrior.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/firewarrior.mp3 -------------------------------------------------------------------------------- /sounds/firewarrior.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/firewarrior.ogg -------------------------------------------------------------------------------- /sounds/firewizard.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/firewizard.mp3 -------------------------------------------------------------------------------- /sounds/firewizard.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/firewizard.ogg -------------------------------------------------------------------------------- /sounds/gameover.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/gameover.mp3 -------------------------------------------------------------------------------- /sounds/gameover.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/gameover.ogg -------------------------------------------------------------------------------- /sounds/generatordeath.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/generatordeath.mp3 -------------------------------------------------------------------------------- /sounds/generatordeath.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/generatordeath.ogg -------------------------------------------------------------------------------- /sounds/highscore.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/highscore.mp3 -------------------------------------------------------------------------------- /sounds/highscore.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/highscore.ogg -------------------------------------------------------------------------------- /sounds/malepain1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/malepain1.mp3 -------------------------------------------------------------------------------- /sounds/malepain1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/malepain1.ogg -------------------------------------------------------------------------------- /sounds/malepain2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/malepain2.mp3 -------------------------------------------------------------------------------- /sounds/malepain2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/malepain2.ogg -------------------------------------------------------------------------------- /sounds/monsterdeath1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/monsterdeath1.mp3 -------------------------------------------------------------------------------- /sounds/monsterdeath1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/monsterdeath1.ogg -------------------------------------------------------------------------------- /sounds/monsterdeath2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/monsterdeath2.mp3 -------------------------------------------------------------------------------- /sounds/monsterdeath2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/monsterdeath2.ogg -------------------------------------------------------------------------------- /sounds/monsterdeath3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/monsterdeath3.mp3 -------------------------------------------------------------------------------- /sounds/monsterdeath3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/monsterdeath3.ogg -------------------------------------------------------------------------------- /sounds/music.bloodyhalo.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/music.bloodyhalo.mp3 -------------------------------------------------------------------------------- /sounds/music.bloodyhalo.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/music.bloodyhalo.ogg -------------------------------------------------------------------------------- /sounds/music.citrinitas.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/music.citrinitas.mp3 -------------------------------------------------------------------------------- /sounds/music.citrinitas.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/music.citrinitas.ogg -------------------------------------------------------------------------------- /sounds/music.fleshandsteel.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/music.fleshandsteel.mp3 -------------------------------------------------------------------------------- /sounds/music.fleshandsteel.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/music.fleshandsteel.ogg -------------------------------------------------------------------------------- /sounds/music.lostcorridors.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/music.lostcorridors.mp3 -------------------------------------------------------------------------------- /sounds/music.lostcorridors.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/music.lostcorridors.ogg -------------------------------------------------------------------------------- /sounds/music.mountingassault.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/music.mountingassault.mp3 -------------------------------------------------------------------------------- /sounds/music.mountingassault.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/music.mountingassault.ogg -------------------------------------------------------------------------------- /sounds/music.phantomdrone.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/music.phantomdrone.mp3 -------------------------------------------------------------------------------- /sounds/music.phantomdrone.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/music.phantomdrone.ogg -------------------------------------------------------------------------------- /sounds/music.thebeginning.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/music.thebeginning.mp3 -------------------------------------------------------------------------------- /sounds/music.thebeginning.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/music.thebeginning.ogg -------------------------------------------------------------------------------- /sounds/music.warbringer.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/music.warbringer.mp3 -------------------------------------------------------------------------------- /sounds/music.warbringer.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/music.warbringer.ogg -------------------------------------------------------------------------------- /sounds/opendoor.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/opendoor.mp3 -------------------------------------------------------------------------------- /sounds/opendoor.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/opendoor.ogg -------------------------------------------------------------------------------- /sounds/victory.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/victory.mp3 -------------------------------------------------------------------------------- /sounds/victory.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/victory.ogg -------------------------------------------------------------------------------- /sounds/weak.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/weak.mp3 -------------------------------------------------------------------------------- /sounds/weak.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakesgordon/javascript-gauntlet/2f9020dc642ba3bf98fd20a5251bed5c9d84d924/sounds/weak.ogg --------------------------------------------------------------------------------