├── .github └── FUNDING.yml ├── .gitignore ├── .htaccess ├── FileSaver.js ├── README.md ├── auth.html ├── compile ├── exiticon.png ├── favicon.png ├── gzipper ├── help.html ├── index.html ├── pinball.js ├── play.html ├── play_inlined.txt ├── player.js ├── rng.js ├── sfxr.js ├── spawnicon.png └── style.css /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [increpare] 4 | patreon: increpare 5 | custom: ['paypal.me/increparegames'] 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/* 2 | bin 3 | 4 | *.gz 5 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine on 2 | RewriteCond %{HTTP:Accept-Encoding} gzip 3 | RewriteCond %{REQUEST_FILENAME}.gz -f 4 | RewriteRule ^(.*)$ $1.gz [L] 5 | -------------------------------------------------------------------------------- /FileSaver.js: -------------------------------------------------------------------------------- 1 | /* FileSaver.js 2 | * A saveAs() FileSaver implementation. 3 | * 2015-03-04 4 | * 5 | * By Eli Grey, http://eligrey.com 6 | * License: X11/MIT 7 | * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md 8 | */ 9 | 10 | /*global self */ 11 | /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ 12 | 13 | /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ 14 | 15 | var saveAs = saveAs 16 | // IE 10+ (native saveAs) 17 | || (typeof navigator !== "undefined" && 18 | navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator)) 19 | // Everyone else 20 | || (function(view) { 21 | "use strict"; 22 | // IE <10 is explicitly unsupported 23 | if (typeof navigator !== "undefined" && 24 | /MSIE [1-9]\./.test(navigator.userAgent)) { 25 | return; 26 | } 27 | var 28 | doc = view.document 29 | // only get URL when necessary in case Blob.js hasn't overridden it yet 30 | , get_URL = function() { 31 | return view.URL || view.webkitURL || view; 32 | } 33 | , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a") 34 | , can_use_save_link = "download" in save_link 35 | , click = function(node) { 36 | var event = doc.createEvent("MouseEvents"); 37 | event.initMouseEvent( 38 | "click", true, false, view, 0, 0, 0, 0, 0 39 | , false, false, false, false, 0, null 40 | ); 41 | node.dispatchEvent(event); 42 | } 43 | , webkit_req_fs = view.webkitRequestFileSystem 44 | , req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem 45 | , throw_outside = function(ex) { 46 | (view.setImmediate || view.setTimeout)(function() { 47 | throw ex; 48 | }, 0); 49 | } 50 | , force_saveable_type = "application/octet-stream" 51 | , fs_min_size = 0 52 | // See https://code.google.com/p/chromium/issues/detail?id=375297#c7 and 53 | // https://github.com/eligrey/FileSaver.js/commit/485930a#commitcomment-8768047 54 | // for the reasoning behind the timeout and revocation flow 55 | , arbitrary_revoke_timeout = 500 // in ms 56 | , revoke = function(file) { 57 | var revoker = function() { 58 | if (typeof file === "string") { // file is an object URL 59 | get_URL().revokeObjectURL(file); 60 | } else { // file is a File 61 | file.remove(); 62 | } 63 | }; 64 | if (view.chrome) { 65 | revoker(); 66 | } else { 67 | setTimeout(revoker, arbitrary_revoke_timeout); 68 | } 69 | } 70 | , dispatch = function(filesaver, event_types, event) { 71 | event_types = [].concat(event_types); 72 | var i = event_types.length; 73 | while (i--) { 74 | var listener = filesaver["on" + event_types[i]]; 75 | if (typeof listener === "function") { 76 | try { 77 | listener.call(filesaver, event || filesaver); 78 | } catch (ex) { 79 | throw_outside(ex); 80 | } 81 | } 82 | } 83 | } 84 | , FileSaver = function(blob, name) { 85 | // First try a.download, then web filesystem, then object URLs 86 | var 87 | filesaver = this 88 | , type = blob.type 89 | , blob_changed = false 90 | , object_url 91 | , target_view 92 | , dispatch_all = function() { 93 | dispatch(filesaver, "writestart progress write writeend".split(" ")); 94 | } 95 | // on any filesys errors revert to saving with object URLs 96 | , fs_error = function() { 97 | // don't create more object URLs than needed 98 | if (blob_changed || !object_url) { 99 | object_url = get_URL().createObjectURL(blob); 100 | } 101 | if (target_view) { 102 | target_view.location.href = object_url; 103 | } else { 104 | var new_tab = view.open(object_url, "_blank"); 105 | if (new_tab == undefined && typeof safari !== "undefined") { 106 | //Apple do not allow window.open, see http://bit.ly/1kZffRI 107 | view.location.href = object_url 108 | } 109 | } 110 | filesaver.readyState = filesaver.DONE; 111 | dispatch_all(); 112 | revoke(object_url); 113 | } 114 | , abortable = function(func) { 115 | return function() { 116 | if (filesaver.readyState !== filesaver.DONE) { 117 | return func.apply(this, arguments); 118 | } 119 | }; 120 | } 121 | , create_if_not_found = {create: true, exclusive: false} 122 | , slice 123 | ; 124 | filesaver.readyState = filesaver.INIT; 125 | if (!name) { 126 | name = "download"; 127 | } 128 | if (can_use_save_link) { 129 | object_url = get_URL().createObjectURL(blob); 130 | save_link.href = object_url; 131 | save_link.download = name; 132 | click(save_link); 133 | filesaver.readyState = filesaver.DONE; 134 | dispatch_all(); 135 | revoke(object_url); 136 | return; 137 | } 138 | // prepend BOM for UTF-8 XML and text/plain types 139 | if (/^\s*(?:text\/(?:plain|xml)|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { 140 | blob = new Blob(["\ufeff", blob], {type: blob.type}); 141 | } 142 | // Object and web filesystem URLs have a problem saving in Google Chrome when 143 | // viewed in a tab, so I force save with application/octet-stream 144 | // http://code.google.com/p/chromium/issues/detail?id=91158 145 | // Update: Google errantly closed 91158, I submitted it again: 146 | // https://code.google.com/p/chromium/issues/detail?id=389642 147 | if (view.chrome && type && type !== force_saveable_type) { 148 | slice = blob.slice || blob.webkitSlice; 149 | blob = slice.call(blob, 0, blob.size, force_saveable_type); 150 | blob_changed = true; 151 | } 152 | // Since I can't be sure that the guessed media type will trigger a download 153 | // in WebKit, I append .download to the filename. 154 | // https://bugs.webkit.org/show_bug.cgi?id=65440 155 | if (webkit_req_fs && name !== "download") { 156 | name += ".download"; 157 | } 158 | if (type === force_saveable_type || webkit_req_fs) { 159 | target_view = view; 160 | } 161 | if (!req_fs) { 162 | fs_error(); 163 | return; 164 | } 165 | fs_min_size += blob.size; 166 | req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) { 167 | fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) { 168 | var save = function() { 169 | dir.getFile(name, create_if_not_found, abortable(function(file) { 170 | file.createWriter(abortable(function(writer) { 171 | writer.onwriteend = function(event) { 172 | target_view.location.href = file.toURL(); 173 | filesaver.readyState = filesaver.DONE; 174 | dispatch(filesaver, "writeend", event); 175 | revoke(file); 176 | }; 177 | writer.onerror = function() { 178 | var error = writer.error; 179 | if (error.code !== error.ABORT_ERR) { 180 | fs_error(); 181 | } 182 | }; 183 | "writestart progress write abort".split(" ").forEach(function(event) { 184 | writer["on" + event] = filesaver["on" + event]; 185 | }); 186 | writer.write(blob); 187 | filesaver.abort = function() { 188 | writer.abort(); 189 | filesaver.readyState = filesaver.DONE; 190 | }; 191 | filesaver.readyState = filesaver.WRITING; 192 | }), fs_error); 193 | }), fs_error); 194 | }; 195 | dir.getFile(name, {create: false}, abortable(function(file) { 196 | // delete file if it already exists 197 | file.remove(); 198 | save(); 199 | }), abortable(function(ex) { 200 | if (ex.code === ex.NOT_FOUND_ERR) { 201 | save(); 202 | } else { 203 | fs_error(); 204 | } 205 | })); 206 | }), fs_error); 207 | }), fs_error); 208 | } 209 | , FS_proto = FileSaver.prototype 210 | , saveAs = function(blob, name) { 211 | return new FileSaver(blob, name); 212 | } 213 | ; 214 | FS_proto.abort = function() { 215 | var filesaver = this; 216 | filesaver.readyState = filesaver.DONE; 217 | dispatch(filesaver, "abort"); 218 | }; 219 | FS_proto.readyState = FS_proto.INIT = 0; 220 | FS_proto.WRITING = 1; 221 | FS_proto.DONE = 2; 222 | 223 | FS_proto.error = 224 | FS_proto.onwritestart = 225 | FS_proto.onprogress = 226 | FS_proto.onwrite = 227 | FS_proto.onabort = 228 | FS_proto.onerror = 229 | FS_proto.onwriteend = 230 | null; 231 | 232 | return saveAs; 233 | }( 234 | typeof self !== "undefined" && self 235 | || typeof window !== "undefined" && window 236 | || this.content 237 | )); 238 | // `self` is undefined in Firefox for Android content script context 239 | // while `this` is nsIContentFrameMessageManager 240 | // with an attribute `content` that corresponds to the window 241 | 242 | if (typeof module !== "undefined" && module.exports) { 243 | module.exports.saveAs = saveAs; 244 | } else if ((typeof define !== "undefined" && define !== null) && (define.amd != null)) { 245 | define([], function() { 246 | return saveAs; 247 | }); 248 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pinball game engine 2 | 3 | http://www.plingpling.org 4 | 5 | ---- 6 | 7 | COMPILATION GUIDE 8 | 9 | you can run it locally (and fix things for me :P ) by downloading files, but if you want to build a version fully working, with the export feature, you need to install some extra software - it uses a modified version of my puzzlescript scripts, so these instructions should help - 10 | 11 | https://groups.google.com/forum/#!searchin/puzzlescript/development/puzzlescript/yptIpY9hlng/cjfrOPy_4jcJ 12 | 13 | ---- 14 | 15 | # plingpling 16 | 17 | The MIT License (MIT) 18 | 19 | Copyright (c) 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in 29 | all copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 37 | THE SOFTWARE. 38 | -------------------------------------------------------------------------------- /auth.html: -------------------------------------------------------------------------------- 1 | Flickgame Login
   


--------------------------------------------------------------------------------
/compile:
--------------------------------------------------------------------------------
 1 | #!/bin/bash 
 2 | echo ===========================
 3 | start=`date +%s`
 4 | rm -rf bin
 5 | mkdir bin
 6 | echo inlining standalone template
 7 | rm play_inlined.txt
 8 | inliner -n play.html > play_inlined.txt
 9 | echo copying files
10 | cp gzipper bin/
11 | cp -r *.* bin/
12 | cp .htaccess bin/
13 | echo compressing html
14 | java -jar ~/progs/htmlcompressor-1.5.3.jar  -r bin/ -o bin/
15 | echo gzipping site
16 | cd bin
17 | ./gzipper
18 | rm gzipper
19 | cd ..
20 | end=`date +%s`
21 | runtime=$((end-start))
22 | echo script took $runtime seconds
23 | echo ===========================
24 | 
25 | 


--------------------------------------------------------------------------------
/exiticon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/increpare/plingpling/4798d4bd193ebd8dc42971d4f489b249baefa0ab/exiticon.png


--------------------------------------------------------------------------------
/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/increpare/plingpling/4798d4bd193ebd8dc42971d4f489b249baefa0ab/favicon.png


--------------------------------------------------------------------------------
/gzipper:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/perl
 2 | 
 3 | # This script should be uploaded to the web server.
 4 | 
 5 | use warnings;
 6 | use strict;
 7 | use File::Find;
 8 | find (\&wanted, ("."));
 9 | sub wanted
10 | {
11 |     if (/(.*\.(?:html|css|txt|js)$)/i) {
12 |         print "Compressing $File::Find::name\n";
13 |         if (! -f "$_.gz") {
14 |             system ("gzip -c --best --force \"$_\" > \"$_.gz\"");
15 |         }
16 |     }
17 | }
18 | 


--------------------------------------------------------------------------------
/help.html:
--------------------------------------------------------------------------------
  1 | 
  2 | 
  3 | 
  6 | 
  7 | 
102 | 
103 | 
104 | 

how to

105 |

106 | draw a pinball table and then play it. 107 |

108 | [ [ example game ] ] 109 |

110 |

editor

111 |

112 | C : copy current image
113 | V : paste current image
114 | Z : undo
115 | P : play
116 | +/- : change brush
117 | , / . : cycle color palette
118 | shift+click : draw line 119 |

120 | 121 | 122 | 125 | 126 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 |
123 | Controls: 124 |
127 | LEFT/RIGHT flippers
DOWN launcher
UP tilt
R restart
138 |

139 |

levels

140 |

141 | The levels are played in order, going to the next one when you hit an exit point, until you reach a blank level - this ends the game and plays a small animation. 142 |

143 |

player

144 | F : go fullscreen 145 |

146 |

How to make an updateable table?

147 |

If you want to share a link to a table you've made, but still be able to update it (in case there are bugs you want to fix, or new levels you want to add), here's how you do that:

Now anytime you want to update it, just go back to the fork you made, make some changes, and the same game URL should be updated too! 148 |

149 | 150 |

source code

151 |

152 | [ [ github ] ] 153 |

154 |


155 |

156 | made by increpare [ support my work on paypal or patreon ] 157 | 158 | 159 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | plingpling game maker 20 | 21 | 22 | 23 |

24 | 25 |
26 | 27 | 64 | 215 | 216 |
28 | 63 | 65 | 66 | 67 | 212 | 213 |
68 | 69 | 70 | 81 | 208 | 209 |
71 |
72 | 73 | 74 |
75 | 76 |
77 | 78 | 79 |
80 |
82 | ⊚ share
83 | 84 | ⊡ export
85 | ⊡ import
86 | 87 | ? help 88 |
89 |
90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
<>
98 | 99 | 100 | 101 | 104 | 107 | 108 | 109 | 112 | 115 | 116 | 117 | 120 | 123 | 124 | 125 | 128 | 131 | 132 | 133 | 136 | 139 | 140 | 141 | 144 | 147 | 148 | 149 | 152 | 155 | 156 | 157 | 160 | 163 | 164 | 165 | 168 | 171 | 172 | 173 | 176 | 179 | 180 | 181 | 184 | 187 | 188 | 189 | 192 | 195 | 196 | 197 | 200 | 203 | 204 |
102 |
103 |
105 | eraser 106 |
110 |
111 |
113 | wall 114 |
118 |
119 |
121 | bumper 122 |
126 |
127 |
129 | flipper 130 |
134 |
135 |
137 | ball spawn 138 |
142 |
143 |
145 | exit point 146 |
150 |
151 |
153 | spring 154 |
158 |
159 |
161 | ↺ flipper pivot 162 |
166 |
167 |
169 | ↻ flipper pivot 170 |
174 |
175 |
177 | target 178 |
182 |
183 |
185 | connection 186 |
190 |
191 |
193 | toggleable wall 194 |
198 |
X
199 |
201 | clear canvas 202 |
205 |

206 | ▶ Play 207 |

210 |
211 |
214 |
217 |
218 | 219 | -------------------------------------------------------------------------------- /pinball.js: -------------------------------------------------------------------------------- 1 | var contexts = new Array(); 2 | var version="0.1"; 3 | var gameTitle="My Game"; 4 | var gameLink="www.plingpling.org" 5 | var winText="Congratulations! You won!" 6 | var width=125; 7 | var height=140; 8 | var zoomFactor=4; 9 | var radius=5; 10 | var stateIndex=0; 11 | var canvasIndex=0; 12 | var canvasses = new Array(); 13 | var VERSION=2; 14 | for (var i=0;i<16;i++){ 15 | canvasses[i] = new Uint8Array(width*height); 16 | } 17 | var dirty=false; 18 | var exitTriggered=false; 19 | var exitPointX=-1000; 20 | var exitPointY=-1000; 21 | var visibleCanvas; 22 | var visibleContext; 23 | var titleInput; 24 | var linkInput; 25 | var scoreText; 26 | var highScoreText; 27 | var winTextInput; 28 | var id; 29 | var id_d; 30 | var loaded=false; 31 | var lastX=-1; 32 | var lastY=-1; 33 | var shareLinkInner; 34 | var mainPaletteOffset=0; 35 | var layerCount=6; 36 | 37 | var levelCanvasses = new Array(); 38 | for (var i=0;i=0){ 234 | r1=r; 235 | r1i=i; 236 | } 237 | if (r.indexOf(region2)>=0){ 238 | r2=r; 239 | r2i=i; 240 | } 241 | if ((r1!==null)&&(r1===r2)){ 242 | return; 243 | } 244 | } 245 | 246 | //order them so I can splice 247 | if (r2iLog in with Github to share
"; 334 | 335 | var shareLink = document.getElementById("shareLink"); 336 | shareLink.innerHTML = toPrint; 337 | shareLinkInner=null; 338 | } 339 | 340 | 341 | 342 | function githubLogOut(){ 343 | window.localStorage.removeItem("oauth_access_token"); 344 | var authUrl = getAuthURL(); 345 | var toPrint = "Logged out of Github.
"; 346 | var shareLink = document.getElementById("shareLink"); 347 | shareLink.innerHTML = toPrint; 348 | shareLinkInner=null; 349 | } 350 | 351 | 352 | function shareClick() { 353 | var oauthAccessToken = window.localStorage.getItem("oauth_access_token"); 354 | if (typeof oauthAccessToken !== "string") { 355 | // Generates 32 letters of random data, like "liVsr/e+luK9tC02fUob75zEKaL4VpQn". 356 | printUnauthorized(); 357 | return; 358 | } 359 | 360 | 361 | var str = stateToString(); 362 | 363 | var gistToCreate = { 364 | "description" : "flickgame", 365 | "public" : true, 366 | "files": { 367 | "readme.txt" : { 368 | "content": "A game made with www.flickgame.org. You can import game.txt there to play the game. Uh, too lazy to describe - HMU at analytic@gmail.com if you want to know how (basically just use the gist ID in the url like other flickgames do...) " 369 | }, 370 | "game.txt" : { 371 | "content": str 372 | } 373 | } 374 | }; 375 | 376 | var githubURL = 'https://api.github.com/gists'; 377 | var githubHTTPClient = new XMLHttpRequest(); 378 | githubHTTPClient.open('POST', githubURL); 379 | githubHTTPClient.onreadystatechange = function() { 380 | var errorCount=0; 381 | if(githubHTTPClient.readyState!=4) { 382 | return; 383 | } 384 | var result = JSON.parse(githubHTTPClient.responseText); 385 | if (githubHTTPClient.status===403) { 386 | errorCount++; 387 | alert(result.message); 388 | } else if (githubHTTPClient.status!==200&&githubHTTPClient.status!==201) { 389 | 390 | if (githubHTTPClient.statusText==="Unauthorized"){ 391 | alert("Authorization check failed. You have to log back into GitHub (or give it permission again or something)."); 392 | window.localStorage.removeItem("oauth_access_token"); 393 | } else { 394 | alert("HTTP Error "+ githubHTTPClient.status + ' - ' + githubHTTPClient. statusText); 395 | } 396 | 397 | printUnauthorized(); 398 | } else if (githubHTTPClient.status!==200&&githubHTTPClient.status!==201) { 399 | errorCount++; 400 | alert("HTTP Error "+ githubHTTPClient.status + ' - ' + githubHTTPClient.statusText); 401 | } else { 402 | var id = result.id; 403 | var url = "play.html?p="+id; 404 | url=qualifyURL(url); 405 | 406 | var editurl = "editor.html?hack="+id; 407 | editurl=qualifyURL(editurl); 408 | var sourceCodeLink = "link to source code:
"+editurl+""; 409 | 410 | var shareLink = document.getElementById("shareLink"); 411 | shareLink.innerHTML = "↳"+id+"
"+ 412 | '(log out of GitHub)
'; 413 | shareLinkInner = shareLink.childNodes[0]; 414 | 415 | if (errorCount>0) { 416 | alert("Cannot link directly to playable game, because there are errors.",true); 417 | } else { 418 | 419 | } 420 | 421 | 422 | } 423 | } 424 | githubHTTPClient.setRequestHeader("Content-type","application/x-www-form-urlencoded"); 425 | githubHTTPClient.setRequestHeader("Authorization","token "+oauthAccessToken); 426 | var stringifiedGist = JSON.stringify(gistToCreate); 427 | githubHTTPClient.send(stringifiedGist); 428 | lastDownTarget=canvas; 429 | } 430 | 431 | 432 | function RLE_encode(input) { 433 | var encoding = []; 434 | var prev, count, i; 435 | for (count = 1, prev = input[0], i = 1; i < input.length; i++) { 436 | if (input[i] != prev) { 437 | encoding.push(count); 438 | encoding.push(prev); 439 | count = 1; 440 | prev = input[i]; 441 | } 442 | else 443 | count ++; 444 | } 445 | encoding.push(count); 446 | encoding.push(prev); 447 | return encoding; 448 | } 449 | 450 | function RLE_decode(encoded) { 451 | var output = ""; 452 | encoded.forEach(function(pair){ output += new Array(1+pair[0]).join(pair[1]) }) 453 | return output; 454 | } 455 | 456 | function stateToString(){ 457 | var state = new Object(); 458 | state.gameTitle=gameTitle; 459 | state.winText=winText; 460 | state.gameLink=gameLink; 461 | state.canvasIndex=canvasIndex; 462 | state.canvasses=new Array(); 463 | state.mainPaletteOffset=mainPaletteOffset; 464 | state.version=VERSION; 465 | for (var i=0;i0){ 518 | continue; 519 | } 520 | var index=0; 521 | for (var i=0;i=0&&px=0&&py (maxDX*maxDX+maxDY*maxDY) ){ 570 | maxDX=dx; 571 | maxDY=dy; 572 | } 573 | } 574 | } 575 | } 576 | 577 | 578 | if (maxDX!==0||maxDY!==0){ 579 | //need to flip velocity about dx,dy 580 | var nSpeed = [-speedX,-speedY]; 581 | var normal = [maxDX,maxDY]; 582 | var refl = subV(mulV(2*dot(normal,nSpeed),normal),nSpeed); 583 | speedX=refl[0]; 584 | speedY=refl[1]; 585 | } 586 | 587 | bpx+=maxDX; 588 | bpy+=maxDY; 589 | speedX+=maxDX; 590 | speedY+=maxDY; 591 | clampSpeed(); 592 | 593 | var xsign=0; 594 | var ysign=0; 595 | if (maxDX>0){ 596 | xsign=1; 597 | } else if (maxDX<0){ 598 | xsign=-1; 599 | } 600 | if (maxDY>0){ 601 | ysign=1; 602 | } else if (maxDY<0){ 603 | ysign=-1; 604 | } 605 | if(xsign===0&&ysign===0){ 606 | ysign=-1; 607 | } 608 | 609 | /* while(ballCollides()){ 610 | bpx+=xsign; 611 | bpy+=ysign; 612 | } 613 | */ 614 | } 615 | 616 | var hasLeftPaddle=true; 617 | var hasRightPaddle=true; 618 | var hasSpring=true; 619 | 620 | function interpolateAreas(oldstateIndex,newstateIndex){ 621 | var oldLeft = oldstateIndex%2; 622 | var newLeft = newstateIndex%2; 623 | 624 | var oldRight = (Math.floor(oldstateIndex/2))%2; 625 | var newRight = (Math.floor(newstateIndex/2))%2; 626 | 627 | var oldDown = (Math.floor(oldstateIndex/4))%2; 628 | var newDown = (Math.floor(newstateIndex/4))%2; 629 | 630 | if (oldDown===1&&newDown===0){ 631 | if (hasSpring){ 632 | playSound(62826107,true); 633 | } 634 | } else if (oldDown===0&&newDown===1){ 635 | if (hasSpring){ 636 | playSound(67535707,true); 637 | } 638 | } else if ((oldLeft===0&&newLeft===1)){ 639 | if (hasLeftPaddle){ 640 | playSound(64004107,true); 641 | } 642 | }else if (oldRight===0&&newRight===1){ 643 | if (hasRightPaddle){ 644 | playSound(64004107,true); 645 | } 646 | } 647 | 648 | 649 | var result = [oldstateIndex]; 650 | if (oldLeft!=newLeft){ 651 | var newItem = result[result.length-1]-oldLeft+newLeft; 652 | result.push(newItem); 653 | } 654 | if (oldRight!=newRight){ 655 | var newItem = result[result.length-1]-oldRight*2+newRight*2; 656 | result.push(newItem); 657 | } 658 | if (oldDown!=newDown){ 659 | var newItem = result[result.length-1]-oldDown*4+newDown*4; 660 | result.push(newItem); 661 | } 662 | return result; 663 | } 664 | 665 | function setstateIndex(oldstateIndex,newstateIndex){ 666 | if (dirty){ 667 | compile(); 668 | } 669 | 670 | var steps = interpolateAreas(oldstateIndex,newstateIndex); 671 | for (var i=0;i " + target ); 679 | return; 680 | } 681 | 682 | applyCanvasSweep( sweepArea ); 683 | } 684 | } 685 | 686 | var tilting=false; 687 | 688 | function setFlipperCanvas(){ 689 | var oldstateIndex=stateIndex; 690 | stateIndex=0; 691 | if (keyBuffer[37]===true){//left 692 | stateIndex=1; 693 | } 694 | if (keyBuffer[39]===true){//right 695 | stateIndex+=2; 696 | } 697 | if (keyBuffer[40]===true){//down 698 | stateIndex+=4; 699 | } 700 | tilting = keyBuffer[38];//up 701 | 702 | if (oldstateIndex!=stateIndex){ 703 | setstateIndex(oldstateIndex,stateIndex); 704 | //38 is up 705 | //40 is down 706 | setVisuals(); 707 | } 708 | } 709 | 710 | function keyup(evt){ 711 | evt = evt || window.event; 712 | keyBuffer[evt.keyCode]=false; 713 | setFlipperCanvas(); 714 | } 715 | 716 | function prevent(e) { 717 | if (e.preventDefault) e.preventDefault(); 718 | if (e.stopImmediatePropagation) e.stopImmediatePropagation(); 719 | if (e.stopPropagation) e.stopPropagation(); 720 | e.returnValue=false; 721 | return false; 722 | } 723 | 724 | 725 | function setLevel(newCanvasIndex,force) { 726 | if ((newCanvasIndex-1)===canvasIndex){ 727 | if (!(force===true)){ 728 | return; 729 | } 730 | } 731 | 732 | canvasIndex=newCanvasIndex-1; 733 | masterCanvas=levelCanvasses[canvasIndex]; 734 | 735 | if (PLAYER!==true){ 736 | for(var i=0;imax){ 760 | max=sample; 761 | } 762 | } 763 | } 764 | thumbCtx.fillStyle=colorPalette[max]; 765 | thumbCtx.fillRect(i,j,1,1); 766 | } 767 | } 768 | 769 | // var dataUrl = thumbnailCanvas[n].toDataURL(); 770 | // "dropdownOption"[0][0].style.backgroundImage="url("+dataUrl+")"; 771 | } 772 | 773 | function press(evt){ 774 | evt = evt || window.event; 775 | keyBuffer[evt.keyCode]=true; 776 | 777 | if ([32, 37, 38, 39, 40].indexOf(evt.keyCode) > -1) { 778 | prevent(evt); 779 | } 780 | 781 | /* 782 | if (evt.keyCode==83){//S(ave) 783 | savedString = stateToString(); 784 | } else if (evt.keyCode==76){//L(oad) 785 | stringToState(savedString); 786 | setVisuals(); 787 | setLevel(stateIndex+1); 788 | } */ 789 | if (evt.keyCode===188){ 790 | cyclePalette(-1); 791 | } else if (evt.keyCode===190){ 792 | cyclePalette(1); 793 | } 794 | else if (evt.keyCode===38 && tilting===false){ 795 | playSound(72335902,true); 796 | } 797 | else if (evt.keyCode===80 ){//p 798 | compile(); 799 | spawnBall(); 800 | } else if (evt.keyCode===82){//r 801 | if (exitTriggered){ 802 | compile(); 803 | } 804 | spawnBall(); 805 | }else if (evt.keyCode===67) { //c 806 | copyImage=JSON.stringify(masterCanvas); 807 | //copyImage=JSON.stringify(canvasses[stateIndex]) 808 | } else if (evt.keyCode===86){ //v 809 | if (copyImage!==null){ 810 | preserveUndoState(); 811 | var ar = JSON.parse(copyImage); 812 | var arui8 = new Uint8Array(width*height); 813 | for (var i=0;i0){ 836 | var newTarget = datArray[index-2]; 837 | var newTargetCol = datArray[index-1]; 838 | selectTool(newTarget,newTargetCol); 839 | } 840 | 841 | } else if (evt.keyCode===187 || evt.keyCode===61){//+ 842 | var datArray = ['eraser', eraserCol, 843 | 'wall', wallCol, 844 | 'bumper', bumperCol, 845 | 'flipper', flipperCol, 846 | 'ballSpawn',ballSpawnCol, 847 | 'exitPoint',exitCol, 848 | 'spring',springCol, 849 | 'leftFlipperPivot', leftFlipperPivotCol, 850 | 'rightFlipperPivot', rightFlipperPivotCol, 851 | 'connection', connectionCol, 852 | 'target', targetCol, 853 | 'togglableWall', togglableWallCol]; 854 | 855 | var index = datArray.indexOf(activeTool); 856 | if (index+20){ 863 | var dat = undoList.pop(); 864 | setLevel(dat.canvasIndex+1); 865 | for(var i = 0; i < dat.canvasDat.length; i++){ 866 | masterCanvas[i] = dat.canvasDat[i]; 867 | } 868 | compile(); 869 | setVisuals(true,true); 870 | if (shareLinkInner!=null){ 871 | shareLinkInner.style.color="gray"; 872 | } 873 | } 874 | } 875 | 876 | setFlipperCanvas(); 877 | } 878 | function getRandomInt(min, max) { 879 | return Math.floor(Math.random() * (max - min)) + min; 880 | } 881 | 882 | var bucketElem; 883 | function titleChange(newTitle){ 884 | gameTitle=newTitle; 885 | } 886 | 887 | function linkChange(newLink){ 888 | gameLink=newLink; 889 | } 890 | function winTextChange(newWinText){ 891 | winText=newWinText; 892 | } 893 | function clearPalette(){ 894 | preserveUndoState(); 895 | var canvas=masterCanvas; 896 | for (var i=0;i0 && 964 | val!==bumperAuraCol && 965 | val!==ballSpawnCol && 966 | val!==exitCol && 967 | val!==connectionCol){ 968 | return true; 969 | } 970 | } 971 | return false; 972 | } 973 | 974 | activatedConnections=[]; 975 | function activateSwitch(index){ 976 | var regionNumber = regionCanvas[index]; 977 | var bbox = boundingBoxes[regionNumber]; 978 | for (var x=bbox[0];x<=bbox[2];x++){ 979 | for (var y=bbox[1];y<=bbox[3];y++){ 980 | var i = x+width*y; 981 | if (regionCanvas[i]===regionNumber){ 982 | for (var j=0;j=0){ 996 | connectionGroupIndex=i; 997 | break; 998 | } 999 | } 1000 | 1001 | if (connectionGroupIndex===-1){ 1002 | playSound(89718103,true); 1003 | return; 1004 | } 1005 | activatedConnections.push(regionNumber); 1006 | 1007 | var r = connections[connectionGroupIndex]; 1008 | 1009 | var foundCables=0; 1010 | var foundTriggers=0; 1011 | var foundWalls=0; 1012 | var triggeredTriggers=0; 1013 | for (var i=0;i=0){ 1023 | triggeredTriggers++; 1024 | } 1025 | } 1026 | } 1027 | if (triggeredTriggers===foundTriggers){ 1028 | removeTogglableWalls(r); 1029 | playSound(66445903,true); 1030 | } else { 1031 | playSound(89718103,true); 1032 | } 1033 | } 1034 | 1035 | function removeTogglableWalls(row){ 1036 | 1037 | for (var i=0;i=width||cpy<0){ 1078 | var px = Math.floor(cpx)+0.5; 1079 | var py = Math.floor(cpy)+0.5; 1080 | var dx = px-x-2; 1081 | var dy = py-y-2; 1082 | collisiondat.push([wallCol,-dx,-dy]) 1083 | continue; 1084 | } 1085 | var index = cpx+width*cpy; 1086 | var val = canvas[index]; 1087 | if (val === targetCol){ 1088 | activateSwitch(index); 1089 | } else if ( 1090 | val>0 && 1091 | val!==bumperAuraCol && 1092 | val!==ballSpawnCol && 1093 | val!==exitCol && 1094 | val!==connectionCol && 1095 | val!==targetActiveCol && 1096 | val!==togglableWallDisabledCol){ 1097 | var px = (index%width)+0.5; 1098 | var py = Math.floor(index/width)+0.5; 1099 | var dx = px-x-2; 1100 | var dy = py-y-2; 1101 | collisiondat.push([val,-dx,-dy,index]); 1102 | } 1103 | } 1104 | return collisiondat; 1105 | } 1106 | 1107 | function dot(v1,v2){ 1108 | return v1[0]*v2[0]+v1[1]*v2[1]; 1109 | } 1110 | 1111 | function mag (v){ 1112 | return Math.sqrt(v[0]*v[0]+v[1]*v[1]); 1113 | } 1114 | 1115 | function normalized(v){ 1116 | var m = mag(v); 1117 | return [v[0]/m,v[1]/m]; 1118 | } 1119 | 1120 | function addV(v1,v2){ 1121 | return [v1[0]+v2[0],v1[1]+v2[1]]; 1122 | } 1123 | 1124 | function subV(v1,v2){ 1125 | return [v1[0]-v2[0],v1[1]-v2[1]]; 1126 | } 1127 | 1128 | function mulV(s,v){ 1129 | return [s*v[0],s*v[1]]; 1130 | } 1131 | 1132 | var speedX=0; 1133 | var speedY=1; 1134 | var ballSpin=1; 1135 | var tickRecalcs=0; 1136 | var tickLength=33; 1137 | var bounceDamp=0.8; 1138 | var bumperSpeed=2.0; 1139 | var maxSpeed=3.0; 1140 | var maxBallSpin=4.0; 1141 | var spinDamp=0.0002; 1142 | function clampSpeed(){ 1143 | var v= [speedX,speedY]; 1144 | var speedMag = mag(v); 1145 | if (speedMag>maxSpeed){ 1146 | speedN = normalized(v); 1147 | v = mulV(maxSpeed,speedN); 1148 | speedX=v[0]; 1149 | speedY=v[1]; 1150 | } 1151 | if (Math.abs(ballSpin)>maxBallSpin){ 1152 | ballSpin=ballSpin/Math.abs(ballSpin)*maxBallSpin; 1153 | } 1154 | } 1155 | var ballSpinSpeed=0.4; 1156 | var bumperHit=-1; 1157 | var lastsoundpos_bump=-1; 1158 | var oldscore=0; 1159 | function tick(){ 1160 | 1161 | var tempsoundpos = Math.round(bpx)+1000*Math.round(bpy); 1162 | if (tempsoundpos!==lastsoundpos_bump){ 1163 | lastsoundpos_bump=-1; 1164 | } 1165 | if (oldscore!==score){ 1166 | // If the user has more points than the currently stored high score then 1167 | if (score > highScore) { 1168 | // Set the high score to the users' current points 1169 | highScore = score; 1170 | // Store the high score 1171 | localStorage.setItem(makeKey('highScore'), highScore); 1172 | } 1173 | setScoreText(); 1174 | } 1175 | if (PLAYER&&loaded){ 1176 | if ((bpy<-500||bpy>height+5)&&exitTriggered===false){ 1177 | spawnBall(); 1178 | } 1179 | } 1180 | bumperHit=-1; 1181 | var signum=ballSpin>0?1:-1; 1182 | ballFrame=(((ballFrame+signum*Math.sqrt(Math.abs(ballSpin))*ballSpinSpeed)%4)+4)%4; 1183 | 1184 | var oldSpeedX=speedX; 1185 | var oldSpeedY=speedY; 1186 | var canvas=canvasses[stateIndex]; 1187 | if (bpx<-10){ 1188 | if ((PLAYER&&exitTriggered) || (tilting===true)){ 1189 | setVisuals(); 1190 | } 1191 | return; 1192 | } 1193 | var G=0.002; 1194 | speedY+=G*tickLength; 1195 | clampSpeed(); 1196 | var nx = bpx+speedX; 1197 | var ny = bpy+speedY; 1198 | var rnx = Math.round(nx); 1199 | var rny = Math.round(ny); 1200 | var collisiondat = collision(rnx,rny); 1201 | if (collisiondat.length===0){ 1202 | bpx=nx; 1203 | bpy=ny; 1204 | if (isNaN(bpx)||isNaN(bpy)){ 1205 | console.log("eek nan"); 1206 | } 1207 | tickRecalcs=0; 1208 | ballSpin*=(1-spinDamp*tickLength); 1209 | } else { 1210 | var avgx=0; 1211 | var avgy=0; 1212 | var bumperCount=0; 1213 | for (var i=0;i0){ 1242 | score+=200; 1243 | targetsound=64236300; 1244 | var speedMag=mag(nSpeed); 1245 | speedMag+=bumperSpeed; 1246 | if (speedMag>maxSpeed){ 1247 | speedMag=maxSpeed; 1248 | } 1249 | nSpeed = mulV(speedMag,normalized(nSpeed)); 1250 | speedX=nSpeed[0]; 1251 | speedY=nSpeed[1]; 1252 | clampSpeed(); 1253 | } else { 1254 | } 1255 | 1256 | var soundpos = Math.round(bpx)+1000*Math.round(bpy); 1257 | if(soundpos!==lastsoundpos_bump){ 1258 | playSound(targetsound,bumperCount===0); 1259 | } 1260 | lastsoundpos_bump=soundpos; 1261 | 1262 | var direction = (nSpeed[0]*normal[1]-nSpeed[1]*normal[0]); 1263 | /* if (direction<0){ 1264 | console.log("left"); 1265 | } else if (direction>0){ 1266 | console.log("right"); 1267 | } else { 1268 | console.log("bang"); 1269 | }*/ 1270 | var refl = subV(mulV(2*dot(normal,nSpeed),normal),nSpeed); 1271 | //add 50% of spin to the bounce 1272 | speedX=bounceDamp*refl[0]; 1273 | speedY=bounceDamp*refl[1]; 1274 | 1275 | leftV = [-normal[1],normal[0]]; 1276 | var ballSpinAmount=1.0; 1277 | speedX+=ballSpinAmount*ballSpin*leftV[0]/2; 1278 | speedY+=ballSpinAmount*ballSpin*leftV[1]/2; 1279 | 1280 | ballSpin/=2; 1281 | ballSpin+=direction*mag([speedX,speedY])*(1-bounceDamp)/2.0; 1282 | 1283 | clampSpeed(); 1284 | nx = bpx+speedX; 1285 | ny = bpy+speedY; 1286 | 1287 | 1288 | tickRecalcs++; 1289 | 1290 | var collisiondat = collision(Math.round(nx),Math.round(ny)); 1291 | if (collisiondat.length===0){ 1292 | bpx=nx; 1293 | bpy=ny; 1294 | if (isNaN(bpx)||isNaN(bpy)){ 1295 | console.log("eek nan"); 1296 | } 1297 | tickRecalcs=0; 1298 | } else { 1299 | if (tickRecalcs<4){ 1300 | tick(); 1301 | return; 1302 | } else { 1303 | bpx=nx; 1304 | bpy=ny; 1305 | } 1306 | 1307 | 1308 | } 1309 | } 1310 | var bpxr=Math.round(bpx); 1311 | var bpyr=Math.round(bpy); 1312 | if (exitTriggered ===false && bpxr<=exitPointX&&exitPointX<=bpxr+4 && bpyr<=exitPointY&&exitPointY<=bpyr+4 ){ 1313 | if(canvasIndex+1=0){ 1551 | if (regionCanvas[i+width*j]===bumperHit){ 1552 | visibleContext.fillStyle=colorPalette[18]; 1553 | } 1554 | } 1555 | visibleContext.fillRect(i*zoom,j*zoom,zoom,zoom); 1556 | } 1557 | } 1558 | return; 1559 | } 1560 | for (var i=0;i=0){ 1565 | if (regionCanvas[i+width*j]===bumperHit){ 1566 | visibleContext.fillStyle=colorPalette[18]; 1567 | } 1568 | } 1569 | visibleContext.fillRect(i*zoom,j*zoom,zoom,zoom); 1570 | } 1571 | } 1572 | 1573 | if (mag([speedX,speedY])>2) { 1574 | var ballPoints=ballPointFrames[Math.floor(lastBallFrame)]; 1575 | visibleContext.fillStyle="#888888"; 1576 | for (var i=0;i30){ 1653 | undoList.shift(); 1654 | } 1655 | 1656 | if (shareLinkInner!=null){ 1657 | shareLinkInner.style.color="gray"; 1658 | } 1659 | } 1660 | 1661 | function mouseDown(e){ 1662 | e = e || window.event; 1663 | 1664 | drawing=1; 1665 | var coords = getCoords(e); 1666 | startTargetX=coords[0]; 1667 | startTargetY=coords[1]; 1668 | lastX=Math.floor(-1+startTargetX/zoomFactor); 1669 | lastY=Math.floor(-1+startTargetY/zoomFactor); 1670 | 1671 | preserveUndoState(); 1672 | mouseMove(e,e.type==="mousedown"); 1673 | if(radius===0){ 1674 | drawing=0; 1675 | } 1676 | } 1677 | 1678 | function mouseUp(e){ 1679 | e = e || window.event; 1680 | calcHalo(); 1681 | setVisuals(); 1682 | drawing=0; 1683 | lastX=-1; 1684 | lastY=-1; 1685 | } 1686 | 1687 | function mouseOut(e){ 1688 | e = e || window.event; 1689 | 1690 | mouseMove(e); 1691 | lastX=-1; 1692 | lastY=-1; 1693 | } 1694 | 1695 | var activeTool="wall"; 1696 | function selectTool(toolName,col){ 1697 | activeTool=toolName; 1698 | for (var i=0;i<16;i++){ 1699 | var elem = colorElem[i]; 1700 | if (elem!=null){ 1701 | elem.setAttribute("class","unselected"); 1702 | } 1703 | } 1704 | colorElem[col].setAttribute("class","selected"); 1705 | } 1706 | 1707 | function line (x1, y1,x2,y2) { 1708 | var coordinatesArray = new Array(); 1709 | // Translate coordinates 1710 | // Define differences and error check 1711 | var dx = Math.abs(x2 - x1); 1712 | var dy = Math.abs(y2 - y1); 1713 | var sx = (x1 < x2) ? 1 : -1; 1714 | var sy = (y1 < y2) ? 1 : -1; 1715 | var err = dx - dy; 1716 | // Set first coordinates 1717 | coordinatesArray.push([x1,y1]); 1718 | // Main loop 1719 | while (!((x1 == x2) && (y1 == y2))) { 1720 | var e2 = err << 1; 1721 | if (e2 > -dy) { 1722 | err -= dy; 1723 | x1 += sx; 1724 | } 1725 | if (e2 < dx) { 1726 | err += dx; 1727 | y1 += sy; 1728 | } 1729 | // Set coordinates 1730 | coordinatesArray.push([x1,y1]); 1731 | } 1732 | // Return the result 1733 | return coordinatesArray; 1734 | } 1735 | 1736 | 1737 | function eraserDraw(x,y){ 1738 | //var points = [[x,y],[x-1,y],[x,y+1],[x+1,y],[x,y-1]]; 1739 | 1740 | var points = [ 1741 | [x-1,y+2],[x,y+2],[x+1,y+2], 1742 | [x-2,y+1],[x-1,y+1],[x,y+1],[x+1,y+1],[x+2,y+1], 1743 | [x-2,y ],[x-1,y ],[x,y ],[x+1,y ],[x+2,y ], 1744 | [x-2,y-1],[x-1,y-1],[x,y-1],[x+1,y-1],[x+2,y-1], 1745 | [x-1,y-2],[x,y-2],[x+1,y-2] 1746 | ]; 1747 | for (var i=0;i=0&&px=0&&py=0&&px=0&&py=0&&px=0&&py=0&&px=0&&py=0&&nx=0&&ny=0&&px=0&&py=0&&nx=0&&ny=0&&nx=0&&ny=0&&px=0&&py=0&&nx=0&&ny=0&&nx=0&&nywidth-3){ 1942 | px=width-3; 1943 | } 1944 | if (py<3){ 1945 | py=3; 1946 | } else if (py>height-3){ 1947 | py=height-3; 1948 | } 1949 | 1950 | var points = [ 1951 | [px-3,py-0], 1952 | [px-3,py-1], 1953 | [px-2,py-2], 1954 | [px-1,py-3], 1955 | [px+0,py-3], 1956 | [px+1,py-2], 1957 | [px+2,py-1], 1958 | [px+2,py-0], 1959 | [px+1,py+1], 1960 | [px+0,py+2], 1961 | [px-1,py+2], 1962 | [px-2,py+1] 1963 | ]; 1964 | for (var i=0;i=0&&px=0&&py=0&&px=0&&py=0&&px=0&&py=0&&px=0&&pywidth-3){ 2031 | px=width-3; 2032 | } 2033 | if (py<2){ 2034 | py=2; 2035 | } else if (py>height-3){ 2036 | py=height-3; 2037 | } 2038 | 2039 | var points = [ 2040 | [px,py], 2041 | [px-1,py-1],[px-2,py-2], 2042 | [px+1,py-1],[px+2,py-2], 2043 | [px-1,py+1],[px-2,py+2], 2044 | [px+1,py+1],[px+2,py+2] 2045 | ]; 2046 | for (var i=0;i0){ 2184 | ballSpawnPointX/=ballSpawnPointCount; 2185 | ballSpawnPointY/=ballSpawnPointCount; 2186 | ballSpawnPointX-=2; 2187 | ballSpawnPointY-=2; 2188 | } else { 2189 | ballSpawnPointX=width/2; 2190 | ballSpawnPointY=height/2; 2191 | } 2192 | 2193 | if (exitPointCount===0){ 2194 | exitPointX=-10000; 2195 | exitPointY=-10000; 2196 | } else { 2197 | exitPointX/=exitPointCount; 2198 | exitPointY/=exitPointCount; 2199 | } 2200 | 2201 | drawBB(); 2202 | scrunchSprings(); 2203 | generateSweepOffsets(); 2204 | makeConnections(); 2205 | 2206 | 2207 | hasLeftPaddle=false; 2208 | hasRightPaddle=false; 2209 | hasSpring=false; 2210 | 2211 | for (var i in pivotPoints){ 2212 | var ppoint = pivotPoints[i]; 2213 | if (ppoint[2]===1){ 2214 | hasLeftPaddle=true; 2215 | } else if (ppoint[2]===2){ 2216 | hasRightPaddle=true; 2217 | } 2218 | } 2219 | for (var i=0;i=0 && 2778 | borderpx=0 && 2780 | borderpy 2 | 3 | 4 | 5 | 8 | pinball player 9 | 10 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 | 48 | 51 | 82 |
49 | 50 |
83 | 84 |
85 | 86 | -------------------------------------------------------------------------------- /player.js: -------------------------------------------------------------------------------- 1 | /**/ 2 | var embeddedArray=["__EmbedBegin__","__EMBED__","__EmbedEnd__"] 3 | var embeddedDat=embeddedArray[1]; 4 | /*__EmbedEnd__*/ 5 | 6 | var PLAYER=true; 7 | 8 | function goFullscreen(evt){ 9 | evt = evt || window.event; 10 | if (evt.keyCode===70) { //f 11 | var elem = document.getElementById("mainCanvas"); 12 | if (elem.requestFullscreen) { 13 | elem.requestFullscreen(); 14 | } else if (elem.msRequestFullscreen) { 15 | elem.msRequestFullscreen(); 16 | } else if (elem.mozRequestFullScreen) { 17 | elem.mozRequestFullScreen(); 18 | } else if (elem.webkitRequestFullscreen) { 19 | elem.webkitRequestFullscreen(); 20 | } 21 | } 22 | } 23 | document.addEventListener("keydown", goFullscreen); -------------------------------------------------------------------------------- /rng.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Seedable random number generator functions. 3 | * @version 1.0.0 4 | * @license Public Domain 5 | * 6 | * @example 7 | * var rng = new RNG('Example'); 8 | * rng.random(40, 50); // => 42 9 | * rng.uniform(); // => 0.7972798995050903 10 | * rng.normal(); // => -0.6698504543216376 11 | * rng.exponential(); // => 1.0547367609131555 12 | * rng.poisson(4); // => 2 13 | * rng.gamma(4); // => 2.781724687386858 14 | */ 15 | 16 | /** 17 | * Get the underlying bytes of this string. 18 | * @return {Array} An array of bytes 19 | */ 20 | String.prototype.getBytes = function() { 21 | var output = []; 22 | for (var i = 0; i < this.length; i++) { 23 | var c = this.charCodeAt(i); 24 | var bytes = []; 25 | do { 26 | bytes.push(c & 0xFF); 27 | c = c >> 8; 28 | } while (c > 0); 29 | output = output.concat(bytes.reverse()); 30 | } 31 | return output; 32 | }; 33 | 34 | /** 35 | * @param {String} seed A string to seed the generator. 36 | * @constructor 37 | */ 38 | function RC4(seed) { 39 | this.s = new Array(256); 40 | this.i = 0; 41 | this.j = 0; 42 | for (var i = 0; i < 256; i++) { 43 | this.s[i] = i; 44 | } 45 | if (seed) { 46 | this.mix(seed); 47 | } 48 | } 49 | 50 | RC4.prototype._swap = function(i, j) { 51 | var tmp = this.s[i]; 52 | this.s[i] = this.s[j]; 53 | this.s[j] = tmp; 54 | }; 55 | 56 | /** 57 | * Mix additional entropy into this generator. 58 | * @param {String} seed 59 | */ 60 | RC4.prototype.mix = function(seed) { 61 | var input = seed.getBytes(); 62 | var j = 0; 63 | for (var i = 0; i < this.s.length; i++) { 64 | j += this.s[i] + input[i % input.length]; 65 | j %= 256; 66 | this._swap(i, j); 67 | } 68 | }; 69 | 70 | /** 71 | * @return {number} The next byte of output from the generator. 72 | */ 73 | RC4.prototype.next = function() { 74 | this.i = (this.i + 1) % 256; 75 | this.j = (this.j + this.s[this.i]) % 256; 76 | this._swap(this.i, this.j); 77 | return this.s[(this.s[this.i] + this.s[this.j]) % 256]; 78 | }; 79 | 80 | function print_call_stack() { 81 | var e = new Error(); 82 | var stack = e.stack; 83 | console.log( stack ); 84 | } 85 | /** 86 | * Create a new random number generator with optional seed. If the 87 | * provided seed is a function (i.e. Math.random) it will be used as 88 | * the uniform number generator. 89 | * @param seed An arbitrary object used to seed the generator. 90 | * @constructor 91 | */ 92 | function RNG(seed) { 93 | this.seed = seed; 94 | if (seed == null) { 95 | seed = (Math.random() + Date.now()).toString(); 96 | //window.console.log("setting random seed "+seed); 97 | //print_call_stack(); 98 | 99 | } else if (typeof seed === 'function') { 100 | // Use it as a uniform number generator 101 | this.uniform = seed; 102 | this.nextByte = function() { 103 | return ~~(this.uniform() * 256); 104 | }; 105 | seed = null; 106 | } else if (Object.prototype.toString.call(seed) !== '[object String]') { 107 | seed = JSON.stringify(seed); 108 | } else { 109 | //window.console.log("setting seed "+seed); 110 | //print_call_stack(); 111 | } 112 | this._normal = null; 113 | if (seed) { 114 | this._state = new RC4(seed); 115 | } else { 116 | this._state = null; 117 | } 118 | } 119 | 120 | /** 121 | * @return {number} Uniform random number between 0 and 255. 122 | */ 123 | RNG.prototype.nextByte = function() { 124 | return this._state.next(); 125 | }; 126 | 127 | /** 128 | * @return {number} Uniform random number between 0 and 1. 129 | */ 130 | RNG.prototype.uniform = function() { 131 | var BYTES = 7; // 56 bits to make a 53-bit double 132 | var output = 0; 133 | for (var i = 0; i < BYTES; i++) { 134 | output *= 256; 135 | output += this.nextByte(); 136 | } 137 | return output / (Math.pow(2, BYTES * 8) - 1); 138 | }; 139 | 140 | /** 141 | * Produce a random integer within [n, m). 142 | * @param {number} [n=0] 143 | * @param {number} m 144 | * 145 | */ 146 | RNG.prototype.random = function(n, m) { 147 | if (n == null) { 148 | return this.uniform(); 149 | } else if (m == null) { 150 | m = n; 151 | n = 0; 152 | } 153 | return n + Math.floor(this.uniform() * (m - n)); 154 | }; 155 | 156 | /** 157 | * Generates numbers using this.uniform() with the Box-Muller transform. 158 | * @return {number} Normally-distributed random number of mean 0, variance 1. 159 | */ 160 | RNG.prototype.normal = function() { 161 | if (this._normal !== null) { 162 | var n = this._normal; 163 | this._normal = null; 164 | return n; 165 | } else { 166 | var x = this.uniform() || Math.pow(2, -53); // can't be exactly 0 167 | var y = this.uniform(); 168 | this._normal = Math.sqrt(-2 * Math.log(x)) * Math.sin(2 * Math.PI * y); 169 | return Math.sqrt(-2 * Math.log(x)) * Math.cos(2 * Math.PI * y); 170 | } 171 | }; 172 | 173 | /** 174 | * Generates numbers using this.uniform(). 175 | * @return {number} Number from the exponential distribution, lambda = 1. 176 | */ 177 | RNG.prototype.exponential = function() { 178 | return -Math.log(this.uniform() || Math.pow(2, -53)); 179 | }; 180 | 181 | /** 182 | * Generates numbers using this.uniform() and Knuth's method. 183 | * @param {number} [mean=1] 184 | * @return {number} Number from the Poisson distribution. 185 | */ 186 | RNG.prototype.poisson = function(mean) { 187 | var L = Math.exp(-(mean || 1)); 188 | var k = 0, p = 1; 189 | do { 190 | k++; 191 | p *= this.uniform(); 192 | } while (p > L); 193 | return k - 1; 194 | }; 195 | 196 | /** 197 | * Generates numbers using this.uniform(), this.normal(), 198 | * this.exponential(), and the Marsaglia-Tsang method. 199 | * @param {number} a 200 | * @return {number} Number from the gamma distribution. 201 | */ 202 | RNG.prototype.gamma = function(a) { 203 | var d = (a < 1 ? 1 + a : a) - 1 / 3; 204 | var c = 1 / Math.sqrt(9 * d); 205 | do { 206 | do { 207 | var x = this.normal(); 208 | var v = Math.pow(c * x + 1, 3); 209 | } while (v <= 0); 210 | var u = this.uniform(); 211 | var x2 = Math.pow(x, 2); 212 | } while (u >= 1 - 0.0331 * x2 * x2 && 213 | Math.log(u) >= 0.5 * x2 + d * (1 - v + Math.log(v))); 214 | if (a < 1) { 215 | return d * v * Math.exp(this.exponential() / -a); 216 | } else { 217 | return d * v; 218 | } 219 | }; 220 | 221 | /** 222 | * Accepts a dice rolling notation string and returns a generator 223 | * function for that distribution. The parser is quite flexible. 224 | * @param {string} expr A dice-rolling, expression i.e. '2d6+10'. 225 | * @param {RNG} rng An optional RNG object. 226 | * @return {Function} 227 | */ 228 | RNG.roller = function(expr, rng) { 229 | var parts = expr.split(/(\d+)?d(\d+)([+-]\d+)?/).slice(1); 230 | var dice = parseFloat(parts[0]) || 1; 231 | var sides = parseFloat(parts[1]); 232 | var mod = parseFloat(parts[2]) || 0; 233 | rng = rng || new RNG(); 234 | return function() { 235 | var total = dice + mod; 236 | for (var i = 0; i < dice; i++) { 237 | total += rng.random(sides); 238 | } 239 | return total; 240 | }; 241 | }; -------------------------------------------------------------------------------- /sfxr.js: -------------------------------------------------------------------------------- 1 | var SOUND_VOL = 0.25; 2 | var SAMPLE_RATE = 5512; 3 | var BIT_DEPTH = 8; 4 | 5 | var SQUARE = 0; 6 | var SAWTOOTH = 1; 7 | var SINE = 2; 8 | var NOISE = 3; 9 | var TRIANGLE = 4; 10 | var BREAKER = 5; 11 | 12 | var SHAPES = [ 13 | 'square', 'sawtooth', 'sine', 'noise', 'triangle', 'breaker' 14 | ]; 15 | 16 | var AUDIO_CONTEXT; 17 | 18 | if (typeof AudioContext != 'undefined') { 19 | AUDIO_CONTEXT = new AudioContext(); 20 | } else if (typeof webkitAudioContext != 'undefined') { 21 | AUDIO_CONTEXT = new webkitAudioContext(); 22 | } 23 | 24 | // Playback volume 25 | var masterVolume = 1.0; 26 | 27 | // Sound generation parameters are on [0,1] unless noted SIGNED, & thus [-1,1] 28 | function Params() { 29 | var result={}; 30 | // Wave shape 31 | result.wave_type = SQUARE; 32 | 33 | // Envelope 34 | result.p_env_attack = 0.0; // Attack time 35 | result.p_env_sustain = 0.3; // Sustain time 36 | result.p_env_punch = 0.0; // Sustain punch 37 | result.p_env_decay = 0.4; // Decay time 38 | 39 | // Tone 40 | result.p_base_freq = 0.3; // Start frequency 41 | result.p_freq_limit = 0.0; // Min frequency cutoff 42 | result.p_freq_ramp = 0.0; // Slide (SIGNED) 43 | result.p_freq_dramp = 0.0; // Delta slide (SIGNED) 44 | // Vibrato 45 | result.p_vib_strength = 0.0; // Vibrato depth 46 | result.p_vib_speed = 0.0; // Vibrato speed 47 | 48 | // Tonal change 49 | result.p_arp_mod = 0.0; // Change amount (SIGNED) 50 | result.p_arp_speed = 0.0; // Change speed 51 | 52 | // Duty (wat's that?) 53 | result.p_duty = 0.0; // Square duty 54 | result.p_duty_ramp = 0.0; // Duty sweep (SIGNED) 55 | 56 | // Repeat 57 | result.p_repeat_speed = 0.0; // Repeat speed 58 | 59 | // Phaser 60 | result.p_pha_offset = 0.0; // Phaser offset (SIGNED) 61 | result.p_pha_ramp = 0.0; // Phaser sweep (SIGNED) 62 | 63 | // Low-pass filter 64 | result.p_lpf_freq = 1.0; // Low-pass filter cutoff 65 | result.p_lpf_ramp = 0.0; // Low-pass filter cutoff sweep (SIGNED) 66 | result.p_lpf_resonance = 0.0;// Low-pass filter resonance 67 | // High-pass filter 68 | result.p_hpf_freq = 0.0; // High-pass filter cutoff 69 | result.p_hpf_ramp = 0.0; // High-pass filter cutoff sweep (SIGNED) 70 | 71 | // Sample parameters 72 | result.sound_vol = 0.5; 73 | result.sample_rate = 44100; 74 | result.bit_depth = 8; 75 | return result; 76 | } 77 | 78 | var rng; 79 | var seeded = false; 80 | function frnd(range) { 81 | if (seeded) { 82 | return rng.uniform() * range; 83 | } else { 84 | return Math.random() * range; 85 | } 86 | } 87 | 88 | 89 | function rnd(max) { 90 | if (seeded) { 91 | return Math.floor(rng.uniform() * (max + 1)); 92 | } else { 93 | return Math.floor(Math.random() * (max + 1)); 94 | } 95 | } 96 | 97 | 98 | pickupCoin = function() { 99 | var result=Params(); 100 | result.wave_type = Math.floor(frnd(SHAPES.length)); 101 | if (result.wave_type === 3) { 102 | result.wave_type = 0; 103 | } 104 | result.p_base_freq = 0.4 + frnd(0.5); 105 | result.p_env_attack = 0.0; 106 | result.p_env_sustain = frnd(0.1); 107 | result.p_env_decay = 0.1 + frnd(0.4); 108 | result.p_env_punch = 0.3 + frnd(0.3); 109 | if (rnd(1)) { 110 | result.p_arp_speed = 0.5 + frnd(0.2); 111 | var num = (frnd(7) | 1) + 1; 112 | var den = num + (frnd(7) | 1) + 2; 113 | result.p_arp_mod = (+num) / (+den); //0.2 + frnd(0.4); 114 | } 115 | return result; 116 | }; 117 | 118 | 119 | laserShoot = function() { 120 | var result=Params(); 121 | result.wave_type = rnd(2); 122 | if (result.wave_type === SINE && rnd(1)) 123 | result.wave_type = rnd(1); 124 | result.wave_type = Math.floor(frnd(SHAPES.length)); 125 | 126 | if (result.wave_type === 3) { 127 | result.wave_type = SQUARE; 128 | } 129 | 130 | result.p_base_freq = 0.5 + frnd(0.5); 131 | result.p_freq_limit = result.p_base_freq - 0.2 - frnd(0.6); 132 | if (result.p_freq_limit < 0.2) result.p_freq_limit = 0.2; 133 | result.p_freq_ramp = -0.15 - frnd(0.2); 134 | if (rnd(2) === 0) 135 | { 136 | result.p_base_freq = 0.3 + frnd(0.6); 137 | result.p_freq_limit = frnd(0.1); 138 | result.p_freq_ramp = -0.35 - frnd(0.3); 139 | } 140 | if (rnd(1)) 141 | { 142 | result.p_duty = frnd(0.5); 143 | result.p_duty_ramp = frnd(0.2); 144 | } 145 | else 146 | { 147 | result.p_duty = 0.4 + frnd(0.5); 148 | result.p_duty_ramp = -frnd(0.7); 149 | } 150 | result.p_env_attack = 0.0; 151 | result.p_env_sustain = 0.1 + frnd(0.2); 152 | result.p_env_decay = frnd(0.4); 153 | if (rnd(1)) 154 | result.p_env_punch = frnd(0.3); 155 | if (rnd(2) === 0) 156 | { 157 | result.p_pha_offset = frnd(0.2); 158 | result.p_pha_ramp = -frnd(0.2); 159 | } 160 | if (rnd(1)) 161 | result.p_hpf_freq = frnd(0.3); 162 | 163 | return result; 164 | }; 165 | 166 | explosion = function() { 167 | var result=Params(); 168 | 169 | if (rnd(1)) { 170 | result.p_base_freq = 0.1 + frnd(0.4); 171 | result.p_freq_ramp = -0.1 + frnd(0.4); 172 | } else { 173 | result.p_base_freq = 0.2 + frnd(0.7); 174 | result.p_freq_ramp = -0.2 - frnd(0.2); 175 | } 176 | result.p_base_freq *= result.p_base_freq; 177 | if (rnd(4) === 0) 178 | result.p_freq_ramp = 0.0; 179 | if (rnd(2) === 0) 180 | result.p_repeat_speed = 0.3 + frnd(0.5); 181 | result.p_env_attack = 0.0; 182 | result.p_env_sustain = 0.1 + frnd(0.3); 183 | result.p_env_decay = frnd(0.5); 184 | if (rnd(1) === 0) { 185 | result.p_pha_offset = -0.3 + frnd(0.9); 186 | result.p_pha_ramp = -frnd(0.3); 187 | } 188 | result.p_env_punch = 0.2 + frnd(0.6); 189 | if (rnd(1)) { 190 | result.p_vib_strength = frnd(0.7); 191 | result.p_vib_speed = frnd(0.6); 192 | } 193 | if (rnd(2) === 0) { 194 | result.p_arp_speed = 0.6 + frnd(0.3); 195 | result.p_arp_mod = 0.8 - frnd(1.6); 196 | } 197 | 198 | return result; 199 | }; 200 | //9675111 201 | birdSound = function() { 202 | var result=Params(); 203 | 204 | if (frnd(10) < 1) { 205 | result.wave_type = Math.floor(frnd(SHAPES.length)); 206 | if (result.wave_type === 3) { 207 | result.wave_type = SQUARE; 208 | } 209 | result.p_env_attack = 0.4304400932967592 + frnd(0.2) - 0.1; 210 | result.p_env_sustain = 0.15739346034252394 + frnd(0.2) - 0.1; 211 | result.p_env_punch = 0.004488201744871758 + frnd(0.2) - 0.1; 212 | result.p_env_decay = 0.07478075528212291 + frnd(0.2) - 0.1; 213 | result.p_base_freq = 0.9865265720147687 + frnd(0.2) - 0.1; 214 | result.p_freq_limit = 0 + frnd(0.2) - 0.1; 215 | result.p_freq_ramp = -0.2995018224359539 + frnd(0.2) - 0.1; 216 | if (frnd(1.0) < 0.5) { 217 | result.p_freq_ramp = 0.1 + frnd(0.15); 218 | } 219 | result.p_freq_dramp = 0.004598608156964473 + frnd(0.1) - 0.05; 220 | result.p_vib_strength = -0.2202799497929496 + frnd(0.2) - 0.1; 221 | result.p_vib_speed = 0.8084998703158364 + frnd(0.2) - 0.1; 222 | result.p_arp_mod = 0;//-0.46410459213693644+frnd(0.2)-0.1; 223 | result.p_arp_speed = 0;//-0.10955361249587248+frnd(0.2)-0.1; 224 | result.p_duty = -0.9031808754347107 + frnd(0.2) - 0.1; 225 | result.p_duty_ramp = -0.8128699999808343 + frnd(0.2) - 0.1; 226 | result.p_repeat_speed = 0.6014860189319991 + frnd(0.2) - 0.1; 227 | result.p_pha_offset = -0.9424902314367765 + frnd(0.2) - 0.1; 228 | result.p_pha_ramp = -0.1055482222272056 + frnd(0.2) - 0.1; 229 | result.p_lpf_freq = 0.9989765717851521 + frnd(0.2) - 0.1; 230 | result.p_lpf_ramp = -0.25051720626043017 + frnd(0.2) - 0.1; 231 | result.p_lpf_resonance = 0.32777871505494693 + frnd(0.2) - 0.1; 232 | result.p_hpf_freq = 0.0023548750981756753 + frnd(0.2) - 0.1; 233 | result.p_hpf_ramp = -0.002375673204842568 + frnd(0.2) - 0.1; 234 | return result; 235 | } 236 | 237 | if (frnd(10) < 1) { 238 | result.wave_type = Math.floor(frnd(SHAPES.length)); 239 | if (result.wave_type === 3) { 240 | result.wave_type = SQUARE; 241 | } 242 | result.p_env_attack = 0.5277795946672003 + frnd(0.2) - 0.1; 243 | result.p_env_sustain = 0.18243733568468432 + frnd(0.2) - 0.1; 244 | result.p_env_punch = -0.020159754546840117 + frnd(0.2) - 0.1; 245 | result.p_env_decay = 0.1561353422051903 + frnd(0.2) - 0.1; 246 | result.p_base_freq = 0.9028855606533718 + frnd(0.2) - 0.1; 247 | result.p_freq_limit = -0.008842787837148716; 248 | result.p_freq_ramp = -0.1; 249 | result.p_freq_dramp = -0.012891241489551925; 250 | result.p_vib_strength = -0.17923136138403065 + frnd(0.2) - 0.1; 251 | result.p_vib_speed = 0.908263385610142 + frnd(0.2) - 0.1; 252 | result.p_arp_mod = 0.41690153355414894 + frnd(0.2) - 0.1; 253 | result.p_arp_speed = 0.0010766233195860703 + frnd(0.2) - 0.1; 254 | result.p_duty = -0.8735363011184684 + frnd(0.2) - 0.1; 255 | result.p_duty_ramp = -0.7397985366747507 + frnd(0.2) - 0.1; 256 | result.p_repeat_speed = 0.0591789344172107 + frnd(0.2) - 0.1; 257 | result.p_pha_offset = -0.9961184222777699 + frnd(0.2) - 0.1; 258 | result.p_pha_ramp = -0.08234769395850523 + frnd(0.2) - 0.1; 259 | result.p_lpf_freq = 0.9412475115697335 + frnd(0.2) - 0.1; 260 | result.p_lpf_ramp = -0.18261358925834958 + frnd(0.2) - 0.1; 261 | result.p_lpf_resonance = 0.24541438107389477 + frnd(0.2) - 0.1; 262 | result.p_hpf_freq = -0.01831940280978611 + frnd(0.2) - 0.1; 263 | result.p_hpf_ramp = -0.03857383633171346 + frnd(0.2) - 0.1; 264 | return result; 265 | 266 | } 267 | if (frnd(10) < 1) { 268 | //result.wave_type = 4; 269 | result.wave_type = Math.floor(frnd(SHAPES.length)); 270 | 271 | if (result.wave_type === 3) { 272 | result.wave_type = SQUARE; 273 | } 274 | result.p_env_attack = 0.4304400932967592 + frnd(0.2) - 0.1; 275 | result.p_env_sustain = 0.15739346034252394 + frnd(0.2) - 0.1; 276 | result.p_env_punch = 0.004488201744871758 + frnd(0.2) - 0.1; 277 | result.p_env_decay = 0.07478075528212291 + frnd(0.2) - 0.1; 278 | result.p_base_freq = 0.9865265720147687 + frnd(0.2) - 0.1; 279 | result.p_freq_limit = 0 + frnd(0.2) - 0.1; 280 | result.p_freq_ramp = -0.2995018224359539 + frnd(0.2) - 0.1; 281 | result.p_freq_dramp = 0.004598608156964473 + frnd(0.2) - 0.1; 282 | result.p_vib_strength = -0.2202799497929496 + frnd(0.2) - 0.1; 283 | result.p_vib_speed = 0.8084998703158364 + frnd(0.2) - 0.1; 284 | result.p_arp_mod = -0.46410459213693644 + frnd(0.2) - 0.1; 285 | result.p_arp_speed = -0.10955361249587248 + frnd(0.2) - 0.1; 286 | result.p_duty = -0.9031808754347107 + frnd(0.2) - 0.1; 287 | result.p_duty_ramp = -0.8128699999808343 + frnd(0.2) - 0.1; 288 | result.p_repeat_speed = 0.7014860189319991 + frnd(0.2) - 0.1; 289 | result.p_pha_offset = -0.9424902314367765 + frnd(0.2) - 0.1; 290 | result.p_pha_ramp = -0.1055482222272056 + frnd(0.2) - 0.1; 291 | result.p_lpf_freq = 0.9989765717851521 + frnd(0.2) - 0.1; 292 | result.p_lpf_ramp = -0.25051720626043017 + frnd(0.2) - 0.1; 293 | result.p_lpf_resonance = 0.32777871505494693 + frnd(0.2) - 0.1; 294 | result.p_hpf_freq = 0.0023548750981756753 + frnd(0.2) - 0.1; 295 | result.p_hpf_ramp = -0.002375673204842568 + frnd(0.2) - 0.1; 296 | return result; 297 | } 298 | if (frnd(5) > 1) { 299 | result.wave_type = Math.floor(frnd(SHAPES.length)); 300 | 301 | if (result.wave_type === 3) { 302 | result.wave_type = SQUARE; 303 | } 304 | if (rnd(1)) { 305 | result.p_arp_mod = 0.2697849293151393 + frnd(0.2) - 0.1; 306 | result.p_arp_speed = -0.3131172257760948 + frnd(0.2) - 0.1; 307 | result.p_base_freq = 0.8090588299313949 + frnd(0.2) - 0.1; 308 | result.p_duty = -0.6210022920964955 + frnd(0.2) - 0.1; 309 | result.p_duty_ramp = -0.00043441813553182567 + frnd(0.2) - 0.1; 310 | result.p_env_attack = 0.004321877246874195 + frnd(0.2) - 0.1; 311 | result.p_env_decay = 0.1 + frnd(0.2) - 0.1; 312 | result.p_env_punch = 0.061737781504416146 + frnd(0.2) - 0.1; 313 | result.p_env_sustain = 0.4987252564798832 + frnd(0.2) - 0.1; 314 | result.p_freq_dramp = 0.31700340314222614 + frnd(0.2) - 0.1; 315 | result.p_freq_limit = 0 + frnd(0.2) - 0.1; 316 | result.p_freq_ramp = -0.163380391341416 + frnd(0.2) - 0.1; 317 | result.p_hpf_freq = 0.4709005021145149 + frnd(0.2) - 0.1; 318 | result.p_hpf_ramp = 0.6924667290539194 + frnd(0.2) - 0.1; 319 | result.p_lpf_freq = 0.8351398631384511 + frnd(0.2) - 0.1; 320 | result.p_lpf_ramp = 0.36616557192873134 + frnd(0.2) - 0.1; 321 | result.p_lpf_resonance = -0.08685777111664439 + frnd(0.2) - 0.1; 322 | result.p_pha_offset = -0.036084571580025544 + frnd(0.2) - 0.1; 323 | result.p_pha_ramp = -0.014806445085568108 + frnd(0.2) - 0.1; 324 | result.p_repeat_speed = -0.8094368475518489 + frnd(0.2) - 0.1; 325 | result.p_vib_speed = 0.4496665457171294 + frnd(0.2) - 0.1; 326 | result.p_vib_strength = 0.23413762515532424 + frnd(0.2) - 0.1; 327 | } else { 328 | result.p_arp_mod = -0.35697118026766184 + frnd(0.2) - 0.1; 329 | result.p_arp_speed = 0.3581140690559588 + frnd(0.2) - 0.1; 330 | result.p_base_freq = 1.3260897696157528 + frnd(0.2) - 0.1; 331 | result.p_duty = -0.30984900436710694 + frnd(0.2) - 0.1; 332 | result.p_duty_ramp = -0.0014374759133411626 + frnd(0.2) - 0.1; 333 | result.p_env_attack = 0.3160357835682254 + frnd(0.2) - 0.1; 334 | result.p_env_decay = 0.1 + frnd(0.2) - 0.1; 335 | result.p_env_punch = 0.24323114016870148 + frnd(0.2) - 0.1; 336 | result.p_env_sustain = 0.4 + frnd(0.2) - 0.1; 337 | result.p_freq_dramp = 0.2866475886237244 + frnd(0.2) - 0.1; 338 | result.p_freq_limit = 0 + frnd(0.2) - 0.1; 339 | result.p_freq_ramp = -0.10956352368742976 + frnd(0.2) - 0.1; 340 | result.p_hpf_freq = 0.20772718017889846 + frnd(0.2) - 0.1; 341 | result.p_hpf_ramp = 0.1564090637378835 + frnd(0.2) - 0.1; 342 | result.p_lpf_freq = 0.6021372770637031 + frnd(0.2) - 0.1; 343 | result.p_lpf_ramp = 0.24016227139979027 + frnd(0.2) - 0.1; 344 | result.p_lpf_resonance = -0.08787383821160144 + frnd(0.2) - 0.1; 345 | result.p_pha_offset = -0.381597686151701 + frnd(0.2) - 0.1; 346 | result.p_pha_ramp = -0.0002481687661373495 + frnd(0.2) - 0.1; 347 | result.p_repeat_speed = 0.07812112809425686 + frnd(0.2) - 0.1; 348 | result.p_vib_speed = -0.13648848579133943 + frnd(0.2) - 0.1; 349 | result.p_vib_strength = 0.0018874158972302657 + frnd(0.2) - 0.1; 350 | } 351 | return result; 352 | 353 | } 354 | 355 | result.wave_type = Math.floor(frnd(SHAPES.length));//TRIANGLE; 356 | if (result.wave_type === 1 || result.wave_type === 3) { 357 | result.wave_type = 2; 358 | } 359 | //new 360 | result.p_base_freq = 0.85 + frnd(0.15); 361 | result.p_freq_ramp = 0.3 + frnd(0.15); 362 | // result.p_freq_dramp = 0.3+frnd(2.0); 363 | 364 | result.p_env_attack = 0 + frnd(0.09); 365 | result.p_env_sustain = 0.2 + frnd(0.3); 366 | result.p_env_decay = 0 + frnd(0.1); 367 | 368 | result.p_duty = frnd(2.0) - 1.0; 369 | result.p_duty_ramp = Math.pow(frnd(2.0) - 1.0, 3.0); 370 | 371 | 372 | result.p_repeat_speed = 0.5 + frnd(0.1); 373 | 374 | result.p_pha_offset = -0.3 + frnd(0.9); 375 | result.p_pha_ramp = -frnd(0.3); 376 | 377 | result.p_arp_speed = 0.4 + frnd(0.6); 378 | result.p_arp_mod = 0.8 + frnd(0.1); 379 | 380 | 381 | result.p_lpf_resonance = frnd(2.0) - 1.0; 382 | result.p_lpf_freq = 1.0 - Math.pow(frnd(1.0), 3.0); 383 | result.p_lpf_ramp = Math.pow(frnd(2.0) - 1.0, 3.0); 384 | if (result.p_lpf_freq < 0.1 && result.p_lpf_ramp < -0.05) 385 | result.p_lpf_ramp = -result.p_lpf_ramp; 386 | result.p_hpf_freq = Math.pow(frnd(1.0), 5.0); 387 | result.p_hpf_ramp = Math.pow(frnd(2.0) - 1.0, 5.0); 388 | 389 | return result; 390 | }; 391 | 392 | 393 | pushSound = function() { 394 | var result=Params(); 395 | result.wave_type = Math.floor(frnd(SHAPES.length));//TRIANGLE; 396 | if (result.wave_type === 2) { 397 | result.wave_type++; 398 | } 399 | if (result.wave_type === 0) { 400 | result.wave_type = NOISE; 401 | } 402 | //new 403 | result.p_base_freq = 0.1 + frnd(0.4); 404 | result.p_freq_ramp = 0.05 + frnd(0.2); 405 | 406 | result.p_env_attack = 0.01 + frnd(0.09); 407 | result.p_env_sustain = 0.01 + frnd(0.09); 408 | result.p_env_decay = 0.01 + frnd(0.09); 409 | 410 | result.p_repeat_speed = 0.3 + frnd(0.5); 411 | result.p_pha_offset = -0.3 + frnd(0.9); 412 | result.p_pha_ramp = -frnd(0.3); 413 | result.p_arp_speed = 0.6 + frnd(0.3); 414 | result.p_arp_mod = 0.8 - frnd(1.6); 415 | 416 | return result; 417 | }; 418 | 419 | 420 | 421 | powerUp = function() { 422 | var result=Params(); 423 | if (rnd(1)) 424 | result.wave_type = SAWTOOTH; 425 | else 426 | result.p_duty = frnd(0.6); 427 | result.wave_type = Math.floor(frnd(SHAPES.length)); 428 | if (result.wave_type === 3) { 429 | result.wave_type = SQUARE; 430 | } 431 | if (rnd(1)) 432 | { 433 | result.p_base_freq = 0.2 + frnd(0.3); 434 | result.p_freq_ramp = 0.1 + frnd(0.4); 435 | result.p_repeat_speed = 0.4 + frnd(0.4); 436 | } 437 | else 438 | { 439 | result.p_base_freq = 0.2 + frnd(0.3); 440 | result.p_freq_ramp = 0.05 + frnd(0.2); 441 | if (rnd(1)) 442 | { 443 | result.p_vib_strength = frnd(0.7); 444 | result.p_vib_speed = frnd(0.6); 445 | } 446 | } 447 | result.p_env_attack = 0.0; 448 | result.p_env_sustain = frnd(0.4); 449 | result.p_env_decay = 0.1 + frnd(0.4); 450 | 451 | return result; 452 | }; 453 | 454 | hitHurt = function() { 455 | result = Params(); 456 | result.wave_type = rnd(2); 457 | if (result.wave_type === SINE) 458 | result.wave_type = NOISE; 459 | if (result.wave_type === SQUARE) 460 | result.p_duty = frnd(0.6); 461 | result.wave_type = Math.floor(frnd(SHAPES.length)); 462 | result.p_base_freq = 0.2 + frnd(0.6); 463 | result.p_freq_ramp = -0.3 - frnd(0.4); 464 | result.p_env_attack = 0.0; 465 | result.p_env_sustain = frnd(0.1); 466 | result.p_env_decay = 0.1 + frnd(0.2); 467 | if (rnd(1)) 468 | result.p_hpf_freq = frnd(0.3); 469 | return result; 470 | }; 471 | 472 | 473 | jump = function() { 474 | result = Params(); 475 | result.wave_type = SQUARE; 476 | result.wave_type = Math.floor(frnd(SHAPES.length)); 477 | if (result.wave_type === 3) { 478 | result.wave_type = SQUARE; 479 | } 480 | result.p_duty = frnd(0.6); 481 | result.p_base_freq = 0.3 + frnd(0.3); 482 | result.p_freq_ramp = 0.1 + frnd(0.2); 483 | result.p_env_attack = 0.0; 484 | result.p_env_sustain = 0.1 + frnd(0.3); 485 | result.p_env_decay = 0.1 + frnd(0.2); 486 | if (rnd(1)) 487 | result.p_hpf_freq = frnd(0.3); 488 | if (rnd(1)) 489 | result.p_lpf_freq = 1.0 - frnd(0.6); 490 | return result; 491 | }; 492 | 493 | blipSelect = function() { 494 | result = Params(); 495 | result.wave_type = rnd(1); 496 | result.wave_type = Math.floor(frnd(SHAPES.length)); 497 | if (result.wave_type === 3) { 498 | result.wave_type = rnd(1); 499 | } 500 | if (result.wave_type === SQUARE) 501 | result.p_duty = frnd(0.6); 502 | result.p_base_freq = 0.2 + frnd(0.4); 503 | result.p_env_attack = 0.0; 504 | result.p_env_sustain = 0.1 + frnd(0.1); 505 | result.p_env_decay = frnd(0.2); 506 | result.p_hpf_freq = 0.1; 507 | return result; 508 | }; 509 | 510 | random = function() { 511 | result = Params(); 512 | result.wave_type = Math.floor(frnd(SHAPES.length)); 513 | result.p_base_freq = Math.pow(frnd(2.0) - 1.0, 2.0); 514 | if (rnd(1)) 515 | result.p_base_freq = Math.pow(frnd(2.0) - 1.0, 3.0) + 0.5; 516 | result.p_freq_limit = 0.0; 517 | result.p_freq_ramp = Math.pow(frnd(2.0) - 1.0, 5.0); 518 | if (result.p_base_freq > 0.7 && result.p_freq_ramp > 0.2) 519 | result.p_freq_ramp = -result.p_freq_ramp; 520 | if (result.p_base_freq < 0.2 && result.p_freq_ramp < -0.05) 521 | result.p_freq_ramp = -result.p_freq_ramp; 522 | result.p_freq_dramp = Math.pow(frnd(2.0) - 1.0, 3.0); 523 | result.p_duty = frnd(2.0) - 1.0; 524 | result.p_duty_ramp = Math.pow(frnd(2.0) - 1.0, 3.0); 525 | result.p_vib_strength = Math.pow(frnd(2.0) - 1.0, 3.0); 526 | result.p_vib_speed = frnd(2.0) - 1.0; 527 | result.p_env_attack = Math.pow(frnd(2.0) - 1.0, 3.0); 528 | result.p_env_sustain = Math.pow(frnd(2.0) - 1.0, 2.0); 529 | result.p_env_decay = frnd(2.0) - 1.0; 530 | result.p_env_punch = Math.pow(frnd(0.8), 2.0); 531 | if (result.p_env_attack + result.p_env_sustain + result.p_env_decay < 0.2) { 532 | result.p_env_sustain += 0.2 + frnd(0.3); 533 | result.p_env_decay += 0.2 + frnd(0.3); 534 | } 535 | result.p_lpf_resonance = frnd(2.0) - 1.0; 536 | result.p_lpf_freq = 1.0 - Math.pow(frnd(1.0), 3.0); 537 | result.p_lpf_ramp = Math.pow(frnd(2.0) - 1.0, 3.0); 538 | if (result.p_lpf_freq < 0.1 && result.p_lpf_ramp < -0.05) 539 | result.p_lpf_ramp = -result.p_lpf_ramp; 540 | result.p_hpf_freq = Math.pow(frnd(1.0), 5.0); 541 | result.p_hpf_ramp = Math.pow(frnd(2.0) - 1.0, 5.0); 542 | result.p_pha_offset = Math.pow(frnd(2.0) - 1.0, 3.0); 543 | result.p_pha_ramp = Math.pow(frnd(2.0) - 1.0, 3.0); 544 | result.p_repeat_speed = frnd(2.0) - 1.0; 545 | result.p_arp_speed = frnd(2.0) - 1.0; 546 | result.p_arp_mod = frnd(2.0) - 1.0; 547 | return result; 548 | }; 549 | 550 | var generators = [ 551 | pickupCoin, 552 | laserShoot, 553 | explosion, 554 | powerUp, 555 | hitHurt, 556 | jump, 557 | blipSelect, 558 | pushSound, 559 | random, 560 | birdSound 561 | ]; 562 | 563 | var generatorNames = [ 564 | 'pickupCoin', 565 | 'laserShoot', 566 | 'explosion', 567 | 'powerUp', 568 | 'hitHurt', 569 | 'jump', 570 | 'blipSelect', 571 | 'pushSound', 572 | 'random', 573 | 'birdSound' 574 | ]; 575 | 576 | /* 577 | i like 9675111 578 | */ 579 | generateFromSeed = function(seed) { 580 | rng = new RNG((seed / 100) | 0); 581 | var generatorindex = seed % 100; 582 | var soundGenerator = generators[generatorindex % generators.length]; 583 | seeded = true; 584 | var result = soundGenerator(); 585 | result.seed = seed; 586 | seeded = false; 587 | return result; 588 | }; 589 | 590 | function SoundEffect(length, sample_rate) { 591 | this._buffer = AUDIO_CONTEXT.createBuffer(1, length, sample_rate); 592 | } 593 | 594 | SoundEffect.prototype.getBuffer = function() { 595 | return this._buffer.getChannelData(0); 596 | }; 597 | 598 | 599 | SoundEffect.prototype.play = function() { 600 | var source = AUDIO_CONTEXT.createBufferSource(); 601 | 602 | source.buffer = this._buffer; 603 | source.connect(AUDIO_CONTEXT.destination); 604 | 605 | 606 | var t = AUDIO_CONTEXT.currentTime; 607 | if (typeof source.start != 'undefined') { 608 | source.start(t); 609 | } else { 610 | source.noteOn(t); 611 | } 612 | }; 613 | 614 | SoundEffect.MIN_SAMPLE_RATE = 22050; 615 | 616 | if (typeof AUDIO_CONTEXT == 'undefined') { 617 | SoundEffect = function SoundEffect(length, sample_rate) { 618 | this._sample_rate = sample_rate; 619 | this._buffer = new Array(length); 620 | this._audioElement = null; 621 | }; 622 | 623 | SoundEffect.prototype.getBuffer = function() { 624 | this._audioElement = null; 625 | return this._buffer; 626 | }; 627 | 628 | SoundEffect.prototype.play = function() { 629 | if (this._audioElement) { 630 | this._audioElement.cloneNode(false).play(); 631 | } else { 632 | for (var i = 0; i < this._buffer.length; i++) { 633 | // bit_depth is always 8, rescale [-1.0, 1.0) to [0, 256) 634 | this._buffer[i] = 255 & Math.floor(128 * Math.max(0, Math.min(this._buffer[i] + 1, 2))); 635 | } 636 | var wav = MakeRiff(this._sample_rate, BIT_DEPTH, this._buffer); 637 | this._audioElement = new Audio(); 638 | this._audioElement.src = wav.dataURI; 639 | this._audioElement.play(); 640 | } 641 | }; 642 | 643 | SoundEffect.MIN_SAMPLE_RATE = 1; 644 | } 645 | 646 | SoundEffect.generate = function(ps) { 647 | /* window.console.log(ps.wave_type + "\t" + ps.seed); 648 | 649 | var psstring=""; 650 | for (var n in ps) { 651 | if (ps.hasOwnProperty(n)) { 652 | psstring = psstring +"result." + n+" = " + ps[n] + ";\n"; 653 | } 654 | } 655 | window.console.log(ps); 656 | window.console.log(psstring);*/ 657 | function repeat() { 658 | rep_time = 0; 659 | 660 | fperiod = 100.0 / (ps.p_base_freq * ps.p_base_freq + 0.001); 661 | period = Math.floor(fperiod); 662 | fmaxperiod = 100.0 / (ps.p_freq_limit * ps.p_freq_limit + 0.001); 663 | 664 | fslide = 1.0 - Math.pow(ps.p_freq_ramp, 3.0) * 0.01; 665 | fdslide = -Math.pow(ps.p_freq_dramp, 3.0) * 0.000001; 666 | 667 | square_duty = 0.5 - ps.p_duty * 0.5; 668 | square_slide = -ps.p_duty_ramp * 0.00005; 669 | 670 | if (ps.p_arp_mod >= 0.0) 671 | arp_mod = 1.0 - Math.pow(ps.p_arp_mod, 2.0) * 0.9; 672 | else 673 | arp_mod = 1.0 + Math.pow(ps.p_arp_mod, 2.0) * 10.0; 674 | arp_time = 0; 675 | arp_limit = Math.floor(Math.pow(1.0 - ps.p_arp_speed, 2.0) * 20000 + 32); 676 | if (ps.p_arp_speed == 1.0) 677 | arp_limit = 0; 678 | }; 679 | 680 | var rep_time; 681 | var fperiod, period, fmaxperiod; 682 | var fslide, fdslide; 683 | var square_duty, square_slide; 684 | var arp_mod, arp_time, arp_limit; 685 | repeat(); // First time through, this is a bit of a misnomer 686 | 687 | // Filter 688 | var fltp = 0.0; 689 | var fltdp = 0.0; 690 | var fltw = Math.pow(ps.p_lpf_freq, 3.0) * 0.1; 691 | var fltw_d = 1.0 + ps.p_lpf_ramp * 0.0001; 692 | var fltdmp = 5.0 / (1.0 + Math.pow(ps.p_lpf_resonance, 2.0) * 20.0) * 693 | (0.01 + fltw); 694 | if (fltdmp > 0.8) fltdmp = 0.8; 695 | var fltphp = 0.0; 696 | var flthp = Math.pow(ps.p_hpf_freq, 2.0) * 0.1; 697 | var flthp_d = 1.0 + ps.p_hpf_ramp * 0.0003; 698 | 699 | // Vibrato 700 | var vib_phase = 0.0; 701 | var vib_speed = Math.pow(ps.p_vib_speed, 2.0) * 0.01; 702 | var vib_amp = ps.p_vib_strength * 0.5; 703 | 704 | // Envelope 705 | var env_vol = 0.0; 706 | var env_stage = 0; 707 | var env_time = 0; 708 | var env_length = [ 709 | Math.floor(ps.p_env_attack * ps.p_env_attack * 100000.0), 710 | Math.floor(ps.p_env_sustain * ps.p_env_sustain * 100000.0), 711 | Math.floor(ps.p_env_decay * ps.p_env_decay * 100000.0) 712 | ]; 713 | var env_total_length = env_length[0] + env_length[1] + env_length[2]; 714 | 715 | // Phaser 716 | var phase = 0; 717 | var fphase = Math.pow(ps.p_pha_offset, 2.0) * 1020.0; 718 | if (ps.p_pha_offset < 0.0) fphase = -fphase; 719 | var fdphase = Math.pow(ps.p_pha_ramp, 2.0) * 1.0; 720 | if (ps.p_pha_ramp < 0.0) fdphase = -fdphase; 721 | var iphase = Math.abs(Math.floor(fphase)); 722 | var ipp = 0; 723 | var phaser_buffer = []; 724 | for (var i = 0; i < 1024; ++i) 725 | phaser_buffer[i] = 0.0; 726 | 727 | // Noise 728 | var noise_buffer = []; 729 | for (var i = 0; i < 32; ++i) 730 | noise_buffer[i] = Math.random() * 2.0 - 1.0; 731 | 732 | // Repeat 733 | var rep_limit = Math.floor(Math.pow(1.0 - ps.p_repeat_speed, 2.0) * 20000 734 | + 32); 735 | if (ps.p_repeat_speed == 0.0) 736 | rep_limit = 0; 737 | 738 | //var gain = 2.0 * Math.log(1 + (Math.E - 1) * ps.sound_vol); 739 | var gain = 2.0 * ps.sound_vol; 740 | var gain = Math.exp(ps.sound_vol) - 1; 741 | 742 | var num_clipped = 0; 743 | 744 | // ...end of initialization. Generate samples. 745 | 746 | var sample_sum = 0; 747 | var num_summed = 0; 748 | var summands = Math.floor(44100 / ps.sample_rate); 749 | 750 | var buffer_i = 0; 751 | var buffer_length = Math.ceil(env_total_length / summands); 752 | var buffer_complete = false; 753 | 754 | var sound; 755 | if (ps.sample_rate < SoundEffect.MIN_SAMPLE_RATE) { 756 | // Assume 4x gets close enough to MIN_SAMPLE_RATE 757 | sound = new SoundEffect(4 * buffer_length, SoundEffect.MIN_SAMPLE_RATE); 758 | } else { 759 | sound = new SoundEffect(buffer_length, ps.sample_rate) 760 | } 761 | var buffer = sound.getBuffer(); 762 | 763 | for (var t = 0;; ++t) { 764 | 765 | // Repeats 766 | if (rep_limit != 0 && ++rep_time >= rep_limit) 767 | repeat(); 768 | 769 | // Arpeggio (single) 770 | if (arp_limit != 0 && t >= arp_limit) { 771 | arp_limit = 0; 772 | fperiod *= arp_mod; 773 | } 774 | 775 | // Frequency slide, and frequency slide slide! 776 | fslide += fdslide; 777 | fperiod *= fslide; 778 | if (fperiod > fmaxperiod) { 779 | fperiod = fmaxperiod; 780 | if (ps.p_freq_limit > 0.0) 781 | buffer_complete = true; 782 | } 783 | 784 | // Vibrato 785 | var rfperiod = fperiod; 786 | if (vib_amp > 0.0) { 787 | vib_phase += vib_speed; 788 | rfperiod = fperiod * (1.0 + Math.sin(vib_phase) * vib_amp); 789 | } 790 | period = Math.floor(rfperiod); 791 | if (period < 8) period = 8; 792 | 793 | square_duty += square_slide; 794 | if (square_duty < 0.0) square_duty = 0.0; 795 | if (square_duty > 0.5) square_duty = 0.5; 796 | 797 | // Volume envelope 798 | env_time++; 799 | if (env_time > env_length[env_stage]) { 800 | env_time = 0; 801 | env_stage++; 802 | if (env_stage === 3) 803 | buffer_complete = true; 804 | } 805 | if (env_stage === 0) 806 | env_vol = env_time / env_length[0]; 807 | else if (env_stage === 1) 808 | env_vol = 1.0 + Math.pow(1.0 - env_time / env_length[1], 809 | 1.0) * 2.0 * ps.p_env_punch; 810 | else // env_stage == 2 811 | env_vol = 1.0 - env_time / env_length[2]; 812 | 813 | // Phaser step 814 | fphase += fdphase; 815 | iphase = Math.abs(Math.floor(fphase)); 816 | if (iphase > 1023) iphase = 1023; 817 | 818 | if (flthp_d != 0.0) { 819 | flthp *= flthp_d; 820 | if (flthp < 0.00001) 821 | flthp = 0.00001; 822 | if (flthp > 0.1) 823 | flthp = 0.1; 824 | } 825 | 826 | // 8x supersampling 827 | var sample = 0.0; 828 | for (var si = 0; si < 8; ++si) { 829 | var sub_sample = 0.0; 830 | phase++; 831 | if (phase >= period) { 832 | phase %= period; 833 | if (ps.wave_type === NOISE) 834 | for (var i = 0; i < 32; ++i) 835 | noise_buffer[i] = Math.random() * 2.0 - 1.0; 836 | } 837 | 838 | // Base waveform 839 | var fp = phase / period; 840 | if (ps.wave_type === SQUARE) { 841 | if (fp < square_duty) 842 | sub_sample = 0.5; 843 | else 844 | sub_sample = -0.5; 845 | } else if (ps.wave_type === SAWTOOTH) { 846 | sub_sample = 1.0 - fp * 2; 847 | } else if (ps.wave_type === SINE) { 848 | sub_sample = Math.sin(fp * 2 * Math.PI); 849 | } else if (ps.wave_type === NOISE) { 850 | sub_sample = noise_buffer[Math.floor(phase * 32 / period)]; 851 | } else if (ps.wave_type === TRIANGLE) { 852 | sub_sample = Math.abs(1 - fp * 2) - 1; 853 | } else if (ps.wave_type === BREAKER) { 854 | sub_sample = Math.abs(1 - fp * fp * 2) - 1; 855 | } else { 856 | throw new Exception('bad wave type! ' + ps.wave_type); 857 | } 858 | 859 | // Low-pass filter 860 | var pp = fltp; 861 | fltw *= fltw_d; 862 | if (fltw < 0.0) fltw = 0.0; 863 | if (fltw > 0.1) fltw = 0.1; 864 | if (ps.p_lpf_freq != 1.0) { 865 | fltdp += (sub_sample - fltp) * fltw; 866 | fltdp -= fltdp * fltdmp; 867 | } else { 868 | fltp = sub_sample; 869 | fltdp = 0.0; 870 | } 871 | fltp += fltdp; 872 | 873 | // High-pass filter 874 | fltphp += fltp - pp; 875 | fltphp -= fltphp * flthp; 876 | sub_sample = fltphp; 877 | 878 | // Phaser 879 | phaser_buffer[ipp & 1023] = sub_sample; 880 | sub_sample += phaser_buffer[(ipp - iphase + 1024) & 1023]; 881 | ipp = (ipp + 1) & 1023; 882 | 883 | // final accumulation and envelope application 884 | sample += sub_sample * env_vol; 885 | } 886 | 887 | // Accumulate samples appropriately for sample rate 888 | sample_sum += sample; 889 | if (++num_summed >= summands) { 890 | num_summed = 0; 891 | sample = sample_sum / summands; 892 | sample_sum = 0; 893 | } else { 894 | continue; 895 | } 896 | 897 | sample = sample / 8 * masterVolume; 898 | sample *= gain; 899 | 900 | buffer[buffer_i++] = sample; 901 | 902 | if (ps.sample_rate < SoundEffect.MIN_SAMPLE_RATE) { 903 | buffer[buffer_i++] = sample; 904 | buffer[buffer_i++] = sample; 905 | buffer[buffer_i++] = sample; 906 | } 907 | 908 | if (buffer_complete) { 909 | for (; buffer_i < buffer_length; buffer_i++) { 910 | if (ps.sample_rate < SoundEffect.MIN_SAMPLE_RATE) { 911 | buffer[buffer_i++] = 0; 912 | buffer[buffer_i++] = 0; 913 | buffer[buffer_i++] = 0; 914 | } 915 | buffer[buffer_i] = 0; 916 | } 917 | break; 918 | } 919 | } 920 | return sound; 921 | }; 922 | 923 | if (typeof exports != 'undefined') { 924 | // For node.js 925 | var RIFFWAVE = require('./riffwave').RIFFWAVE; 926 | exports.Params = Params; 927 | exports.generate = generate; 928 | } 929 | 930 | var sfxCache = {}; 931 | var cachedSeeds = []; 932 | var CACHE_MAX = 50; 933 | 934 | function cacheSeed(seed){ 935 | if (seed in sfxCache) { 936 | return sfxCache[seed]; 937 | } 938 | 939 | var params = generateFromSeed(seed); 940 | params.sound_vol = SOUND_VOL; 941 | params.sample_rate = SAMPLE_RATE; 942 | params.bit_depth = BIT_DEPTH; 943 | 944 | var sound = SoundEffect.generate(params); 945 | sfxCache[seed] = sound; 946 | cachedSeeds.push(seed); 947 | 948 | while (cachedSeeds.length>CACHE_MAX) { 949 | var toRemove=cachedSeeds[0]; 950 | cachedSeeds = cachedSeeds.slice(1); 951 | delete sfxCache[toRemove]; 952 | } 953 | 954 | return sound; 955 | } 956 | 957 | var lastSoundCoord=-1; 958 | function playSound(seed,force) { 959 | var curCoord=Math.round(bpx)+100000*Math.round(bpy)+seed*1000; 960 | if (force===true){ 961 | }else if (curCoord===lastSoundCoord){ 962 | return; 963 | } 964 | var sound = cacheSeed(seed); 965 | sound.play(); 966 | 967 | lastSoundCoord=curCoord; 968 | } 969 | -------------------------------------------------------------------------------- /spawnicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/increpare/plingpling/4798d4bd193ebd8dc42971d4f489b249baefa0ab/spawnicon.png -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | #maincanvas { 2 | cursor:crosshair; 3 | } 4 | body { 5 | -webkit-user-select: none; /* webkit (safari, chrome) browsers */ 6 | -moz-user-select: none; /* mozilla browsers */ 7 | -khtml-user-select: none; /* webkit (konqueror) browsers */ 8 | -ms-user-select: none; /* IE10+ */ 9 | } 10 | select { 11 | background-color:black; 12 | color:white; 13 | text-indent:5px; 14 | } 15 | canvas { 16 | position:relative; 17 | } 18 | body { 19 | background-color:black; 20 | color: gray; 21 | margin-top: 0; 22 | padding-top: 0; 23 | border-top: 0; 24 | } 25 | ul { 26 | list-style-type: none; 27 | overflow:hidden; overflow-y:scroll; 28 | } 29 | input { 30 | background-color: black; 31 | color:white; 32 | text-align: center; 33 | font-size: 100%; 34 | } 35 | input#linkInput { 36 | background-color: black; 37 | color:white; 38 | text-align: center; 39 | font-size: 100%; 40 | } 41 | input#winText { 42 | width:90%; 43 | } 44 | li { 45 | font-size: 150%; 46 | text-align: center; 47 | width:100%; 48 | } 49 | a { 50 | color:white; 51 | } 52 | a.layerItem{ 53 | color: white; 54 | background-color: black; 55 | text-decoration: none; 56 | width:100%; 57 | display:block; 58 | } 59 | a.layerItem.selectedItem{ 60 | color: black; 61 | background-color: white; 62 | } 63 | img.selected { 64 | border: 2px solid red; 65 | } 66 | li.selected { 67 | cursor:auto; 68 | color:white; 69 | } 70 | a{ 71 | white-space:nowrap; 72 | } 73 | div.selected { 74 | height:16px; 75 | width:30px; 76 | border: 2px solid red; 77 | } 78 | div.unselected { 79 | height:16px; 80 | width:30px; 81 | border: 2px solid black; 82 | } 83 | span.selected { 84 | height:16px; 85 | width:30px; 86 | border: 2px solid red; 87 | } 88 | span.unselected { 89 | height:16px; 90 | width:30px; 91 | border: 2px solid black; 92 | } 93 | img.radius { 94 | border: 1px solid black; 95 | } 96 | img.selected { 97 | border: 1px solid red; 98 | } 99 | select { 100 | width:100%; 101 | -webkit-appearance: none; 102 | -moz-appearance: none; 103 | } 104 | select::-ms-expand { 105 | display: none; 106 | } 107 | canvas#dropdownthumb { 108 | border:2px solid gray; 109 | background-color:black; 110 | } --------------------------------------------------------------------------------