├── .DS_Store ├── README.md ├── crystalQuest.css ├── favicon.ico ├── img ├── .DS_Store ├── alien_bullet1.png ├── alien_bullet2.png ├── alien_bullet3.png ├── basic_alien.png ├── basic_alien_sp.png ├── big_crystal.png ├── blob_alien.png ├── bomb.png ├── bullet_alien.png ├── computer_alien.png ├── computer_hash.png ├── crystal.png ├── cs_alien.png ├── four_leg_alien.png ├── laser_alien.png ├── life.png ├── rm_basic_alien.png ├── rm_blob_alien.png ├── rm_bullet_alien.png ├── rm_computer_alien.png ├── rm_cs_alien.png ├── rm_four_leg_alien.png ├── rm_shooter_alien.png ├── rm_x_alien.png ├── shooter_alien.png └── x_alien.png ├── index.html ├── jquery-2.1.1.js ├── keymaster.js ├── lib ├── .DS_Store ├── CSAlien.js ├── XSAlien.js ├── alienBullet.js ├── asteroid.js ├── basicAlien.js ├── bigCrystal.js ├── blobAlien.js ├── bomb.js ├── bullet.js ├── bulletAlien.js ├── clock.js ├── computerAlien.js ├── computerHash.js ├── crystal.js ├── crystalPoints.js ├── fourLegAlien.js ├── game.js ├── gate.js ├── laserAlien.js ├── movingObject.js ├── points.js ├── portal.js ├── ship.js ├── shooterAlien.js ├── utils.js └── wave.js └── sounds ├── .DS_Store ├── crystal.wav ├── laser.wav ├── levelSuccess.wav ├── reverbLaser.wav ├── shortLaser.wav ├── success.mp3 └── tristeza.wav /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/.DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CrystalQuest 2 | This is a remake of the classic game designed for the 1987 Macintosh. It uses HTML5 Canvas, Sprites, Javascript, and jQuery. 3 | 4 | Crystal Quest features **fifteen** different levels and **eight** different kinds of aliens (so far!). Each alien has its own look, its own dangers, and its own way of moving. Each wave increases in difficulty, and features increasingly perilous foes. 5 | 6 | See if you can beat it! 7 | 8 | [Play Crystal Quest](https://haleymt.github.io/CrystalQuest) 9 | 10 | ### Gameplay 11 | * Move your ship around using the direction keys 12 | * Scoop up all the crystals in order to open the gate. 13 | * Don't run into the aliens, asteroids, or portals or you'll lose a life. 14 | * Lose all your lives and it's game over. 15 | * You can use your cherry bombs to clear the screen if you're in danger. 16 | * If you collect the big crystal you'll gain a life and some bonus points. 17 | * Press the space bar to shoot some aliens. 18 | 19 | ### Aliens 20 | 21 | ![Basic Alien](img/rm_basic_alien.png) 22 | ![Shooter Alien](img/rm_shooter_alien.png) 23 | ![Computer Alien](img/rm_computer_alien.png) 24 | ![Blob Alien](img/rm_blob_alien.png) 25 | ![Computer Shooter Alien](img/rm_cs_alien.png) 26 | ![X Shooter Alien](img/rm_x_alien.png) 27 | ![Four Leg Alien](img/rm_four_leg_alien.png) 28 | 29 | Each alien changes direction regularly. Accomplishing this requires two parts. First, I have to create a random movement vector for the alien: 30 | ```javascript 31 | Util.randomVec = function (length) { 32 | var deg = 2 * Math.PI * Math.random(); 33 | return Util.scale([Math.sin(deg), Math.cos(deg)], length); 34 | }; 35 | 36 | Util.scale = function (vec, m) { 37 | return [vec[0] * m, vec[1] * m]; 38 | }; 39 | ``` 40 | Next, I start an interval running when I create the alien that changes its movement vector every half second: 41 | 42 | ```javascript 43 | var that = this; 44 | this.dirInterval = setInterval( function () { 45 | that.vel = window.CrystalQuest.Util.randomVec(3); 46 | }, 500); 47 | ``` 48 | If I want to change how fast an alien moves, I just change the argument for `randomVec`. A similar principle applies for creating and moving bullets. 49 | 50 | If an alien hits a wall I want it to bounce off in an opposite but random direction: 51 | ```javascript 52 | Wave.prototype.bounce = function (object) { 53 | xVel = object.vel[0]; 54 | yVel = object.vel[1]; 55 | var dirs = [-1, 1]; 56 | var idx = Math.floor(Math.random() * 2); 57 | 58 | if (xVel < 0 && yVel < 0) { 59 | object.vel = [-xVel, dirs[idx] * yVel] 60 | } else if (xVel < 0 && yVel > 0) { 61 | object.vel = [dirs[idx] * xVel, -yVel] 62 | } else if (xVel > 0 && yVel > 0) { 63 | object.vel = [-xVel, dirs[idx] * yVel] 64 | } else if (xVel > 0 && yVel < 0) { 65 | object.vel = [dirs[idx] * xVel, -yVel] 66 | } else if (xVel === 0 && yVel !== 0) { 67 | object.vel = [xVel, -yVel] 68 | } else if (yVel === 0 && xVel !== 0) { 69 | object.vel = [-xVel, yVel] 70 | } 71 | }; 72 | ``` 73 | ### Waves 74 | Each wave's attributes are defined in an Object: 75 | 76 | ```javascript 77 | Game.WAVE_EIGHT = { 78 | numAsteroids: 10, 79 | numBombs: 1, 80 | numCrystals: 25, 81 | numComputerAliens: 5, 82 | numBasicAliens: 1, 83 | numBigCrystals: 0, 84 | numPoints: 3 85 | }; 86 | ``` 87 | The array `Game.WAVES` holds all of these objects. The game keeps track of what wave it's on by incrementing a counter that refers to an index in `Game.WAVES`. 88 | 89 | ### Intervals 90 | Javascript's `setInterval` is notoriously tricky. It is strict about scope, it doesn't return any values, and any child `setInterval`s nested inside of another parent `setInterval` will *keep running* even after a parent interval is cleared. This poses potentially huge performance problems. To fix it, I carefully clear any intervals I create throughout the game: 91 | 92 | * All intervals are defined at the top level (`this.interval` instead of `var interval`). Which means I can define a method on the top level that clears those intervals: 93 | ```javascript 94 | Game.prototype.stop = function() { 95 | if (this.interval) { 96 | clearInterval(this.interval); 97 | this.interval = null; 98 | } 99 | }; 100 | ``` 101 | I can then call `this.stop()` during the winning or losing scenario. 102 | * Upon destroying an alien, all of its intervals get cleared as well: 103 | ```javascript 104 | if (object instanceof CrystalQuest.ShooterAlien) { 105 | clearInterval(object.shootInterval); 106 | object.shootInterval = null; 107 | this.aliens.splice(this.aliens.indexOf(object), 1); 108 | ``` 109 | * Upon finishing a level, I iterate through all the remaining aliens, and clear their movement and shooting intervals as well. 110 | 111 | ### High Scores 112 | For now, high scores are stored in the localStorage and not in a database. 113 | 114 | If you're running the game for the first time, it will create an item called `"high-scores"` upon initialization and set it to an empty array: 115 | ```javascript 116 | Game.prototype.run = function () { 117 | if (this.scores === null) { 118 | localStorage.setItem("high-scores", JSON.stringify([])); 119 | this.scores = JSON.parse(localStorage.getItem("high-scores")); 120 | } 121 | ... 122 | }; 123 | ``` 124 | The array will eventually contain a collection of Objects containing a name and score, which we'll set at the end of a wave like so: 125 | ```javascript 126 | Game.prototype.lose = function() { 127 | ... 128 | var score = parseInt($('#score').text()) 129 | if ((this.scores.length < 10) || (score > this.scores[this.scores.length - 1]['score'])) { 130 | var name = prompt("You got a new high score! Enter your initials: "); 131 | if ((name !== "") && (name !== null)) { 132 | if (this.scores.length === 10) { 133 | this.scores.pop(); 134 | } 135 | this.scores.push({'name': name, 'score': score}) 136 | this.scores.sort( function(a, b) { 137 | return b['score'] - a['score']; 138 | }); 139 | localStorage.setItem("high-scores", JSON.stringify(this.scores)); 140 | } 141 | this.showHighScores(); 142 | } 143 | ... 144 | }; 145 | ``` 146 | And then at the beginning of a new game, we'll just grab all the scores: 147 | ```javascript 148 | var Game = window.CrystalQuest.Game = function (xDim, yDim, ctx) { 149 | ... 150 | this.scores = JSON.parse(localStorage.getItem("high-scores")); 151 | ... 152 | }; 153 | ``` 154 | Easy peasy. 155 | -------------------------------------------------------------------------------- /crystalQuest.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: black; 3 | font-family: sans-serif; 4 | } 5 | 6 | .big-container { 7 | margin-left:auto; 8 | margin-right:auto; 9 | width: 790px; 10 | height: 560px; 11 | } 12 | 13 | #game-canvas { 14 | border-bottom: 1px solid white; 15 | } 16 | 17 | #info { 18 | position: absolute; 19 | left: 0; 20 | right: 0; 21 | color: white; 22 | } 23 | 24 | #info strong { 25 | line-height: 23px; 26 | } 27 | 28 | .instructions { 29 | padding-left:50px; 30 | float: left; 31 | padding-top: 10px; 32 | padding-bottom: 15px; 33 | } 34 | 35 | .keys { 36 | padding-right:50px; 37 | float: right; 38 | padding-top: 10px; 39 | padding-bottom: 15px; 40 | } 41 | 42 | code { 43 | border: 1px solid white; 44 | border-radius: 3px; 45 | padding: 1px 3px; 46 | } 47 | 48 | #container { 49 | height: 550px; 50 | position: relative; 51 | width: 780px; 52 | background: black; 53 | border: 1px solid white; 54 | border-bottom: 0px; 55 | } 56 | 57 | #controls { 58 | position: absolute; 59 | top: 50% !important; 60 | left: 50%; 61 | -webkit-transform: translate(-50%, -50%); 62 | -ms-transform: translate(-50%, -50%); 63 | -o-transform: translate(-50%, -50%); 64 | transform: translate(-50%, -50%); 65 | color: white; 66 | padding: 0px; 67 | display: inline-block 68 | } 69 | 70 | #controls ul { 71 | list-style-type: none; 72 | padding: 0px; 73 | font-family: 'Audiowide', cursive; 74 | } 75 | 76 | #controls p { 77 | font-family: 'Audiowide', cursive; 78 | } 79 | 80 | #controls h1 { 81 | font-family: 'Audiowide', cursive; 82 | } 83 | 84 | #controls h2, 85 | #controls h3 { 86 | font-family: 'Audiowide', cursive; 87 | color: rgb(255, 204, 0); 88 | } 89 | 90 | .other-game { 91 | display: inline-block; 92 | width: 100%; 93 | margin-top: 20px; 94 | font-size: 14px; 95 | margin-bottom: 20px; 96 | } 97 | 98 | .other-game a { 99 | font-family: 'Bungee Shade', cursive; 100 | color: white; 101 | text-decoration: none; 102 | font-size: 16px; 103 | } 104 | 105 | .other-game a:hover { 106 | opacity: 0.95; 107 | } 108 | 109 | #controls button { 110 | font-family: 'Press Start 2P', cursive; 111 | padding: 10px; 112 | cursor: pointer; 113 | border-radius: 3px; 114 | border: none; 115 | outline: none !important; 116 | background: white; 117 | margin-bottom: 10px; 118 | margin-right: 5px; 119 | margin-left: 5px; 120 | } 121 | 122 | #controls button:hover { 123 | background: rgb(255, 204, 0); 124 | } 125 | 126 | #controls button:focus { 127 | background: rgb(255, 223, 96); 128 | } 129 | 130 | #status-bar { 131 | height: 50px; 132 | width: 100%; 133 | background: black; 134 | color: white; 135 | border-bottom: 1px solid white; 136 | font-size: 20px; 137 | font-weight: bold; 138 | } 139 | 140 | #status-bar ul { 141 | margin: 0; 142 | padding: 0px; 143 | list-style-type: none; 144 | width: 100%; 145 | display: flex; 146 | align-items: center; 147 | justify-content: center; 148 | } 149 | 150 | #status-bar ul li { 151 | display: inline; 152 | padding: 10px; 153 | } 154 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/favicon.ico -------------------------------------------------------------------------------- /img/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/.DS_Store -------------------------------------------------------------------------------- /img/alien_bullet1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/alien_bullet1.png -------------------------------------------------------------------------------- /img/alien_bullet2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/alien_bullet2.png -------------------------------------------------------------------------------- /img/alien_bullet3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/alien_bullet3.png -------------------------------------------------------------------------------- /img/basic_alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/basic_alien.png -------------------------------------------------------------------------------- /img/basic_alien_sp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/basic_alien_sp.png -------------------------------------------------------------------------------- /img/big_crystal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/big_crystal.png -------------------------------------------------------------------------------- /img/blob_alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/blob_alien.png -------------------------------------------------------------------------------- /img/bomb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/bomb.png -------------------------------------------------------------------------------- /img/bullet_alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/bullet_alien.png -------------------------------------------------------------------------------- /img/computer_alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/computer_alien.png -------------------------------------------------------------------------------- /img/computer_hash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/computer_hash.png -------------------------------------------------------------------------------- /img/crystal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/crystal.png -------------------------------------------------------------------------------- /img/cs_alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/cs_alien.png -------------------------------------------------------------------------------- /img/four_leg_alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/four_leg_alien.png -------------------------------------------------------------------------------- /img/laser_alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/laser_alien.png -------------------------------------------------------------------------------- /img/life.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/life.png -------------------------------------------------------------------------------- /img/rm_basic_alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/rm_basic_alien.png -------------------------------------------------------------------------------- /img/rm_blob_alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/rm_blob_alien.png -------------------------------------------------------------------------------- /img/rm_bullet_alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/rm_bullet_alien.png -------------------------------------------------------------------------------- /img/rm_computer_alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/rm_computer_alien.png -------------------------------------------------------------------------------- /img/rm_cs_alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/rm_cs_alien.png -------------------------------------------------------------------------------- /img/rm_four_leg_alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/rm_four_leg_alien.png -------------------------------------------------------------------------------- /img/rm_shooter_alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/rm_shooter_alien.png -------------------------------------------------------------------------------- /img/rm_x_alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/rm_x_alien.png -------------------------------------------------------------------------------- /img/shooter_alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/shooter_alien.png -------------------------------------------------------------------------------- /img/x_alien.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/img/x_alien.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Crystal Quest 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 | 16 | 17 |
18 | 19 | 20 | Instructions

21 | Collect all the crystals and escape out the gate
22 | Don't run into any aliens or asteroids!
23 | Use your cherry bombs to clear the screen
24 | Catch the big crystal for a bonus prize 25 |
26 | 27 | Keys

28 | or w s a d to move
29 | r to stop
30 | space to shoot
31 | shift to FLASHBOMB
32 | esc to return to start menu
33 |
34 |
Want to play another game? Try Snake.
35 |
36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /keymaster.js: -------------------------------------------------------------------------------- 1 | // keymaster.js 2 | // (c) 2011-2013 Thomas Fuchs 3 | // keymaster.js may be freely distributed under the MIT license. 4 | 5 | ;(function(global){ 6 | var k, 7 | _handlers = {}, 8 | _mods = { 16: false, 18: false, 17: false, 91: false }, 9 | _scope = 'all', 10 | // modifier keys 11 | _MODIFIERS = { 12 | '⌥': 18, alt: 18, option: 18, 13 | '⌃': 17, ctrl: 17, control: 17, 14 | '⌘': 91, command: 91 15 | }, 16 | // special keys 17 | _MAP = { 18 | '⇧': 16, shift: 16, 19 | backspace: 8, tab: 9, clear: 12, 20 | enter: 13, 'return': 13, 21 | esc: 27, escape: 27, space: 32, 22 | left: 37, up: 38, 23 | right: 39, down: 40, 24 | del: 46, 'delete': 46, 25 | home: 36, end: 35, 26 | pageup: 33, pagedown: 34, 27 | ',': 188, '.': 190, '/': 191, 28 | '`': 192, '-': 189, '=': 187, 29 | ';': 186, '\'': 222, 30 | '[': 219, ']': 221, '\\': 220 31 | }, 32 | code = function(x){ 33 | return _MAP[x] || x.toUpperCase().charCodeAt(0); 34 | }, 35 | _downKeys = []; 36 | 37 | for(k=1;k<20;k++) _MAP['f'+k] = 111+k; 38 | 39 | // IE doesn't support Array#indexOf, so have a simple replacement 40 | function index(array, item){ 41 | var i = array.length; 42 | while(i--) if(array[i]===item) return i; 43 | return -1; 44 | } 45 | 46 | // for comparing mods before unassignment 47 | function compareArray(a1, a2) { 48 | if (a1.length != a2.length) return false; 49 | for (var i = 0; i < a1.length; i++) { 50 | if (a1[i] !== a2[i]) return false; 51 | } 52 | return true; 53 | } 54 | 55 | var modifierMap = { 56 | 16:'shiftKey', 57 | 18:'altKey', 58 | 17:'ctrlKey', 59 | 91:'metaKey' 60 | }; 61 | function updateModifierKey(event) { 62 | for(k in _mods) _mods[k] = event[modifierMap[k]]; 63 | }; 64 | 65 | // handle keydown event 66 | function dispatch(event) { 67 | var key, handler, k, i, modifiersMatch, scope; 68 | key = event.keyCode; 69 | 70 | if (index(_downKeys, key) == -1) { 71 | _downKeys.push(key); 72 | } 73 | 74 | // if a modifier key, set the key. property to true and return 75 | if(key == 93 || key == 224) key = 91; // right command on webkit, command on Gecko 76 | if(key in _mods) { 77 | _mods[key] = true; 78 | // 'assignKey' from inside this closure is exported to window.key 79 | for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = true; 80 | return; 81 | } 82 | updateModifierKey(event); 83 | 84 | // see if we need to ignore the keypress (filter() can can be overridden) 85 | // by default ignore key presses if a select, textarea, or input is focused 86 | if(!assignKey.filter.call(this, event)) return; 87 | 88 | // abort if no potentially matching shortcuts found 89 | if (!(key in _handlers)) return; 90 | 91 | scope = getScope(); 92 | 93 | // for each potential shortcut 94 | for (i = 0; i < _handlers[key].length; i++) { 95 | handler = _handlers[key][i]; 96 | 97 | // see if it's in the current scope 98 | if(handler.scope == scope || handler.scope == 'all'){ 99 | // check if modifiers match if any 100 | modifiersMatch = handler.mods.length > 0; 101 | for(k in _mods) 102 | if((!_mods[k] && index(handler.mods, +k) > -1) || 103 | (_mods[k] && index(handler.mods, +k) == -1)) modifiersMatch = false; 104 | // call the handler and stop the event if neccessary 105 | if((handler.mods.length == 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91]) || modifiersMatch){ 106 | if(handler.method(event, handler)===false){ 107 | if(event.preventDefault) event.preventDefault(); 108 | else event.returnValue = false; 109 | if(event.stopPropagation) event.stopPropagation(); 110 | if(event.cancelBubble) event.cancelBubble = true; 111 | } 112 | } 113 | } 114 | } 115 | }; 116 | 117 | // unset modifier keys on keyup 118 | function clearModifier(event){ 119 | var key = event.keyCode, k, 120 | i = index(_downKeys, key); 121 | 122 | // remove key from _downKeys 123 | if (i >= 0) { 124 | _downKeys.splice(i, 1); 125 | } 126 | 127 | if(key == 93 || key == 224) key = 91; 128 | if(key in _mods) { 129 | _mods[key] = false; 130 | for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = false; 131 | } 132 | }; 133 | 134 | function resetModifiers() { 135 | for(k in _mods) _mods[k] = false; 136 | for(k in _MODIFIERS) assignKey[k] = false; 137 | }; 138 | 139 | // parse and assign shortcut 140 | function assignKey(key, scope, method){ 141 | var keys, mods; 142 | keys = getKeys(key); 143 | if (method === undefined) { 144 | method = scope; 145 | scope = 'all'; 146 | } 147 | 148 | // for each shortcut 149 | for (var i = 0; i < keys.length; i++) { 150 | // set modifier keys if any 151 | mods = []; 152 | key = keys[i].split('+'); 153 | if (key.length > 1){ 154 | mods = getMods(key); 155 | key = [key[key.length-1]]; 156 | } 157 | // convert to keycode and... 158 | key = key[0] 159 | key = code(key); 160 | // ...store handler 161 | if (!(key in _handlers)) _handlers[key] = []; 162 | _handlers[key].push({ shortcut: keys[i], scope: scope, method: method, key: keys[i], mods: mods }); 163 | } 164 | }; 165 | 166 | // unbind all handlers for given key in current scope 167 | function unbindKey(key, scope) { 168 | var multipleKeys, keys, 169 | mods = [], 170 | i, j, obj; 171 | 172 | multipleKeys = getKeys(key); 173 | 174 | for (j = 0; j < multipleKeys.length; j++) { 175 | keys = multipleKeys[j].split('+'); 176 | 177 | if (keys.length > 1) { 178 | mods = getMods(keys); 179 | } 180 | 181 | key = keys[keys.length - 1]; 182 | key = code(key); 183 | 184 | if (scope === undefined) { 185 | scope = getScope(); 186 | } 187 | if (!_handlers[key]) { 188 | return; 189 | } 190 | for (i = 0; i < _handlers[key].length; i++) { 191 | obj = _handlers[key][i]; 192 | // only clear handlers if correct scope and mods match 193 | if (obj.scope === scope && compareArray(obj.mods, mods)) { 194 | _handlers[key][i] = {}; 195 | } 196 | } 197 | } 198 | }; 199 | 200 | // Returns true if the key with code 'keyCode' is currently down 201 | // Converts strings into key codes. 202 | function isPressed(keyCode) { 203 | if (typeof(keyCode)=='string') { 204 | keyCode = code(keyCode); 205 | } 206 | return index(_downKeys, keyCode) != -1; 207 | } 208 | 209 | function getPressedKeyCodes() { 210 | return _downKeys.slice(0); 211 | } 212 | 213 | function filter(event){ 214 | var tagName = (event.target || event.srcElement).tagName; 215 | // ignore keypressed in any elements that support keyboard data input 216 | return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA'); 217 | } 218 | 219 | // initialize key. to false 220 | for(k in _MODIFIERS) assignKey[k] = false; 221 | 222 | // set current scope (default 'all') 223 | function setScope(scope){ _scope = scope || 'all' }; 224 | function getScope(){ return _scope || 'all' }; 225 | 226 | // delete all handlers for a given scope 227 | function deleteScope(scope){ 228 | var key, handlers, i; 229 | 230 | for (key in _handlers) { 231 | handlers = _handlers[key]; 232 | for (i = 0; i < handlers.length; ) { 233 | if (handlers[i].scope === scope) handlers.splice(i, 1); 234 | else i++; 235 | } 236 | } 237 | }; 238 | 239 | // abstract key logic for assign and unassign 240 | function getKeys(key) { 241 | var keys; 242 | key = key.replace(/\s/g, ''); 243 | keys = key.split(','); 244 | if ((keys[keys.length - 1]) == '') { 245 | keys[keys.length - 2] += ','; 246 | } 247 | return keys; 248 | } 249 | 250 | // abstract mods logic for assign and unassign 251 | function getMods(key) { 252 | var mods = key.slice(0, key.length - 1); 253 | for (var mi = 0; mi < mods.length; mi++) 254 | mods[mi] = _MODIFIERS[mods[mi]]; 255 | return mods; 256 | } 257 | 258 | // cross-browser events 259 | function addEvent(object, event, method) { 260 | if (object.addEventListener) 261 | object.addEventListener(event, method, false); 262 | else if(object.attachEvent) 263 | object.attachEvent('on'+event, function(){ method(window.event) }); 264 | }; 265 | 266 | // set the handlers globally on document 267 | addEvent(document, 'keydown', function(event) { dispatch(event) }); // Passing _scope to a callback to ensure it remains the same by execution. Fixes #48 268 | addEvent(document, 'keyup', clearModifier); 269 | 270 | // reset modifiers to false whenever the window is (re)focused. 271 | addEvent(window, 'focus', resetModifiers); 272 | 273 | // store previously defined key 274 | var previousKey = global.key; 275 | 276 | // restore previously defined key and return reference to our key object 277 | function noConflict() { 278 | var k = global.key; 279 | global.key = previousKey; 280 | return k; 281 | } 282 | 283 | // set window.key and window.key.set/get/deleteScope, and the default filter 284 | global.key = assignKey; 285 | global.key.setScope = setScope; 286 | global.key.getScope = getScope; 287 | global.key.deleteScope = deleteScope; 288 | global.key.filter = filter; 289 | global.key.isPressed = isPressed; 290 | global.key.getPressedKeyCodes = getPressedKeyCodes; 291 | global.key.noConflict = noConflict; 292 | global.key.unbind = unbindKey; 293 | 294 | if(typeof module !== 'undefined') module.exports = assignKey; 295 | 296 | })(this); 297 | -------------------------------------------------------------------------------- /lib/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haleymt/CrystalQuest/8bd29df2ca65d264fecf37aabd32659ba0a3c153/lib/.DS_Store -------------------------------------------------------------------------------- /lib/CSAlien.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (typeof CrystalQuest === "undefined") { 3 | window.CrystalQuest = {}; 4 | } 5 | var MovingObject = window.CrystalQuest.MovingObject; 6 | 7 | CrystalQuest.CSAlien = function (options) { 8 | MovingObject.call(this, options); 9 | this.radius = 12; 10 | this.img = 'img/cs_alien.png'; 11 | this.bulletImg = 'img/alien_bullet2.png'; 12 | }; 13 | 14 | window.CrystalQuest.Util.inherits(CrystalQuest.CSAlien, MovingObject); 15 | 16 | CrystalQuest.CSAlien.prototype.startIntervals = function() { 17 | var that = this; 18 | this.dirInterval = setInterval( function () { 19 | that.vel = window.CrystalQuest.Util.randomVec(3); 20 | if (that.wave.gameOver) { 21 | that.clearIntervals(); 22 | } 23 | }, 500); 24 | this.shootInterval = setInterval( function () { 25 | var bulletVel = window.CrystalQuest.Util.randomVec(2); 26 | var bullet = new window.CrystalQuest.AlienBullet({ 27 | pos: that.pos, 28 | vel: bulletVel, 29 | wave: that.wave, 30 | img: that.bulletImg 31 | }); 32 | if (that.pos[0] > 12 && that.pos[0] < (780 - 12)) { 33 | that.wave.alienBullets.push(bullet); 34 | } 35 | if (that.wave.gameOver || that.wave.indexOf(that) < 0) { 36 | that.clearIntervals(); 37 | } 38 | }, 500); 39 | }; 40 | })(); 41 | -------------------------------------------------------------------------------- /lib/XSAlien.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (typeof CrystalQuest === "undefined") { 3 | window.CrystalQuest = {}; 4 | } 5 | var MovingObject = window.CrystalQuest.MovingObject; 6 | 7 | CrystalQuest.XSAlien = function (options) { 8 | MovingObject.call(this, options); 9 | this.radius = 12; 10 | this.img = 'img/x_alien.png'; 11 | this.bulletImg = 'img/alien_bullet3.png'; 12 | }; 13 | 14 | window.CrystalQuest.Util.inherits(CrystalQuest.XSAlien, MovingObject); 15 | 16 | CrystalQuest.XSAlien.prototype.startIntervals = function() { 17 | var that = this; 18 | this.dirInterval = setInterval( function () { 19 | that.vel = window.CrystalQuest.Util.randomVec(3); 20 | if (that.wave.gameOver) { 21 | that.clearIntervals(); 22 | } 23 | }, 500); 24 | 25 | this.shootInterval = setInterval( function () { 26 | var bulletVel = [ 27 | that.vel[0] * 2, that.vel[1] * 2 28 | ]; 29 | var bullet = new window.CrystalQuest.AlienBullet({ 30 | pos: that.pos, 31 | vel: bulletVel, 32 | wave: that.wave, 33 | img: that.bulletImg 34 | }); 35 | if (that.pos[0] > 12 && that.pos[0] < (780 - 12)) { 36 | that.wave.alienBullets.push(bullet); 37 | } 38 | if (that.wave.gameOver || that.wave.indexOf(that) < 0) { 39 | that.clearIntervals(); 40 | } 41 | }, 200); 42 | } 43 | })(); 44 | -------------------------------------------------------------------------------- /lib/alienBullet.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (typeof CrystalQuest === "undefined") { 3 | window.CrystalQuest = {}; 4 | } 5 | 6 | var MovingObject = window.CrystalQuest.MovingObject; 7 | var Wave = window.CrystalQuest.Wave; 8 | 9 | var AlienBullet = CrystalQuest.AlienBullet = function (options) { 10 | MovingObject.call(this, options); 11 | this.radius = 4; 12 | this.img = options.img; 13 | }; 14 | 15 | window.CrystalQuest.Util.inherits(AlienBullet, MovingObject); 16 | CrystalQuest.AlienBullet.prototype.isBounceable = function () { 17 | return false; 18 | }; 19 | 20 | })(); 21 | -------------------------------------------------------------------------------- /lib/asteroid.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (typeof CrystalQuest === "undefined") { 3 | window.CrystalQuest = {}; 4 | } 5 | 6 | var Asteroid = CrystalQuest.Asteroid = function (options) { 7 | this.pos = options.pos; 8 | this.wave = options.wave; 9 | this.color = "#FF7519"; 10 | this.width = 20; 11 | }; 12 | 13 | Asteroid.prototype.draw = function(ctx) { 14 | ctx.fillStyle = this.color; 15 | ctx.fillRect(this.pos[0], this.pos[1], this.width, this.width); 16 | 17 | ctx.fill(); 18 | }; 19 | 20 | 21 | })(); 22 | -------------------------------------------------------------------------------- /lib/basicAlien.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (typeof CrystalQuest === "undefined") { 3 | window.CrystalQuest = {}; 4 | } 5 | var MovingObject = window.CrystalQuest.MovingObject; 6 | 7 | CrystalQuest.BasicAlien = function (options) { 8 | MovingObject.call(this, options); 9 | this.radius = 14; 10 | this.img = 'img/basic_alien_sp.png'; 11 | }; 12 | 13 | window.CrystalQuest.Util.inherits(CrystalQuest.BasicAlien, MovingObject); 14 | 15 | CrystalQuest.BasicAlien.prototype.startIntervals = function() { 16 | var that = this; 17 | this.dirInterval = setInterval( function () { 18 | that.vel = window.CrystalQuest.Util.randomVec(3); 19 | if (that.wave.gameOver) { 20 | that.clearIntervals(); 21 | } 22 | }, 500); 23 | }; 24 | 25 | })(); 26 | -------------------------------------------------------------------------------- /lib/bigCrystal.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (typeof CrystalQuest === "undefined") { 3 | window.CrystalQuest = {}; 4 | } 5 | var MovingObject = window.CrystalQuest.MovingObject; 6 | 7 | var BigCrystal = CrystalQuest.BigCrystal = function (options) { 8 | MovingObject.call(this, options); 9 | this.radius = 20; 10 | this.img = 'img/big_crystal.png'; 11 | this.num = Math.floor(Math.random() * (30000) + 2000); 12 | }; 13 | 14 | window.CrystalQuest.Util.inherits(CrystalQuest.BigCrystal, MovingObject); 15 | 16 | })(); 17 | -------------------------------------------------------------------------------- /lib/blobAlien.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (typeof CrystalQuest === "undefined") { 3 | window.CrystalQuest = {}; 4 | } 5 | var MovingObject = window.CrystalQuest.MovingObject; 6 | 7 | CrystalQuest.BlobAlien = function (options) { 8 | MovingObject.call(this, options); 9 | this.radius = 20; 10 | this.img = 'img/blob_alien.png'; 11 | }; 12 | 13 | window.CrystalQuest.Util.inherits(CrystalQuest.BlobAlien, MovingObject); 14 | 15 | CrystalQuest.BlobAlien.prototype.startIntervals = function() { 16 | var that = this; 17 | this.dirInterval = setInterval( function () { 18 | that.vel = window.CrystalQuest.Util.randomVec(1); 19 | if (that.wave.gameOver) { 20 | that.clearIntervals(); 21 | } 22 | }, 500); 23 | }; 24 | })(); 25 | -------------------------------------------------------------------------------- /lib/bomb.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (typeof CrystalQuest === "undefined") { 3 | window.CrystalQuest = {}; 4 | } 5 | 6 | var Bomb = CrystalQuest.Bomb = function (options) { 7 | this.pos = options.pos; 8 | this.wave = options.wave; 9 | this.width = 15; 10 | }; 11 | 12 | Bomb.prototype.draw = function(ctx) { 13 | var img = new Image(); 14 | x = this.pos[0]; 15 | y = this.pos[1]; 16 | img.src = 'img/bomb.png'; 17 | ctx.drawImage(img, x, y, 15, 15); 18 | }; 19 | 20 | 21 | })(); 22 | -------------------------------------------------------------------------------- /lib/bullet.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (typeof CrystalQuest === "undefined") { 3 | window.CrystalQuest = {}; 4 | } 5 | 6 | var MovingObject = window.CrystalQuest.MovingObject; 7 | var Wave = window.CrystalQuest.Wave; 8 | 9 | var Bullet = CrystalQuest.Bullet = function (options) { 10 | MovingObject.call(this, options); 11 | this.color = "#FFFFFF"; 12 | this.radius = 3; 13 | }; 14 | 15 | Bullet.SPEED = 3; 16 | 17 | window.CrystalQuest.Util.inherits(Bullet, MovingObject); 18 | CrystalQuest.Bullet.prototype.isBounceable = function () { 19 | return false; 20 | }; 21 | 22 | })(); 23 | -------------------------------------------------------------------------------- /lib/bulletAlien.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (typeof CrystalQuest === "undefined") { 3 | window.CrystalQuest = {}; 4 | } 5 | var MovingObject = window.CrystalQuest.MovingObject; 6 | 7 | CrystalQuest.BulletAlien = function (options) { 8 | MovingObject.call(this, options); 9 | this.radius = 7; 10 | this.img = 'img/bullet_alien.png'; 11 | }; 12 | 13 | window.CrystalQuest.Util.inherits(CrystalQuest.BulletAlien, MovingObject); 14 | 15 | CrystalQuest.BulletAlien.prototype.startIntervals = function() { 16 | var that = this; 17 | this.dirInterval = setInterval( function () { 18 | that.vel = window.CrystalQuest.Util.randomVec(4); 19 | if (that.wave.gameOver) { 20 | that.clearIntervals(); 21 | } 22 | }, 800); 23 | } 24 | })(); 25 | -------------------------------------------------------------------------------- /lib/clock.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (typeof CrystalQuest === "undefined") { 3 | window.CrystalQuest = {}; 4 | } 5 | 6 | var Clock = CrystalQuest.Clock = function() { 7 | this.minutes = 0 8 | this.seconds = 0 9 | } 10 | 11 | Clock.prototype.stop = function() { 12 | clearInterval(this.interval); 13 | this.interval = null; 14 | }; 15 | 16 | Clock.prototype.printTime = function() { 17 | if (this.minutes > 9) { 18 | if (this.seconds > 9) { 19 | $('#time').html(this.minutes + ":" + this.seconds); 20 | } else { 21 | $('#time').html(this.minutes + ":0" + this.seconds); 22 | } 23 | } else { 24 | if (this.seconds > 9) { 25 | $('#time').html("0" + this.minutes + ":" + this.seconds); 26 | } else { 27 | $('#time').html("0" + this.minutes + ":0" + this.seconds); 28 | } 29 | } 30 | }; 31 | 32 | Clock.prototype.run = function () { 33 | this.printTime(); 34 | var that = this; 35 | this.interval = setInterval(function () { 36 | that._tick(); 37 | }, 1000); 38 | }; 39 | 40 | Clock.prototype._tick = function () { 41 | var seconds = this.seconds + 1; 42 | if (seconds >= 60) { 43 | this.minutes += 1; 44 | this.seconds = 0 + (seconds - 60); 45 | } else { 46 | this.seconds += 1; 47 | } 48 | this.printTime(); 49 | }; 50 | 51 | })(); 52 | -------------------------------------------------------------------------------- /lib/computerAlien.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (typeof CrystalQuest === "undefined") { 3 | window.CrystalQuest = {}; 4 | } 5 | var MovingObject = window.CrystalQuest.MovingObject; 6 | 7 | CrystalQuest.ComputerAlien = function (options) { 8 | MovingObject.call(this, options); 9 | this.radius = 12; 10 | this.img = 'img/computer_alien.png'; 11 | }; 12 | 13 | window.CrystalQuest.Util.inherits(CrystalQuest.ComputerAlien, MovingObject); 14 | 15 | CrystalQuest.ComputerAlien.prototype.startIntervals = function() { 16 | var that = this; 17 | this.dirInterval = setInterval( function () { 18 | that.vel = window.CrystalQuest.Util.randomVec(5); 19 | if (!that.wave || that.wave.gameOver) { 20 | that.clearIntervals(); 21 | } 22 | }, 500); 23 | this.hashInterval = setInterval( function () { 24 | var hash = new window.CrystalQuest.ComputerHash({ 25 | pos: that.pos, 26 | wave: that.wave 27 | }); 28 | if (that.pos[0] > 12 && that.pos[0] < (780 - 12) && that.wave) { 29 | that.wave.asteroids.push(hash); 30 | } 31 | if (!that.wave || that.wave.gameOver) { 32 | that.clearIntervals(); 33 | } 34 | }, 5000); 35 | } 36 | 37 | })(); 38 | -------------------------------------------------------------------------------- /lib/computerHash.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (typeof CrystalQuest === "undefined") { 3 | window.CrystalQuest = {}; 4 | } 5 | 6 | var ComputerHash = CrystalQuest.ComputerHash = function (options) { 7 | this.pos = options.pos; 8 | this.wave = options.wave; 9 | this.width = 15; 10 | }; 11 | 12 | ComputerHash.prototype.draw = function(ctx) { 13 | var img = new Image(); 14 | x = this.pos[0] - 7.5; 15 | y = this.pos[1] - 7.5; 16 | img.src = 'img/computer_hash.png'; 17 | ctx.drawImage(img, x, y, 15, 15); 18 | }; 19 | 20 | })(); 21 | -------------------------------------------------------------------------------- /lib/crystal.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (typeof CrystalQuest === "undefined") { 3 | window.CrystalQuest = {}; 4 | } 5 | 6 | var Crystal = CrystalQuest.Crystal = function (options) { 7 | this.pos = options.pos; 8 | this.wave = options.wave; 9 | this.width = 15; 10 | }; 11 | 12 | Crystal.prototype.draw = function(ctx) { 13 | var img = new Image(); 14 | x = this.pos[0] - 7.5; 15 | y = this.pos[1] - 7.5; 16 | img.src = 'img/crystal.png'; 17 | ctx.drawImage(img, x, y, 15, 15); 18 | }; 19 | 20 | })(); 21 | -------------------------------------------------------------------------------- /lib/crystalPoints.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (typeof CrystalQuest === "undefined") { 3 | window.CrystalQuest = {}; 4 | } 5 | 6 | CrystalQuest.CrystalPoints = function (options) { 7 | this.pos = options.pos; 8 | this.wave = options.wave; 9 | this.num = options.num; 10 | this.width = 20; 11 | }; 12 | 13 | CrystalQuest.CrystalPoints.prototype.draw = function(ctx) { 14 | ctx.fillStyle = "yellow"; 15 | ctx.font = "10pt Arial"; 16 | ctx.fillText(this.num, this.pos[0], this.pos[1]); 17 | ctx.fill(); 18 | }; 19 | 20 | })(); 21 | -------------------------------------------------------------------------------- /lib/fourLegAlien.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (typeof CrystalQuest === "undefined") { 3 | window.CrystalQuest = {}; 4 | } 5 | var MovingObject = window.CrystalQuest.MovingObject; 6 | 7 | CrystalQuest.FourLegAlien = function (options) { 8 | MovingObject.call(this, options); 9 | this.radius = 12; 10 | this.img = 'img/four_leg_alien.png'; 11 | }; 12 | 13 | window.CrystalQuest.Util.inherits(CrystalQuest.FourLegAlien, MovingObject); 14 | 15 | CrystalQuest.FourLegAlien.prototype.startIntervals = function() { 16 | var that = this; 17 | this.dirInterval = setInterval( function () { 18 | that.vel = window.CrystalQuest.Util.randomVec(3); 19 | if (that.wave.gameOver) { 20 | that.clearIntervals(); 21 | } 22 | }, 500); 23 | 24 | this.shootInterval = setInterval( function () { 25 | var bulletVel = window.CrystalQuest.Util.randomVec(3); 26 | var bullet = new window.CrystalQuest.BulletAlien({ 27 | pos: that.pos, 28 | vel: bulletVel, 29 | wave: that.wave 30 | }); 31 | if (that.pos[0] > 12 && that.pos[0] < (780 - 12)) { 32 | that.wave.aliens.push(bullet); 33 | } 34 | if (that.wave.gameOver || that.wave.indexOf(that) < 0) { 35 | that.clearIntervals(); 36 | } 37 | }, 2000); 38 | } 39 | })(); 40 | -------------------------------------------------------------------------------- /lib/game.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (typeof CrystalQuest === "undefined") { 3 | window.CrystalQuest = {}; 4 | } 5 | 6 | var Game = window.CrystalQuest.Game = function (xDim, yDim, ctx, audioCtx) { 7 | this.X_DIM = xDim; 8 | this.Y_DIM = yDim; 9 | this.ctx = ctx; 10 | this.audioCtx = audioCtx; 11 | this.timeBonus = 0; 12 | this.counter = 0; 13 | // this.database = {}; 14 | this.database = new Firebase("https://luminous-inferno-7080.firebaseio.com/web/data"); 15 | 16 | $(document).keyup(function (event) { 17 | event.preventDefault(); 18 | if (event.keyCode === 13) { 19 | $('#start').click(); 20 | $('#next-level').click(); 21 | } 22 | }.bind(this)) 23 | } 24 | 25 | Game.prototype.getHighScores = function(cb) { 26 | var scores = this.database.child('highScores'); 27 | scores.orderByChild('score').limitToLast(10).once("value", function(snapshot) { 28 | var val = snapshot.val(); 29 | var result = []; 30 | for (var key in val) { 31 | result.push(val[key]); 32 | } 33 | result.sort(function(a, b) { 34 | return a.score > b.score; 35 | }); 36 | 37 | if (cb) { 38 | cb(result); 39 | } 40 | }, function (errorObject) { 41 | console.log("The read failed: " + errorObject.code); 42 | }); 43 | return scores; 44 | } 45 | 46 | Game.prototype.bindKeyHandlers = function(wave) { 47 | var ship = wave.ship 48 | key('up', function(e) { e.preventDefault(); ship.power([0,-2]) }); 49 | key('down', function(e) { e.preventDefault(); ship.power([0,2]) }); 50 | key('right', function (e) { e.preventDefault(); ship.power([2,0]) }); 51 | key('left', function (e) { e.preventDefault(); ship.power([-2,0]) }); 52 | key('w', function(e) { e.preventDefault(); ship.power([0,-2]) }); 53 | key('s', function(e) { e.preventDefault(); ship.power([0,2]) }); 54 | key('d', function (e) { e.preventDefault(); ship.power([2,0]) }); 55 | key('a', function (e) { e.preventDefault(); ship.power([-2,0]) }); 56 | key('r', function(e) { e.preventDefault(); ship.power([0,0]) }); 57 | key('space', function(e) { e.preventDefault(); ship.fireBullet() }); 58 | }; 59 | 60 | Game.prototype.playSound = function(sound) { 61 | var file = Game.SOUNDS[sound]; 62 | var request = new XMLHttpRequest(); 63 | request.open('GET', file, true); 64 | request.responseType = 'arraybuffer'; 65 | 66 | var _this = this; 67 | request.onload = function() { 68 | var audioData = request.response; 69 | _this.audioCtx.decodeAudioData(audioData, function(buffer) { 70 | var src = _this.audioCtx.createBufferSource(); 71 | 72 | src.buffer = buffer; 73 | src.connect(this.audioCtx.destination); 74 | src.start(0); 75 | }); 76 | } 77 | 78 | request.send(); 79 | }; 80 | 81 | Game.prototype.stop = function() { 82 | if (this.interval) { 83 | clearInterval(this.interval); 84 | this.interval = null; 85 | } 86 | }; 87 | 88 | Game.prototype.win = function() { 89 | var timeTaken = $('#time').text(); 90 | var seconds = (parseInt(timeTaken.split(":")[0]) / 60) + parseInt(timeTaken.split(":")[1]); 91 | var bonus = Math.floor((30 - seconds) * 500); 92 | this.timeBonus = bonus > 0 ? bonus : 0; 93 | 94 | if (this.counter === Game.WAVES.length - 1) { 95 | this.counter = 0; 96 | var total = this.timeBonus + parseInt($('#score').text()); 97 | var content = "

YOU WON!!!

Wave number " + $('#wave').text() + " completed

Time taken: " + timeTaken + "

Time bonus: " + this.timeBonus + "

Total Score: " + total + "

" 98 | $('#controls').append(content); 99 | var _this = this; 100 | this.getHighScores(function(scores) { 101 | if (scores.length < 10 || scores[0].score < score) { 102 | _this.enterHighScore(total); 103 | } 104 | }); 105 | } else { 106 | var content = "

Wave number " + $('#wave').text() + " completed

Time taken: " + timeTaken + "

Time bonus: " + this.timeBonus + "


"; 107 | $('#controls').append(content); 108 | } 109 | }; 110 | 111 | Game.prototype.lose = function() { 112 | this.counter = 0; 113 | var score = parseInt($('#score').text()); 114 | var _this = this; 115 | this.getHighScores(function(scores) { 116 | if (scores.length < 10 || scores[0].score < score) { 117 | _this.enterHighScore(score); 118 | _this.showHighScores(); 119 | } else { 120 | var content = "

Game Over


" 121 | $('#controls').append(content) 122 | } 123 | }); 124 | }; 125 | 126 | Game.prototype.showIntro = function () { 127 | $('#status-bar').empty(); 128 | var str = "

CRYSTAL QUEST





Press enter or click Start to start" 129 | $('#controls').append(str); 130 | }; 131 | 132 | Game.prototype.showHighScores = function () { 133 | $('#status-bar').empty(); 134 | var str = "

HIGH SCORES

" 135 | $('#controls').append(str); 136 | this.getHighScores(function(scores) { 137 | scores.forEach(function(score) { 138 | var scoreStr = "
  • " + score['name'] + "     " + score['score'] + "
  • " 139 | $('ul').prepend(scoreStr); 140 | }); 141 | }); 142 | }; 143 | 144 | Game.prototype.enterHighScore = function (score) { 145 | var name = prompt("You got a new high score! Enter your initials: "); 146 | if ((name !== "") && (name !== null)) { 147 | var scoresRef = this.database.child('highScores'); 148 | scoresRef.push({ name: name.slice(0,3), score: score }); 149 | } 150 | } 151 | 152 | Game.prototype.startLevel = function () { 153 | $('#controls').empty(); 154 | 155 | if (this.counter === 0) { 156 | $('#status-bar').empty(); 157 | $('#status-bar').append('