├── .editorconfig ├── .gitignore ├── .lvimrc ├── .prettierrc.js ├── LICENSE ├── README.md ├── assets ├── all-fedora.txt ├── clh-logo-white.svg ├── clh-thumb.png ├── cmds │ ├── README.md │ ├── bash.js │ ├── from-path-fedora.txt │ ├── from-path-ubuntu.txt │ ├── generateBashCmds.js │ ├── html.js │ ├── js.js │ └── python.js ├── fonts │ ├── _overpass.scss │ ├── example.html │ ├── fonts.css │ ├── overpass-bold-italic.eot │ ├── overpass-bold-italic.ttf │ ├── overpass-bold-italic.woff │ ├── overpass-bold-italic.woff2 │ ├── overpass-bold.eot │ ├── overpass-bold.ttf │ ├── overpass-bold.woff │ ├── overpass-bold.woff2 │ ├── overpass-extrabold-italic.eot │ ├── overpass-extrabold-italic.ttf │ ├── overpass-extrabold-italic.woff │ ├── overpass-extrabold-italic.woff2 │ ├── overpass-extrabold.eot │ ├── overpass-extrabold.ttf │ ├── overpass-extrabold.woff │ ├── overpass-extrabold.woff2 │ ├── overpass-extralight-italic.eot │ ├── overpass-extralight-italic.ttf │ ├── overpass-extralight-italic.woff │ ├── overpass-extralight-italic.woff2 │ ├── overpass-extralight.eot │ ├── overpass-extralight.ttf │ ├── overpass-extralight.woff │ ├── overpass-extralight.woff2 │ ├── overpass-heavy-italic.eot │ ├── overpass-heavy-italic.ttf │ ├── overpass-heavy-italic.woff │ ├── overpass-heavy-italic.woff2 │ ├── overpass-heavy.eot │ ├── overpass-heavy.ttf │ ├── overpass-heavy.woff │ ├── overpass-heavy.woff2 │ ├── overpass-italic.eot │ ├── overpass-italic.ttf │ ├── overpass-italic.woff │ ├── overpass-italic.woff2 │ ├── overpass-light-italic.eot │ ├── overpass-light-italic.ttf │ ├── overpass-light-italic.woff │ ├── overpass-light-italic.woff2 │ ├── overpass-light.eot │ ├── overpass-light.ttf │ ├── overpass-light.woff │ ├── overpass-light.woff2 │ ├── overpass-mono-bold.eot │ ├── overpass-mono-bold.otf │ ├── overpass-mono-bold.ttf │ ├── overpass-mono-bold.woff │ ├── overpass-mono-bold.woff2 │ ├── overpass-mono-light.eot │ ├── overpass-mono-light.otf │ ├── overpass-mono-light.ttf │ ├── overpass-mono-light.woff │ ├── overpass-mono-light.woff2 │ ├── overpass-mono-regular.eot │ ├── overpass-mono-regular.otf │ ├── overpass-mono-regular.ttf │ ├── overpass-mono-regular.woff │ ├── overpass-mono-regular.woff2 │ ├── overpass-mono-semibold.eot │ ├── overpass-mono-semibold.otf │ ├── overpass-mono-semibold.ttf │ ├── overpass-mono-semibold.woff │ ├── overpass-mono-semibold.woff2 │ ├── overpass-regular.eot │ ├── overpass-regular.ttf │ ├── overpass-regular.woff │ ├── overpass-regular.woff2 │ ├── overpass-semibold-italic.eot │ ├── overpass-semibold-italic.ttf │ ├── overpass-semibold-italic.woff │ ├── overpass-semibold-italic.woff2 │ ├── overpass-semibold.eot │ ├── overpass-semibold.ttf │ ├── overpass-semibold.woff │ ├── overpass-semibold.woff2 │ ├── overpass-thin-italic.eot │ ├── overpass-thin-italic.ttf │ ├── overpass-thin-italic.woff │ ├── overpass-thin-italic.woff2 │ ├── overpass-thin.eot │ ├── overpass-thin.ttf │ ├── overpass-thin.woff │ ├── overpass-thin.woff2 │ └── overpass.css ├── images │ ├── monitor_bezel_outline.png │ └── monitor_fire_inner.png ├── models │ ├── CLH_Computer.mtl.gz │ ├── CLH_Computer.obj.gz │ ├── CLH_Shuttle.mtl.gz │ ├── CLH_Shuttle.obj.gz │ ├── CLH_ep2_computer_high_poly.mtl.gz │ ├── CLH_ep2_computer_high_poly.obj.gz │ ├── CLH_ep2_cyc_wall.mtl.gz │ └── CLH_ep2_cyc_wall.obj.gz ├── sfx │ ├── Waveshaper - 66 MHz.mp3 │ ├── boot.mp3 │ ├── cmd-bad.mp3 │ ├── cmd-gold.mp3 │ ├── cmd-good.mp3 │ ├── keypress.mp3 │ ├── leaderboard.mp3 │ ├── menu-music.mp3 │ ├── play.mp3 │ ├── timer-relaxed.mp3 │ └── timer-urgent.mp3 ├── styles.css └── textures │ ├── clh_test_pattern.jpg │ ├── equirectangular.png │ └── wall.png ├── index.html ├── package.json ├── src ├── Fire.js ├── MTLLoaderPhysical.js ├── app.js ├── cmds.js ├── config.js ├── console-canvas.js ├── keycodes.js ├── leaderboard.js ├── main.js ├── palette.js ├── sfx.js ├── sleep.js ├── states.js ├── three-utils.js ├── tween-camera.js └── unzip.js └── start.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig: http://EditorConfig.org 2 | 3 | # Top-most EditorConfig file 4 | root = true 5 | 6 | # Rules for JavaScript files: 7 | 8 | [*.{js,py,json,sh,html}] 9 | # 4 space indentation 10 | indent_style = space 11 | indent_size = 4 12 | # No trailing spaces 13 | trim_trailing_whitespace = true 14 | # Unix-style newlines 15 | end_of_line = lf 16 | # Newline ending every file 17 | insert_final_newline = true 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /tags 3 | .idea 4 | npm-debug.log 5 | dist 6 | package-lock.json 7 | .DS_Store 8 | *~ 9 | 10 | assets/models/*.mtl 11 | assets/models/*.obj 12 | -------------------------------------------------------------------------------- /.lvimrc: -------------------------------------------------------------------------------- 1 | let g:ale_fixers = {} 2 | let g:ale_fixers['javascript'] = ['prettier'] 3 | let g:ale_fixers['json'] = ['prettier'] 4 | let g:ale_fixers['css'] = ['prettier'] 5 | 6 | let g:ale_fix_on_save = 1 7 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/.prettierrc.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clh-bash 2 | 3 | ![screenshot from 2019-02-06 09-54-54](https://user-images.githubusercontent.com/3926730/52349830-53226480-29f5-11e9-82a9-783a04808e55.png) 4 | 5 | ## Development 6 | 7 | Install dependencies. 8 | 9 | npm install 10 | 11 | Extract the compressed MTL and OBJ files. 12 | 13 | npm run extract 14 | 15 | 16 | 17 | Extract the compressed MTL and OBJ files for Windows OS. 18 | 19 | npm run unzip 20 | 21 | Start the dev server. 22 | 23 | npm start 24 | 25 | If you need to make changes to MTL/OBJ files and want to preserve them, run this to compress them. Only gzipped MTL/OBJ files are saved in the repo. 26 | 27 | npm run compress 28 | 29 | ## Leaderboard selection 30 | 31 | Bash supports multiple options for leaderboard storage. The default is in-browser `localStorage`. A networked leaderboard is also supported, through sending leaderboard entries to a Parse server. 32 | 33 | ### Networked leaderboards with Parse 34 | 35 | If you want a networked leaderboard, you must have a Parse instance up and running. Then, open `src/config.js` and change `PARSE_URL` to point to the URL of your parse server. 36 | 37 | Finally, when you launch Bash, add `&storage=parse` to the end. 38 | 39 | ### Selecting a leaderboard namespace 40 | 41 | Both `localStorage` and Parse leaderboard support namespacing. In other words, you can give the leaderboard a name. This is especially useful if you need to maintain multiple leaderboards, for tournament rounds, timed events at conferences, etc. Switching between leaderboards is as easy as changing the namespace. 42 | 43 | Then, when you launch Bash, add `&name=NAMESPACE` to the end of Bash's URL. Note that you can change the word `NAMESPACE` to be anything you want. 44 | 45 | 46 | ### How to get Help 47 | 48 | 1. Post a question in the repo [issues](https://github.com/CommandLineHeroes/clh-bash/issues) 49 | 2. Ask a question in real-time in our [public Discord server](https://discord.gg/rpnmpVj) 50 | 3. Send a tweet to one of the twitter links below [social](#social) 51 | 52 | ## Community 53 | 54 | Join our [public Discord server](https://discord.gg/rpnmpVj)! 55 | 56 | ## Social 57 | 58 | - Jared Sprague [@caramelcode](https://twitter.com/caramelcode) 59 | - Michael Clayton [@mwcz](https://twitter.com/mwcz) 60 | - [Command Line Heroes](https://www.redhat.com/en/command-line-heroes) 61 | - [#CommandLinePod](https://twitter.com/hashtag/CommandLinePod?src=hash) 62 | -------------------------------------------------------------------------------- /assets/all-fedora.txt: -------------------------------------------------------------------------------- 1 | Fedora 29 - x86_64 37 kB/s | 10 MB 04:41 2 | -------------------------------------------------------------------------------- /assets/clh-logo-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 217 | 218 | -------------------------------------------------------------------------------- /assets/clh-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/clh-thumb.png -------------------------------------------------------------------------------- /assets/cmds/README.md: -------------------------------------------------------------------------------- 1 | # Building a shell command database 2 | 3 | We pull from several sources to populate the command database. 4 | 5 | _Note: all commands to be run from this directory, `assets/commands/`._ 6 | 7 | ## From \$PATH (Fedora) 8 | 9 | Get all executables from the \$PATH and all built-in bash functions. 10 | 11 | compgen -bc > from-path-fedora.txt 12 | 13 | Get all DNF packages (this does not get executable names). 14 | 15 | dnf list all 16 | 17 | ## JavaScript 18 | 19 | A few types of JS code are accepted. 20 | 21 | - all [keywords](https://tc39.github.io/ecma262/#sec-keywords) 22 | - some [literals](https://tc39.github.io/ecma262/#sec-ecmascript-language-lexical-grammar-literals) 23 | - literals to include: `null`, `true`, `false` 24 | - all [properties of the global object](https://tc39.github.io/ecma262/#sec-global-object) 25 | - all objects listed in [chapters 19-26](https://tc39.github.io/ecma262/#sec-fundamental-objects) 26 | - for example, the subchapters of chapter 19 are "Object Objects", "Function Objects", "Boolean Objects", "Symbol Objects", and "Error Objects", so `object`, `function`, `boolean`, `symbol`, and `error` should be used. 27 | - additional [built-in properties of the global object](https://tc39.github.io/ecma262/#sec-additional-properties-of-the-global-object) 28 | - `async` should be added. it won't appear in the above lists because it is not a proper keyword (it's only a keyword in certain contexts but can still be used as a variable name, etc). 29 | - `window`, `document`, `navigator` I can't find in any spec but should be included 30 | -------------------------------------------------------------------------------- /assets/cmds/generateBashCmds.js: -------------------------------------------------------------------------------- 1 | // This file will generate and write the bash.js file. 2 | // It this takes a set of text files that are generated from "compgen -bc > file.txt" as input 3 | // Sorts, removes duplicates, and unions the files together then writes bash.js 4 | // This makes it possible to combine different linux distros together e.g. Fedora, Ubuntu, RHEL 5 | // Example Usage: 6 | // node generateBashCmds.js from-path-fedora.txt from-path-ubuntu.txt 7 | 8 | "use strict"; 9 | 10 | let fs = require("fs"); 11 | 12 | let allCmds = []; 13 | 14 | // First load the files from command line arguments 15 | for (let i = 2; i < process.argv.length; i++) { 16 | let fileName = process.argv[i]; 17 | console.log("Loading file: " + fileName); 18 | 19 | let fileContents = fs.readFileSync(fileName, "ascii"); 20 | let linesArray = fileContents.split("\n"); 21 | 22 | allCmds = allCmds.concat(linesArray); 23 | 24 | console.log("command count: ", linesArray.length); 25 | } 26 | 27 | console.log("all commands count: ", allCmds.length); 28 | 29 | // Sort 30 | allCmds.sort(); 31 | 32 | // Remove duplicates 33 | let uniqueCmds = allCmds.filter(function(elem, index, self) { 34 | return index === self.indexOf(elem); 35 | }); 36 | 37 | console.log("unique commands count: ", uniqueCmds.length); 38 | 39 | // Write to file 40 | console.log("Writing to: bash.js"); 41 | let outFileContent = "/** Generated from generateBashCmds.js **/\n"; 42 | outFileContent += "export default [\n"; 43 | uniqueCmds.forEach(value => { 44 | outFileContent += '"' + value + '",\n'; 45 | }); 46 | outFileContent += "];\n"; 47 | 48 | try { 49 | fs.writeFileSync("./bash.js", outFileContent); 50 | } catch (err) { 51 | console.error(err); 52 | } 53 | -------------------------------------------------------------------------------- /assets/cmds/html.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "HTML5", 3 | commonCmds: [ 4 | "html", 5 | "head", 6 | "title", 7 | "link", 8 | "meta", 9 | "style", 10 | "body", 11 | "a", 12 | "nav", 13 | "h1", 14 | "h2", 15 | "h3", 16 | "p", 17 | "hr", 18 | "pre", 19 | "blockquote", 20 | "ol", 21 | "ul", 22 | "li", 23 | "div", 24 | "br", 25 | "table", 26 | "tr", 27 | "td" 28 | ], 29 | cmds: [ 30 | "html", 31 | "head", 32 | "title", 33 | "base", 34 | "link", 35 | "meta", 36 | "style", 37 | "body", 38 | "article", 39 | "section", 40 | "nav", 41 | "aside", 42 | "h1", 43 | "h2", 44 | "h3", 45 | "h4", 46 | "h5", 47 | "h6", 48 | "header", 49 | "footer", 50 | "p", 51 | "address", 52 | "hr", 53 | "pre", 54 | "blockquote", 55 | "ol", 56 | "ul", 57 | "li", 58 | "dl", 59 | "dt", 60 | "dd", 61 | "figure", 62 | "figcaption", 63 | "main", 64 | "div", 65 | "a", 66 | "em", 67 | "strong", 68 | "small", 69 | "s", 70 | "cite", 71 | "q", 72 | "dfn", 73 | "abbr", 74 | "ruby", 75 | "rb", 76 | "rt", 77 | "rtc", 78 | "rp", 79 | "data", 80 | "time", 81 | "code", 82 | "var", 83 | "samp", 84 | "kbd", 85 | "sub", 86 | "sup", 87 | "i", 88 | "b", 89 | "u", 90 | "mark", 91 | "bdi", 92 | "bdo", 93 | "span", 94 | "br", 95 | "wbr", 96 | "ins", 97 | "del", 98 | "picture", 99 | "source", 100 | "img", 101 | "iframe", 102 | "embed", 103 | "object", 104 | "param", 105 | "video", 106 | "audio", 107 | "track", 108 | "map", 109 | "area", 110 | "table", 111 | "caption", 112 | "colgroup", 113 | "col", 114 | "tbody", 115 | "thead", 116 | "tfoot", 117 | "tr", 118 | "td", 119 | "th", 120 | "form", 121 | "label", 122 | "input", 123 | "button", 124 | "select", 125 | "datalist", 126 | "optgroup", 127 | "option", 128 | "textarea", 129 | "output", 130 | "progress", 131 | "meter", 132 | "fieldset", 133 | "legend", 134 | "details", 135 | "summary", 136 | "dialog", 137 | "script", 138 | "noscript", 139 | "template", 140 | "canvas", 141 | "slot", 142 | "hr", 143 | "fieldset", 144 | "legend", 145 | "button", 146 | "details", 147 | "summary", 148 | "marquee", 149 | "meter", 150 | "progress", 151 | "select", 152 | "textarea", 153 | "marquee" 154 | ] 155 | }; 156 | -------------------------------------------------------------------------------- /assets/cmds/js.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a list of JavaScript keywords, "standard library" objects, etc. 3 | * 4 | * See the README in this directory for more on how this list is assembled. 5 | * 6 | * There are duplicates, and that's okay. But if you are removing items, be sure to look for multiple entries! 7 | */ 8 | export default { 9 | name: "JavaScript", 10 | commonCmds: [ 11 | "if", 12 | "else", 13 | "for", 14 | "function", 15 | "let", 16 | "var", 17 | "const", 18 | "JSON", 19 | "Date", 20 | "window" 21 | ], 22 | cmds: [ 23 | // keywords 24 | "await", 25 | "break", 26 | "case", 27 | "catch", 28 | "class", 29 | "const", 30 | "continue", 31 | "debugger", 32 | "default", 33 | "delete", 34 | "do", 35 | "else", 36 | "export", 37 | "extends", 38 | "finally", 39 | "for", 40 | "function", 41 | "if", 42 | "import", 43 | "in", 44 | "instanceof", 45 | "new", 46 | "return", 47 | "super", 48 | "switch", 49 | "this", 50 | "throw", 51 | "try", 52 | "typeof", 53 | "var", 54 | "void", 55 | "while", 56 | "with", 57 | "yield", 58 | // some literals 59 | "null", 60 | "true", 61 | "false", 62 | // global object properties 63 | "Infinity", 64 | "NaN", 65 | "undefined", 66 | "eval", 67 | "isFinite", 68 | "isNaN", 69 | "parseFloat", 70 | "parseInt", 71 | "decodeURI", 72 | "decodeURIComponent", 73 | "encodeURI", 74 | "encodeURIComponent", 75 | "Array", 76 | "ArrayBuffer", 77 | "Boolean", 78 | "DataView", 79 | "Date", 80 | "Error", 81 | "EvalError", 82 | "Float32Array", 83 | "Float64Array", 84 | "Function", 85 | "Int8Array", 86 | "Int16Array", 87 | "Int32Array", 88 | "Map", 89 | "Number", 90 | "Object", 91 | "Promise", 92 | "Proxy", 93 | "RangeError", 94 | "ReferenceError", 95 | "RegExp", 96 | "Set", 97 | "SharedArrayBuffer", 98 | "String", 99 | "Symbol", 100 | "SyntaxError", 101 | "TypeError", 102 | "Uint8Array", 103 | "Uint8ClampedArray", 104 | "Uint16Array", 105 | "Uint32Array", 106 | "URIError", 107 | "WeakMap", 108 | "WeakSet", 109 | 110 | // fundamental objects (ch 19) 111 | "Object", 112 | "Function", 113 | "Boolean", 114 | "Symbol", 115 | "Error", 116 | 117 | // numbers and dates (ch 20) 118 | "Number", 119 | "Math", 120 | "Date", 121 | 122 | // text processing (ch 21) 123 | "String", 124 | "RegExp", 125 | 126 | // indexed collections (ch 22) 127 | "Array", 128 | 129 | // keyed collections (ch 23) 130 | "Map", 131 | "Set", 132 | "WeakMap", 133 | "WeakSet", 134 | 135 | // structured data (ch 24) 136 | "ArrayBuffer", 137 | "SharedArrayBuffer", 138 | "DataView", 139 | "Atomics", 140 | "JSON", 141 | 142 | // control abstraction objects (ch 25) 143 | "Generator", 144 | "AsyncGenerator", 145 | "Promise", 146 | 147 | // reflection (ch 26) 148 | "Reflect", 149 | "Proxy", 150 | 151 | // some curiously hard to find ones in the spec 152 | "async", 153 | "let", 154 | "static", 155 | "else", 156 | "document", 157 | "window", 158 | "navigator", 159 | "then", 160 | "set", 161 | "get", 162 | "of", 163 | 164 | // Object.keys(window) in chrome 165 | "postMessage", 166 | "blur", 167 | "focus", 168 | "close", 169 | "parent", 170 | "opener", 171 | "top", 172 | "length", 173 | "frames", 174 | "closed", 175 | "location", 176 | "self", 177 | "window", 178 | "document", 179 | "name", 180 | "customElements", 181 | "history", 182 | "locationbar", 183 | "menubar", 184 | "personalbar", 185 | "scrollbars", 186 | "statusbar", 187 | "toolbar", 188 | "status", 189 | "frameElement", 190 | "navigator", 191 | "origin", 192 | "external", 193 | "screen", 194 | "innerWidth", 195 | "innerHeight", 196 | "scrollX", 197 | "pageXOffset", 198 | "scrollY", 199 | "pageYOffset", 200 | "visualViewport", 201 | "screenX", 202 | "screenY", 203 | "outerWidth", 204 | "outerHeight", 205 | "devicePixelRatio", 206 | "clientInformation", 207 | "screenLeft", 208 | "screenTop", 209 | "defaultStatus", 210 | "defaultstatus", 211 | "styleMedia", 212 | "onanimationend", 213 | "onanimationiteration", 214 | "onanimationstart", 215 | "onsearch", 216 | "ontransitionend", 217 | "onwebkitanimationend", 218 | "onwebkitanimationiteration", 219 | "onwebkitanimationstart", 220 | "onwebkittransitionend", 221 | "isSecureContext", 222 | "onabort", 223 | "onblur", 224 | "oncancel", 225 | "oncanplay", 226 | "oncanplaythrough", 227 | "onchange", 228 | "onclick", 229 | "onclose", 230 | "oncontextmenu", 231 | "oncuechange", 232 | "ondblclick", 233 | "ondrag", 234 | "ondragend", 235 | "ondragenter", 236 | "ondragleave", 237 | "ondragover", 238 | "ondragstart", 239 | "ondrop", 240 | "ondurationchange", 241 | "onemptied", 242 | "onended", 243 | "onerror", 244 | "onfocus", 245 | "oninput", 246 | "oninvalid", 247 | "onkeydown", 248 | "onkeypress", 249 | "onkeyup", 250 | "onload", 251 | "onloadeddata", 252 | "onloadedmetadata", 253 | "onloadstart", 254 | "onmousedown", 255 | "onmouseenter", 256 | "onmouseleave", 257 | "onmousemove", 258 | "onmouseout", 259 | "onmouseover", 260 | "onmouseup", 261 | "onmousewheel", 262 | "onpause", 263 | "onplay", 264 | "onplaying", 265 | "onprogress", 266 | "onratechange", 267 | "onreset", 268 | "onresize", 269 | "onscroll", 270 | "onseeked", 271 | "onseeking", 272 | "onselect", 273 | "onstalled", 274 | "onsubmit", 275 | "onsuspend", 276 | "ontimeupdate", 277 | "ontoggle", 278 | "onvolumechange", 279 | "onwaiting", 280 | "onwheel", 281 | "onauxclick", 282 | "ongotpointercapture", 283 | "onlostpointercapture", 284 | "onpointerdown", 285 | "onpointermove", 286 | "onpointerup", 287 | "onpointercancel", 288 | "onpointerover", 289 | "onpointerout", 290 | "onpointerenter", 291 | "onpointerleave", 292 | "onselectstart", 293 | "onselectionchange", 294 | "onafterprint", 295 | "onbeforeprint", 296 | "onbeforeunload", 297 | "onhashchange", 298 | "onlanguagechange", 299 | "onmessage", 300 | "onmessageerror", 301 | "onoffline", 302 | "ononline", 303 | "onpagehide", 304 | "onpageshow", 305 | "onpopstate", 306 | "onrejectionhandled", 307 | "onstorage", 308 | "onunhandledrejection", 309 | "onunload", 310 | "performance", 311 | "stop", 312 | "open", 313 | "alert", 314 | "confirm", 315 | "prompt", 316 | "print", 317 | "queueMicrotask", 318 | "requestAnimationFrame", 319 | "cancelAnimationFrame", 320 | "captureEvents", 321 | "releaseEvents", 322 | "requestIdleCallback", 323 | "cancelIdleCallback", 324 | "getComputedStyle", 325 | "matchMedia", 326 | "moveTo", 327 | "moveBy", 328 | "resizeTo", 329 | "resizeBy", 330 | "getSelection", 331 | "find", 332 | "webkitRequestAnimationFrame", 333 | "webkitCancelAnimationFrame", 334 | "fetch", 335 | "btoa", 336 | "atob", 337 | "setTimeout", 338 | "clearTimeout", 339 | "setInterval", 340 | "clearInterval", 341 | "createImageBitmap", 342 | "scroll", 343 | "scrollTo", 344 | "scrollBy", 345 | "onappinstalled", 346 | "onbeforeinstallprompt", 347 | "crypto", 348 | "ondevicemotion", 349 | "ondeviceorientation", 350 | "ondeviceorientationabsolute", 351 | "indexedDB", 352 | "webkitStorageInfo", 353 | "sessionStorage", 354 | "localStorage", 355 | "chrome", 356 | "speechSynthesis", 357 | "webkitRequestFileSystem", 358 | "webkitResolveLocalFileSystemURL", 359 | "openDatabase" 360 | ] 361 | }; 362 | -------------------------------------------------------------------------------- /assets/cmds/python.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a list of Python3+ keywords and built in functions 3 | * 4 | * See the README in this directory for more on how this list is assembled. 5 | * 6 | * There are duplicates, and that's okay. But if you are removing items, be sure to look for multiple entries! 7 | */ 8 | export default { 9 | name: "Python", 10 | commonCmds: [ 11 | "def", 12 | "len", 13 | "lambda", 14 | "tuple", 15 | "elif", 16 | "if", 17 | "or", 18 | "return", 19 | "finally", 20 | "class", 21 | "and", 22 | "del", 23 | "not", 24 | "while", 25 | "break", 26 | "with", 27 | "True", 28 | "False", 29 | "max", 30 | "round", 31 | "globals" 32 | ], 33 | cmds: [ 34 | // keywords 35 | "False", 36 | "class", 37 | "finally", 38 | "is", 39 | "return", 40 | "None", 41 | "continue", 42 | "for", 43 | "lambda", 44 | "try", 45 | "True", 46 | "def", 47 | "from", 48 | "nonlocal", 49 | "while", 50 | "and", 51 | "del", 52 | "global", 53 | "not", 54 | "with", 55 | "as", 56 | "elif", 57 | "if", 58 | "or", 59 | "yield", 60 | "assert", 61 | "else", 62 | "import", 63 | "pass", 64 | "break", 65 | "except", 66 | "in", 67 | "raise", 68 | // funtions 69 | "abs()", 70 | "delattr()", 71 | "hash()", 72 | "memoryview()", 73 | "set()", 74 | "all()", 75 | "dict()", 76 | "help()", 77 | "min()", 78 | "setattr()", 79 | "any()", 80 | "dir()", 81 | "hex()", 82 | "next()", 83 | "slice()", 84 | "ascii()", 85 | "divmod()", 86 | "id()", 87 | "object()", 88 | "sorted()", 89 | "bin()", 90 | "enumerate()", 91 | "input()", 92 | "oct()", 93 | "staticmethod()", 94 | "bool()", 95 | "eval()", 96 | "int()", 97 | "open()", 98 | "str()", 99 | "breakpoint()", 100 | "exec()", 101 | "isinstance()", 102 | "ord()", 103 | "sum()", 104 | "bytearray()", 105 | "filter()", 106 | "issubclass()", 107 | "pow()", 108 | "super()", 109 | "bytes()", 110 | "float()", 111 | "iter()", 112 | "print()", 113 | "tuple()", 114 | "callable()", 115 | "format()", 116 | "len()", 117 | "property()", 118 | "type()", 119 | "chr()", 120 | "frozenset()", 121 | "list()", 122 | "range()", 123 | "vars()", 124 | "classmethod()", 125 | "getattr()", 126 | "locals()", 127 | "repr()", 128 | "zip()", 129 | "compile()", 130 | "globals()", 131 | "map()", 132 | "reversed()", 133 | "__import__()", 134 | "complex()", 135 | "hasattr()", 136 | "max()", 137 | "round()", 138 | // functions without parens 139 | "abs", 140 | "delattr", 141 | "hash", 142 | "memoryview", 143 | "set", 144 | "all", 145 | "dict", 146 | "help", 147 | "min", 148 | "setattr", 149 | "any", 150 | "dir", 151 | "hex", 152 | "next", 153 | "slice", 154 | "ascii", 155 | "divmod", 156 | "id", 157 | "object", 158 | "sorted", 159 | "bin", 160 | "enumerate", 161 | "input", 162 | "oct", 163 | "staticmethod", 164 | "bool", 165 | "eval", 166 | "int", 167 | "open", 168 | "str", 169 | "breakpoint", 170 | "exec", 171 | "isinstance", 172 | "ord", 173 | "sum", 174 | "bytearray", 175 | "filter", 176 | "issubclass", 177 | "pow", 178 | "super", 179 | "bytes", 180 | "float", 181 | "iter", 182 | "print", 183 | "tuple", 184 | "callable", 185 | "format", 186 | "len", 187 | "property", 188 | "type", 189 | "chr", 190 | "frozenset", 191 | "list", 192 | "range", 193 | "vars", 194 | "classmethod", 195 | "getattr", 196 | "locals", 197 | "repr", 198 | "zip", 199 | "compile", 200 | "globals", 201 | "map", 202 | "reversed", 203 | "__import__", 204 | "complex", 205 | "hasattr", 206 | "max", 207 | "round" 208 | ] 209 | }; 210 | -------------------------------------------------------------------------------- /assets/fonts/_overpass.scss: -------------------------------------------------------------------------------- 1 | /* Overpass helper mixins */ 2 | 3 | //-- This is a map of the current font weights, keyed on their file names 4 | $supported-weights: ( 5 | thin: 200, 6 | extralight: 300, 7 | light: 400, 8 | regular: 500, 9 | semibold: 600, 10 | bold: 700, 11 | extrabold: 800, 12 | heavy: 900 13 | ); 14 | 15 | //-- This is a list of the currently supported font styles 16 | $supported-styles: (normal italic); 17 | 18 | //-- This simple mixin adds the overpass basic styles to a typography element 19 | @mixin _overpass-styles { 20 | font-family: 'overpass'; 21 | -webkit-font-smoothing: antialiased; 22 | -moz-osx-font-smoothing: grayscale; 23 | } 24 | 25 | //-- This mixins will dynamically print the font-face declarations based on your levels of support 26 | // * Weight and style lists can be limited to those your wish to incorporate in your styles, 27 | // if left with defaults, it will add support for all overpass variations 28 | // * The weight map can be customized if you wish to set your own values 29 | // Example: To use `font-weight: bolder` instead of `font-weight: 800`, we create a custom map 30 | // that maps the key extrabold to the value bolder -> `extrabold: bolder` 31 | // * The path to dist represents the location of the overpass font files relative to your architecture 32 | @mixin print-overpass-font-face( 33 | $weights: map-keys($supported-weights), 34 | $styles: $supported-styles, 35 | $weight-map: $supported-weights, 36 | $path_to_dist: './' 37 | ) { 38 | @each $weight in $weights { 39 | @each $style in $styles { 40 | @font-face { 41 | font-family: 'overpass'; 42 | src: url("#{$path_to_dist}overpass-#{$weight}#{if($style != normal, #{-$style}, "")}.eot?"); 43 | src: url("#{$path_to_dist}overpass-#{$weight}#{if($style != normal, #{-$style}, "")}.eot?#iefix") format("embedded-opentype"), 44 | url("#{$path_to_dist}overpass-#{$weight}#{if($style != normal, #{-$style}, "")}.woff?") format("woff"), 45 | url("#{$path_to_dist}overpass-#{$weight}#{if($style != normal, #{-$style}, "")}.ttf?") format("truetype"); 46 | font-weight: map-get($weight-map, $weight); 47 | font-style: $style; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /assets/fonts/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |

Overpass Overpass

10 |

Overpass Overpass

11 |

Overpass Overpass

12 |

Overpass Overpass

13 |

Overpass Overpass

14 |

Overpass Overpass

15 |

Overpass Overpass

16 |

Overpass Overpass

17 | 18 | 19 | -------------------------------------------------------------------------------- /assets/fonts/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "overpass-mono"; 3 | src: url("overpass-mono-light.eot"); 4 | src: url("overpass-mono-light.eot?#iefix") format("embedded-opentype"), 5 | url("overpass-mono-light.woff2") format("woff2"), 6 | url("overpass-mono-light.woff") format("woff"), 7 | url("overpass-mono-light.ttf") format("truetype"); 8 | font-weight: 300; 9 | font-style: normal; 10 | } 11 | 12 | @font-face { 13 | font-family: "overpass-mono"; 14 | src: url("overpass-mono-regular.eot"); 15 | src: url("overpass-mono-regular.eot?#iefix") format("embedded-opentype"), 16 | url("overpass-mono-regular.woff2") format("woff2"), 17 | url("overpass-mono-regular.woff") format("woff"), 18 | url("overpass-mono-regular.ttf") format("truetype"); 19 | font-weight: 400; 20 | font-style: normal; 21 | } 22 | 23 | @font-face { 24 | font-family: "overpass-mono"; 25 | src: url("overpass-mono-semibold.eot"); 26 | src: url("overpass-mono-semibold.eot?#iefix") format("embedded-opentype"), 27 | url("overpass-mono-semibold.woff2") format("woff2"), 28 | url("overpass-mono-semibold.woff") format("woff"), 29 | url("overpass-mono-semibold.ttf") format("truetype"); 30 | font-weight: 500; 31 | font-style: normal; 32 | } 33 | 34 | @font-face { 35 | font-family: "overpass-mono"; 36 | src: url("overpass-mono-bold.eot"); 37 | src: url("overpass-mono-bold.eot?#iefix") format("embedded-opentype"), 38 | url("overpass-mono-bold.woff2") format("woff2"), 39 | url("overpass-mono-bold.woff") format("woff"), 40 | url("overpass-mono-bold.ttf") format("truetype"); 41 | font-weight: 600; 42 | font-style: normal; 43 | } 44 | 45 | @font-face { 46 | font-family: "overpass"; 47 | src: url("overpass-thin.eot"); /* IE9 Compat Modes */ 48 | src: url("overpass-thin.eot?#iefix") format("embedded-opentype"), 49 | /* IE6-IE8 */ url("overpass-thin.woff2") format("woff2"), 50 | /* Super Modern Browsers */ url("overpass-thin.woff") format("woff"), 51 | /* Pretty Modern Browsers */ url("overpass-thin.ttf") format("truetype"); /* Safari, Android, iOS */ 52 | font-weight: 200; 53 | font-style: normal; 54 | } 55 | 56 | @font-face { 57 | font-family: "overpass"; 58 | src: url("overpass-thin-italic.eot"); 59 | src: url("overpass-thin-italic.eot?#iefix") format("embedded-opentype"), 60 | url("overpass-thin-italic.woff2") format("woff2"), 61 | url("overpass-thin-italic.woff") format("woff"), 62 | url("overpass-thin-italic.ttf") format("truetype"); 63 | font-weight: 200; 64 | font-style: italic; 65 | } 66 | 67 | @font-face { 68 | font-family: "overpass"; 69 | src: url("overpass-extralight.eot"); 70 | src: url("overpass-extralight.eot?#iefix") format("embedded-opentype"), 71 | url("overpass-extralight.woff2") format("woff2"), 72 | url("overpass-extralight.woff") format("woff"), 73 | url("overpass-extralight.ttf") format("truetype"); 74 | font-weight: 300; 75 | font-style: normal; 76 | } 77 | 78 | @font-face { 79 | font-family: "overpass"; 80 | src: url("overpass-extralight-italic.eot"); 81 | src: url("overpass-extralight-italic.eot?#iefix") format("embedded-opentype"), 82 | url("overpass-extralight-italic.woff2") format("woff2"), 83 | url("overpass-extralight-italic.woff") format("woff"), 84 | url("overpass-extralight-italic.ttf") format("truetype"); 85 | font-weight: 300; 86 | font-style: italic; 87 | } 88 | 89 | @font-face { 90 | font-family: "overpass"; 91 | src: url("overpass-light.eot"); 92 | src: url("overpass-light.eot?#iefix") format("embedded-opentype"), 93 | url("overpass-light.woff2") format("woff2"), 94 | url("overpass-light.woff") format("woff"), 95 | url("overpass-light.ttf") format("truetype"); 96 | font-weight: 400; 97 | font-style: normal; 98 | } 99 | 100 | @font-face { 101 | font-family: "overpass"; 102 | src: url("overpass-light-italic.eot"); 103 | src: url("overpass-light-italic.eot?#iefix") format("embedded-opentype"), 104 | url("overpass-light-italic.woff2") format("woff2"), 105 | url("overpass-light-italic.woff") format("woff"), 106 | url("overpass-light-italic.ttf") format("truetype"); 107 | font-weight: 400; 108 | font-style: italic; 109 | } 110 | 111 | @font-face { 112 | font-family: "overpass"; 113 | src: url("overpass-regular.eot"); 114 | src: url("overpass-regular.eot?#iefix") format("embedded-opentype"), 115 | url("overpass-regular.woff2") format("woff2"), 116 | url("overpass-regular.woff") format("woff"), 117 | url("overpass-regular.ttf") format("truetype"); 118 | font-weight: 500; 119 | font-style: normal; 120 | } 121 | 122 | @font-face { 123 | font-family: "overpass"; 124 | src: url("overpass-italic.eot"); 125 | src: url("overpass-italic.eot?#iefix") format("embedded-opentype"), 126 | url("overpass-italic.woff2") format("woff2"), 127 | url("overpass-italic.woff") format("woff"), 128 | url("overpass-italic.ttf") format("truetype"); 129 | font-weight: 500; 130 | font-style: italic; 131 | } 132 | 133 | @font-face { 134 | font-family: "overpass"; 135 | src: url("overpass-semibold.eot"); 136 | src: url("overpass-semibold.eot?#iefix") format("embedded-opentype"), 137 | url("overpass-semibold.woff2") format("woff2"), 138 | url("overpass-semibold.woff") format("woff"), 139 | url("overpass-semibold.ttf") format("truetype"); 140 | font-weight: 600; 141 | font-style: normal; 142 | } 143 | 144 | @font-face { 145 | font-family: "overpass"; 146 | src: url("overpass-semibold-italic.eot"); 147 | src: url("overpass-semibold-italic.eot?#iefix") format("embedded-opentype"), 148 | url("overpass-semibold-italic.woff2") format("woff2"), 149 | url("overpass-semibold-italic.woff") format("woff"), 150 | url("overpass-semibold-italic.ttf") format("truetype"); 151 | font-weight: 600; 152 | font-style: italic; 153 | } 154 | 155 | @font-face { 156 | font-family: "overpass"; 157 | src: url("overpass-bold.eot"); 158 | src: url("overpass-bold.eot?#iefix") format("embedded-opentype"), 159 | url("overpass-bold.woff2") format("woff2"), 160 | url("overpass-bold.woff") format("woff"), 161 | url("overpass-bold.ttf") format("truetype"); 162 | font-weight: 700; 163 | font-style: normal; 164 | } 165 | 166 | @font-face { 167 | font-family: "overpass"; 168 | src: url("overpass-bold-italic.eot"); 169 | src: url("overpass-bold-italic.eot?#iefix") format("embedded-opentype"), 170 | url("overpass-bold-italic.woff2") format("woff2"), 171 | url("overpass-bold-italic.woff") format("woff"), 172 | url("overpass-bold-italic.ttf") format("truetype"); 173 | font-weight: 700; 174 | font-style: italic; 175 | } 176 | 177 | @font-face { 178 | font-family: "overpass"; 179 | src: url("overpass-extrabold.eot"); 180 | src: url("overpass-extrabold.eot?#iefix") format("embedded-opentype"), 181 | url("overpass-extrabold.woff2") format("woff2"), 182 | url("overpass-extrabold.woff") format("woff"), 183 | url("overpass-extrabold.ttf") format("truetype"); 184 | font-weight: 800; 185 | font-style: normal; 186 | } 187 | 188 | @font-face { 189 | font-family: "overpass"; 190 | src: url("overpass-extrabold-italic.eot"); 191 | src: url("overpass-extrabold-italic.eot?#iefix") format("embedded-opentype"), 192 | url("overpass-extrabold-italic.woff2") format("woff2"), 193 | url("overpass-extrabold-italic.woff") format("woff"), 194 | url("overpass-extrabold-italic.ttf") format("truetype"); 195 | font-weight: 800; 196 | font-style: italic; 197 | } 198 | 199 | @font-face { 200 | font-family: "overpass"; 201 | src: url("overpass-heavy.eot"); 202 | src: url("overpass-heavy.eot?#iefix") format("embedded-opentype"), 203 | url("overpass-heavy.woff2") format("woff2"), 204 | url("overpass-heavy.woff") format("woff"), 205 | url("overpass-heavy.ttf") format("truetype"); 206 | font-weight: 900; 207 | font-style: normal; 208 | } 209 | 210 | @font-face { 211 | font-family: "overpass"; 212 | src: url("overpass-heavy-italic.eot"); 213 | src: url("overpass-heavy-italic.eot?#iefix") format("embedded-opentype"), 214 | url("overpass-heavy-italic.woff2") format("woff2"), 215 | url("overpass-heavy-italic.woff") format("woff"), 216 | url("overpass-heavy-italic.ttf") format("truetype"); 217 | font-weight: 900; 218 | font-style: italic; 219 | } 220 | -------------------------------------------------------------------------------- /assets/fonts/overpass-bold-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-bold-italic.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-bold-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-bold-italic.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-bold-italic.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-bold-italic.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-bold.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-bold.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-bold.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-bold.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass-extrabold-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extrabold-italic.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-extrabold-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extrabold-italic.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-extrabold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extrabold-italic.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-extrabold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extrabold-italic.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass-extrabold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extrabold.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-extrabold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extrabold.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-extrabold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extrabold.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-extrabold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extrabold.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass-extralight-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extralight-italic.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-extralight-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extralight-italic.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-extralight-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extralight-italic.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-extralight-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extralight-italic.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass-extralight.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extralight.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-extralight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extralight.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-extralight.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extralight.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-extralight.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-extralight.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass-heavy-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-heavy-italic.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-heavy-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-heavy-italic.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-heavy-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-heavy-italic.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-heavy-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-heavy-italic.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass-heavy.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-heavy.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-heavy.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-heavy.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-heavy.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-heavy.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-heavy.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-heavy.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-italic.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-italic.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-italic.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-italic.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass-light-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-light-italic.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-light-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-light-italic.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-light-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-light-italic.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-light-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-light-italic.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-light.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-light.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-light.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-light.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-bold.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-bold.otf -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-bold.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-bold.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-bold.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-light.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-light.otf -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-light.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-light.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-light.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-regular.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-regular.otf -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-regular.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-regular.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-regular.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-semibold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-semibold.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-semibold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-semibold.otf -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-semibold.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-semibold.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-mono-semibold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-mono-semibold.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-regular.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-regular.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-regular.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-regular.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass-semibold-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-semibold-italic.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-semibold-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-semibold-italic.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-semibold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-semibold-italic.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-semibold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-semibold-italic.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass-semibold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-semibold.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-semibold.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-semibold.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-semibold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-semibold.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass-thin-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-thin-italic.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-thin-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-thin-italic.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-thin-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-thin-italic.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-thin-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-thin-italic.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass-thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-thin.eot -------------------------------------------------------------------------------- /assets/fonts/overpass-thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-thin.ttf -------------------------------------------------------------------------------- /assets/fonts/overpass-thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-thin.woff -------------------------------------------------------------------------------- /assets/fonts/overpass-thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/fonts/overpass-thin.woff2 -------------------------------------------------------------------------------- /assets/fonts/overpass.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'overpass'; 3 | src: url('overpass-thin.eot'); /* IE9 Compat Modes */ 4 | src: url('overpass-thin.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('overpass-thin.woff2') format('woff2'), /* Super Modern Browsers */ 6 | url('overpass-thin.woff') format('woff'), /* Pretty Modern Browsers */ 7 | url('overpass-thin.ttf') format('truetype'); /* Safari, Android, iOS */ 8 | font-weight: 200; 9 | font-style: normal; 10 | } 11 | 12 | @font-face { 13 | font-family: 'overpass'; 14 | src: url('overpass-thin-italic.eot'); 15 | src: url('overpass-thin-italic.eot?#iefix') format('embedded-opentype'), 16 | url('overpass-thin-italic.woff2') format('woff2'), 17 | url('overpass-thin-italic.woff') format('woff'), 18 | url('overpass-thin-italic.ttf') format('truetype'); 19 | font-weight: 200; 20 | font-style: italic; 21 | } 22 | 23 | 24 | @font-face { 25 | font-family: 'overpass'; 26 | src: url('overpass-extralight.eot'); 27 | src: url('overpass-extralight.eot?#iefix') format('embedded-opentype'), 28 | url('overpass-extralight.woff2') format('woff2'), 29 | url('overpass-extralight.woff') format('woff'), 30 | url('overpass-extralight.ttf') format('truetype'); 31 | font-weight: 300; 32 | font-style: normal; 33 | } 34 | 35 | @font-face { 36 | font-family: 'overpass'; 37 | src: url('overpass-extralight-italic.eot'); 38 | src: url('overpass-extralight-italic.eot?#iefix') format('embedded-opentype'), 39 | url('overpass-extralight-italic.woff2') format('woff2'), 40 | url('overpass-extralight-italic.woff') format('woff'), 41 | url('overpass-extralight-italic.ttf') format('truetype'); 42 | font-weight: 300; 43 | font-style: italic; 44 | } 45 | 46 | 47 | 48 | @font-face { 49 | font-family: 'overpass'; 50 | src: url('overpass-light.eot'); 51 | src: url('overpass-light.eot?#iefix') format('embedded-opentype'), 52 | url('overpass-light.woff2') format('woff2'), 53 | url('overpass-light.woff') format('woff'), 54 | url('overpass-light.ttf') format('truetype'); 55 | font-weight: 400; 56 | font-style: normal; 57 | } 58 | 59 | @font-face { 60 | font-family: 'overpass'; 61 | src: url('overpass-light-italic.eot'); 62 | src: url('overpass-light-italic.eot?#iefix') format('embedded-opentype'), 63 | url('overpass-light-italic.woff2') format('woff2'), 64 | url('overpass-light-italic.woff') format('woff'), 65 | url('overpass-light-italic.ttf') format('truetype'); 66 | font-weight: 400; 67 | font-style: italic; 68 | } 69 | 70 | 71 | 72 | @font-face { 73 | font-family: 'overpass'; 74 | src: url('overpass-regular.eot'); 75 | src: url('overpass-regular.eot?#iefix') format('embedded-opentype'), 76 | url('overpass-regular.woff2') format('woff2'), 77 | url('overpass-regular.woff') format('woff'), 78 | url('overpass-regular.ttf') format('truetype'); 79 | font-weight: 500; 80 | font-style: normal; 81 | } 82 | 83 | @font-face { 84 | font-family: 'overpass'; 85 | src: url('overpass-italic.eot'); 86 | src: url('overpass-italic.eot?#iefix') format('embedded-opentype'), 87 | url('overpass-italic.woff2') format('woff2'), 88 | url('overpass-italic.woff') format('woff'), 89 | url('overpass-italic.ttf') format('truetype'); 90 | font-weight: 500; 91 | font-style: italic; 92 | } 93 | 94 | 95 | 96 | 97 | @font-face { 98 | font-family: 'overpass'; 99 | src: url('overpass-semibold.eot'); 100 | src: url('overpass-semibold.eot?#iefix') format('embedded-opentype'), 101 | url('overpass-semibold.woff2') format('woff2'), 102 | url('overpass-semibold.woff') format('woff'), 103 | url('overpass-semibold.ttf') format('truetype'); 104 | font-weight: 600; 105 | font-style: normal; 106 | } 107 | 108 | @font-face { 109 | font-family: 'overpass'; 110 | src: url('overpass-semibold-italic.eot'); 111 | src: url('overpass-semibold-italic.eot?#iefix') format('embedded-opentype'), 112 | url('overpass-semibold-italic.woff2') format('woff2'), 113 | url('overpass-semibold-italic.woff') format('woff'), 114 | url('overpass-semibold-italic.ttf') format('truetype'); 115 | font-weight: 600; 116 | font-style: italic; 117 | } 118 | 119 | 120 | 121 | 122 | @font-face { 123 | font-family: 'overpass'; 124 | src: url('overpass-bold.eot'); 125 | src: url('overpass-bold.eot?#iefix') format('embedded-opentype'), 126 | url('overpass-bold.woff2') format('woff2'), 127 | url('overpass-bold.woff') format('woff'), 128 | url('overpass-bold.ttf') format('truetype'); 129 | font-weight: 700; 130 | font-style: normal; 131 | } 132 | 133 | @font-face { 134 | font-family: 'overpass'; 135 | src: url('overpass-bold-italic.eot'); 136 | src: url('overpass-bold-italic.eot?#iefix') format('embedded-opentype'), 137 | url('overpass-bold-italic.woff2') format('woff2'), 138 | url('overpass-bold-italic.woff') format('woff'), 139 | url('overpass-bold-italic.ttf') format('truetype'); 140 | font-weight: 700; 141 | font-style: italic; 142 | } 143 | 144 | 145 | 146 | @font-face { 147 | font-family: 'overpass'; 148 | src: url('overpass-extrabold.eot'); 149 | src: url('overpass-extrabold.eot?#iefix') format('embedded-opentype'), 150 | url('overpass-extrabold.woff2') format('woff2'), 151 | url('overpass-extrabold.woff') format('woff'), 152 | url('overpass-extrabold.ttf') format('truetype'); 153 | font-weight: 800; 154 | font-style: normal; 155 | } 156 | 157 | @font-face { 158 | font-family: 'overpass'; 159 | src: url('overpass-extrabold-italic.eot'); 160 | src: url('overpass-extrabold-italic.eot?#iefix') format('embedded-opentype'), 161 | url('overpass-extrabold-italic.woff2') format('woff2'), 162 | url('overpass-extrabold-italic.woff') format('woff'), 163 | url('overpass-extrabold-italic.ttf') format('truetype'); 164 | font-weight: 800; 165 | font-style: italic; 166 | } 167 | 168 | 169 | @font-face { 170 | font-family: 'overpass'; 171 | src: url('overpass-heavy.eot'); 172 | src: url('overpass-heavy.eot?#iefix') format('embedded-opentype'), 173 | url('overpass-heavy.woff2') format('woff2'), 174 | url('overpass-heavy.woff') format('woff'), 175 | url('overpass-heavy.ttf') format('truetype'); 176 | font-weight: 900; 177 | font-style: normal; 178 | } 179 | 180 | @font-face { 181 | font-family: 'overpass'; 182 | src: url('overpass-heavy-italic.eot'); 183 | src: url('overpass-heavy-italic.eot?#iefix') format('embedded-opentype'), 184 | url('overpass-heavy-italic.woff2') format('woff2'), 185 | url('overpass-heavy-italic.woff') format('woff'), 186 | url('overpass-heavy-italic.ttf') format('truetype'); 187 | font-weight: 900; 188 | font-style: italic; 189 | } 190 | -------------------------------------------------------------------------------- /assets/images/monitor_bezel_outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/images/monitor_bezel_outline.png -------------------------------------------------------------------------------- /assets/images/monitor_fire_inner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/images/monitor_fire_inner.png -------------------------------------------------------------------------------- /assets/models/CLH_Computer.mtl.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/models/CLH_Computer.mtl.gz -------------------------------------------------------------------------------- /assets/models/CLH_Computer.obj.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/models/CLH_Computer.obj.gz -------------------------------------------------------------------------------- /assets/models/CLH_Shuttle.mtl.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/models/CLH_Shuttle.mtl.gz -------------------------------------------------------------------------------- /assets/models/CLH_Shuttle.obj.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/models/CLH_Shuttle.obj.gz -------------------------------------------------------------------------------- /assets/models/CLH_ep2_computer_high_poly.mtl.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/models/CLH_ep2_computer_high_poly.mtl.gz -------------------------------------------------------------------------------- /assets/models/CLH_ep2_computer_high_poly.obj.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/models/CLH_ep2_computer_high_poly.obj.gz -------------------------------------------------------------------------------- /assets/models/CLH_ep2_cyc_wall.mtl.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/models/CLH_ep2_cyc_wall.mtl.gz -------------------------------------------------------------------------------- /assets/models/CLH_ep2_cyc_wall.obj.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/models/CLH_ep2_cyc_wall.obj.gz -------------------------------------------------------------------------------- /assets/sfx/Waveshaper - 66 MHz.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/Waveshaper - 66 MHz.mp3 -------------------------------------------------------------------------------- /assets/sfx/boot.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/boot.mp3 -------------------------------------------------------------------------------- /assets/sfx/cmd-bad.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/cmd-bad.mp3 -------------------------------------------------------------------------------- /assets/sfx/cmd-gold.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/cmd-gold.mp3 -------------------------------------------------------------------------------- /assets/sfx/cmd-good.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/cmd-good.mp3 -------------------------------------------------------------------------------- /assets/sfx/keypress.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/keypress.mp3 -------------------------------------------------------------------------------- /assets/sfx/leaderboard.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/leaderboard.mp3 -------------------------------------------------------------------------------- /assets/sfx/menu-music.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/menu-music.mp3 -------------------------------------------------------------------------------- /assets/sfx/play.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/play.mp3 -------------------------------------------------------------------------------- /assets/sfx/timer-relaxed.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/timer-relaxed.mp3 -------------------------------------------------------------------------------- /assets/sfx/timer-urgent.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/sfx/timer-urgent.mp3 -------------------------------------------------------------------------------- /assets/styles.css: -------------------------------------------------------------------------------- 1 | @import "./fonts/fonts.css"; 2 | 3 | html, 4 | body { 5 | height: 100%; 6 | padding: 0; 7 | margin: 0; 8 | overflow: hidden; 9 | } 10 | 11 | body { 12 | background: var(--clh-black); 13 | color: var(--clh-white); 14 | 15 | display: flex; 16 | align-items: center; 17 | justify-items: center; 18 | justify-content: center; 19 | 20 | font-family: overpass; 21 | 22 | --clh-white: #dbdbdb; 23 | --clh-black: #101114; 24 | --clh-purple: #200a5f; 25 | --clh-purple-light: #6b45a0; 26 | --clh-yellow: #f4be4a; 27 | --clh-yellow-light: #f7cf78; 28 | --clh-orange: #ef702b; 29 | --clh-orange-light: #f7a060; 30 | --clh-blue: #00a9e0; 31 | --clh-blue-light: #68cceb; 32 | 33 | --title-reveal-interval: 600ms; 34 | } 35 | 36 | a { 37 | color: var(--clh-blue); 38 | } 39 | a:active { 40 | color: var(--clh-white); 41 | } 42 | a:visited { 43 | color: var(--clh-purple-light); 44 | } 45 | 46 | textarea#cmd { 47 | background: rgba(0, 0, 0, 0.6); 48 | color: var(--clh-blue-light); 49 | 50 | border: 3px solid var(--clh-blue); 51 | outline: none; 52 | font-family: monospace; 53 | padding: 8px; 54 | font-size: 32px; 55 | text-transform: lowercase; 56 | text-align: left; 57 | position: absolute; 58 | bottom: 20px; 59 | right: 20px; 60 | width: 20vw; 61 | height: 30vw; 62 | 63 | opacity: 0; 64 | } 65 | 66 | #game-canvas { 67 | position: absolute; 68 | z-index: -1; 69 | top: 0; 70 | left: 0; 71 | right: 0; 72 | bottom: 0; 73 | } 74 | 75 | #console-canvas { 76 | position: absolute; 77 | width: calc(1920px / 7); 78 | bottom: 20px; 79 | right: 20px; 80 | border: 3px solid var(--clh-blue); 81 | user-select: none; /* disable ctrl+a to select all text */ 82 | 83 | /* letter-spacing: -0.5em; */ 84 | 85 | opacity: 0; 86 | } 87 | 88 | #test-pattern { 89 | display: none; 90 | } 91 | 92 | /* Title screen */ 93 | #title-screen > * { 94 | opacity: 0; 95 | transition: opacity 100ms linear; 96 | width: 100%; 97 | } 98 | #title-screen > *.show { 99 | opacity: 1; 100 | transition: opacity 1800ms linear; 101 | } 102 | 103 | #title-screen { 104 | opacity: 0; 105 | transition: opacity 100ms linear; 106 | position: absolute; 107 | top: 20vh; 108 | left: 10vw; 109 | width: 40vw; 110 | height: 70vh; 111 | display: grid; 112 | 113 | font-size: 1.8rem; 114 | font-weight: bold; 115 | } 116 | #title-screen.show { 117 | opacity: 1; 118 | } 119 | 120 | #credits { 121 | font-size: 0.7em; 122 | color: var(--clh-white); 123 | display: none; 124 | } 125 | 126 | /* delay the reveal of each title screen element so the reveals cacade down the screen */ 127 | #title-screen > *:nth-child(1) { 128 | transition-delay: calc(0 * var(--title-reveal-interval)); 129 | } 130 | #title-screen > *:nth-child(2) { 131 | transition-delay: calc(1 * var(--title-reveal-interval)); 132 | } 133 | #title-screen > *:nth-child(3) { 134 | transition-delay: calc(2 * var(--title-reveal-interval)); 135 | } 136 | #title-screen > *:nth-child(4) { 137 | transition-delay: calc(3 * var(--title-reveal-interval)); 138 | } 139 | #title-screen > *:nth-child(5) { 140 | transition-delay: calc(4 * var(--title-reveal-interval)); 141 | } 142 | #title-screen > *:nth-child(6) { 143 | transition-delay: calc(5 * var(--title-reveal-interval)); 144 | } 145 | #title-screen > *:nth-child(7) { 146 | transition-delay: calc(6 * var(--title-reveal-interval)); 147 | } 148 | #title-screen > *:nth-child(8) { 149 | transition-delay: calc(6 * var(--title-reveal-interval)); 150 | } 151 | #title-screen > *:nth-child(9) { 152 | transition-delay: calc(6 * var(--title-reveal-interval)); 153 | } 154 | #title-screen > *:nth-child(10) { 155 | transition-delay: calc(6 * var(--title-reveal-interval)); 156 | } 157 | 158 | /* individual elements of the title screen */ 159 | #langs { 160 | text-align: center; 161 | font-size: 0.8em; 162 | color: var(--clh-yellow-light); 163 | font-family: overpass-mono; 164 | } 165 | #langs .sep { 166 | color: var(--clh-white); 167 | } 168 | #listen { 169 | font-size: 0.7em; 170 | color: var(--clh-white); 171 | } 172 | 173 | #intro { 174 | opacity: 0; 175 | transition: opacity linear 300ms; 176 | position: absolute; 177 | top: 0; 178 | right: 0; 179 | bottom: 0; 180 | left: 0; 181 | background: var(--clh-black); 182 | color: var(--clh-white); 183 | font-size: 3rem; 184 | font-weight: bold; 185 | display: flex; 186 | flex-direction: column; 187 | justify-content: center; 188 | align-items: center; 189 | user-select: none; 190 | padding-right: 30px; 191 | padding-left: 30px; 192 | } 193 | #intro .cursor { 194 | animation: blink 666ms steps(2, start) infinite; 195 | } 196 | #intro.show { 197 | opacity: 1; 198 | } 199 | #intro .logo { 200 | display: block; 201 | clear: both; 202 | max-width: 100%; 203 | width: 600px; 204 | margin-bottom: 10vh; 205 | } 206 | 207 | @keyframes blink { 208 | to { 209 | visibility: hidden; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /assets/textures/clh_test_pattern.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/textures/clh_test_pattern.jpg -------------------------------------------------------------------------------- /assets/textures/equirectangular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/textures/equirectangular.png -------------------------------------------------------------------------------- /assets/textures/wall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CommandLineHeroes/clh-bash/5f53bb84d7afcaee657f7807d79da4454a0991aa/assets/textures/wall.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | The Command Line Heroes BASH! 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | 24 | 25 | Apologies, mobile is not yet supported, but PRs are welcome! 26 | DOWNLOADING 27 | 28 | 29 |
30 |
31 | 32 |

33 | Test your command line skills.
34 | You have {{parseInt(gameDuration/1000)}} seconds to type commands from: 35 |

36 |

37 | BASH · JAVASCRIPT · PYTHON · HTML5 38 |

39 |

40 | Set the terminal on fire. Type PLAY to begin. 41 |

42 |

43 | Listen to the podcast at redhat.com/commandlineheroes.

44 | Credits · GitHub 45 |

46 |

47 | Created by Michael Clayton, Jared Sprague, and the Command Line Heroes team and community.

48 | Soundtrack: "66 MHZ" by Waveshaper.

49 | 50 | Special thanks: Dan Courcy · Eric Kramer · Open-source contributors · Command Line Heroes Discord community

51 | 52 | 53 | Back 54 |

55 |
56 | 66 |
67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clh-bash", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node start.js", 9 | "extract": "gunzip -k assets/models/*.gz", 10 | "compress": "gzip -fk assets/models/*.{mtl,obj}", 11 | "prettier": "node ./node_modules/.bin/prettier --write src/*.js assets/cmds/*.js", 12 | "unzip": "node src/unzip.js" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "devDependencies": { 18 | "browser-sync": "^2.26.14", 19 | "compression": "^1.7.3", 20 | "prettier": "^1.17.0" 21 | }, 22 | "dependencies": { 23 | "@tweenjs/tween.js": "^17.2.0", 24 | "howler": "^2.1.1", 25 | "ismobilejs": "^0.5.1", 26 | "lodash": "^4.17.11", 27 | "three": "^0.126.1", 28 | "vue": "^2.5.22", 29 | "vue-autofocus-directive": "0.0.5" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Fire.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mike Piecuch / https://github.com/mikepiecuch 3 | * 4 | * Based on research paper "Real-Time Fluid Dynamics for Games" by Jos Stam 5 | * http://www.dgp.toronto.edu/people/stam/reality/Research/pdf/GDC03.pdf 6 | * 7 | */ 8 | 9 | THREE.Fire = function ( geometry, options ) { 10 | 11 | THREE.Mesh.call( this, geometry ); 12 | 13 | this.type = 'Fire'; 14 | 15 | this.clock = new THREE.Clock(); 16 | 17 | options = options || {}; 18 | 19 | var textureWidth = options.textureWidth || 512; 20 | var textureHeight = options.textureHeight || 512; 21 | var oneOverWidth = 1.0 / textureWidth; 22 | var oneOverHeight = 1.0 / textureHeight; 23 | 24 | var debug = ( options.debug === undefined ) ? false : options.debug; 25 | this.color1 = options.color1 || new THREE.Color( 0xffffff ); 26 | this.color2 = options.color2 || new THREE.Color( 0xffa000 ); 27 | this.color3 = options.color3 || new THREE.Color( 0x000000 ); 28 | this.colorBias = ( options.colorBias === undefined ) ? 0.8 : options.colorBias; 29 | this.diffuse = ( options.diffuse === undefined ) ? 1.33 : options.diffuse; 30 | this.viscosity = ( options.viscosity === undefined ) ? 0.25 : options.viscosity; 31 | this.expansion = ( options.expansion === undefined ) ? - 0.25 : options.expansion; 32 | this.swirl = ( options.swirl === undefined ) ? 50.0 : options.swirl; 33 | this.burnRate = ( options.burnRate === undefined ) ? 0.3 : options.burnRate; 34 | this.drag = ( options.drag === undefined ) ? 0.35 : options.drag; 35 | this.airSpeed = ( options.airSpeed === undefined ) ? 6.0 : options.airSpeed; 36 | this.windVector = options.windVector || new THREE.Vector2( 0.0, 0.75 ); 37 | this.speed = ( options.speed === undefined ) ? 500.0 : options.speed; 38 | this.massConservation = ( options.massConservation === undefined ) ? false : options.massConservation; 39 | 40 | var size = textureWidth * textureHeight; 41 | this.sourceData = new Uint8Array( 4 * size ); 42 | 43 | this.clearSources = function () { 44 | 45 | for ( var y = 0; y < textureHeight; y ++ ) { 46 | 47 | for ( var x = 0; x < textureWidth; x ++ ) { 48 | 49 | var i = y * textureWidth + x; 50 | var stride = i * 4; 51 | 52 | this.sourceData[ stride ] = 0; 53 | this.sourceData[ stride + 1 ] = 0; 54 | this.sourceData[ stride + 2 ] = 0; 55 | this.sourceData[ stride + 3 ] = 0; 56 | 57 | } 58 | 59 | } 60 | 61 | this.sourceMaterial.uniforms.sourceMap.value = this.internalSource; 62 | this.sourceMaterial.needsUpdate = true; 63 | 64 | return this.sourceData; 65 | 66 | }; 67 | 68 | this.addSource = function ( u, v, radius, density = null, windX = null, windY = null ) { 69 | 70 | var startX = Math.max( Math.floor( ( u - radius ) * textureWidth ), 0 ); 71 | var startY = Math.max( Math.floor( ( v - radius ) * textureHeight ), 0 ); 72 | var endX = Math.min( Math.floor( ( u + radius ) * textureWidth ), textureWidth ); 73 | var endY = Math.min( Math.floor( ( v + radius ) * textureHeight ), textureHeight ); 74 | 75 | for ( var y = startY; y < endY; y ++ ) { 76 | 77 | for ( var x = startX; x < endX; x ++ ) { 78 | 79 | var diffX = x * oneOverWidth - u; 80 | var diffY = y * oneOverHeight - v; 81 | 82 | if ( diffX * diffX + diffY * diffY < radius * radius ) { 83 | 84 | var i = y * textureWidth + x; 85 | var stride = i * 4; 86 | 87 | if ( density != null ) { 88 | 89 | this.sourceData[ stride ] = Math.min( Math.max( density, 0.0 ), 1.0 ) * 255; 90 | 91 | } 92 | if ( windX != null ) { 93 | 94 | var wind = Math.min( Math.max( windX, - 1.0 ), 1.0 ); 95 | wind = ( wind < 0.0 ) ? Math.floor( wind * 127 ) + 255 : Math.floor( wind * 127 ); 96 | this.sourceData[ stride + 1 ] = wind; 97 | 98 | } 99 | if ( windY != null ) { 100 | 101 | var wind = Math.min( Math.max( windY, - 1.0 ), 1.0 ); 102 | wind = ( wind < 0.0 ) ? Math.floor( wind * 127 ) + 255 : Math.floor( wind * 127 ); 103 | this.sourceData[ stride + 2 ] = wind; 104 | 105 | } 106 | 107 | } 108 | 109 | } 110 | 111 | } 112 | 113 | this.internalSource.needsUpdate = true; 114 | 115 | return this.sourceData; 116 | 117 | }; 118 | 119 | // When setting source map, red channel is density. Green and blue channels 120 | // encode x and y velocity respectively as signed chars: 121 | // (0 -> 127 = 0.0 -> 1.0, 128 -> 255 = -1.0 -> 0.0 ) 122 | this.setSourceMap = function ( texture ) { 123 | 124 | this.sourceMaterial.uniforms.sourceMap.value = texture; 125 | 126 | }; 127 | 128 | var parameters = { 129 | minFilter: THREE.NearestFilter, 130 | magFilter: THREE.NearestFilter, 131 | depthBuffer: false, 132 | stencilBuffer: false 133 | }; 134 | 135 | 136 | this.field0 = new THREE.WebGLRenderTarget( textureWidth, textureHeight, parameters ); 137 | 138 | this.field0.background = new THREE.Color( 0x000000 ); 139 | 140 | this.field1 = new THREE.WebGLRenderTarget( textureWidth, textureHeight, parameters ); 141 | 142 | this.field0.background = new THREE.Color( 0x000000 ); 143 | 144 | this.fieldProj = new THREE.WebGLRenderTarget( textureWidth, textureHeight, parameters ); 145 | 146 | this.field0.background = new THREE.Color( 0x000000 ); 147 | 148 | if ( ! THREE.Math.isPowerOfTwo( textureWidth ) || 149 | ! THREE.Math.isPowerOfTwo( textureHeight ) ) { 150 | 151 | this.field0.texture.generateMipmaps = false; 152 | this.field1.texture.generateMipmaps = false; 153 | this.fieldProj.texture.generateMipmaps = false; 154 | 155 | } 156 | 157 | 158 | this.fieldScene = new THREE.Scene(); 159 | this.fieldScene.background = new THREE.Color( 0x000000 ); 160 | 161 | this.orthoCamera = new THREE.OrthographicCamera( textureWidth / - 2, textureWidth / 2, textureHeight / 2, textureHeight / - 2, 1, 2 ); 162 | this.orthoCamera.position.z = 1; 163 | 164 | this.fieldGeometry = new THREE.PlaneBufferGeometry( textureWidth, textureHeight ); 165 | 166 | this.internalSource = new THREE.DataTexture( this.sourceData, textureWidth, textureHeight, THREE.RGBAFormat ); 167 | 168 | // Source Shader 169 | 170 | var shader = THREE.Fire.SourceShader; 171 | this.sourceMaterial = new THREE.ShaderMaterial( { 172 | uniforms: shader.uniforms, 173 | vertexShader: shader.vertexShader, 174 | fragmentShader: shader.fragmentShader, 175 | transparent: false 176 | } ); 177 | 178 | this.clearSources(); 179 | 180 | this.sourceMesh = new THREE.Mesh( this.fieldGeometry, this.sourceMaterial ); 181 | this.fieldScene.add( this.sourceMesh ); 182 | 183 | // Diffuse Shader 184 | 185 | var shader = THREE.Fire.DiffuseShader; 186 | this.diffuseMaterial = new THREE.ShaderMaterial( { 187 | uniforms: shader.uniforms, 188 | vertexShader: shader.vertexShader, 189 | fragmentShader: shader.fragmentShader, 190 | transparent: false 191 | } ); 192 | 193 | this.diffuseMaterial.uniforms.oneOverWidth.value = oneOverWidth; 194 | this.diffuseMaterial.uniforms.oneOverHeight.value = oneOverHeight; 195 | 196 | this.diffuseMesh = new THREE.Mesh( this.fieldGeometry, this.diffuseMaterial ); 197 | this.fieldScene.add( this.diffuseMesh ); 198 | 199 | // Drift Shader 200 | 201 | shader = THREE.Fire.DriftShader; 202 | this.driftMaterial = new THREE.ShaderMaterial( { 203 | uniforms: shader.uniforms, 204 | vertexShader: shader.vertexShader, 205 | fragmentShader: shader.fragmentShader, 206 | transparent: false 207 | } ); 208 | 209 | this.driftMaterial.uniforms.oneOverWidth.value = oneOverWidth; 210 | this.driftMaterial.uniforms.oneOverHeight.value = oneOverHeight; 211 | 212 | this.driftMesh = new THREE.Mesh( this.fieldGeometry, this.driftMaterial ); 213 | this.fieldScene.add( this.driftMesh ); 214 | 215 | // Projection Shader 1 216 | 217 | shader = THREE.Fire.ProjectionShader1; 218 | this.projMaterial1 = new THREE.ShaderMaterial( { 219 | uniforms: shader.uniforms, 220 | vertexShader: shader.vertexShader, 221 | fragmentShader: shader.fragmentShader, 222 | transparent: false 223 | } ); 224 | 225 | this.projMaterial1.uniforms.oneOverWidth.value = oneOverWidth; 226 | this.projMaterial1.uniforms.oneOverHeight.value = oneOverHeight; 227 | 228 | this.projMesh1 = new THREE.Mesh( this.fieldGeometry, this.projMaterial1 ); 229 | this.fieldScene.add( this.projMesh1 ); 230 | 231 | // Projection Shader 2 232 | 233 | shader = THREE.Fire.ProjectionShader2; 234 | this.projMaterial2 = new THREE.ShaderMaterial( { 235 | uniforms: shader.uniforms, 236 | vertexShader: shader.vertexShader, 237 | fragmentShader: shader.fragmentShader, 238 | transparent: false 239 | } ); 240 | 241 | 242 | this.projMaterial2.uniforms.oneOverWidth.value = oneOverWidth; 243 | this.projMaterial2.uniforms.oneOverHeight.value = oneOverHeight; 244 | 245 | this.projMesh2 = new THREE.Mesh( this.fieldGeometry, this.projMaterial2 ); 246 | this.fieldScene.add( this.projMesh2 ); 247 | 248 | // Projection Shader 3 249 | 250 | shader = THREE.Fire.ProjectionShader3; 251 | this.projMaterial3 = new THREE.ShaderMaterial( { 252 | uniforms: shader.uniforms, 253 | vertexShader: shader.vertexShader, 254 | fragmentShader: shader.fragmentShader, 255 | transparent: false 256 | } ); 257 | 258 | 259 | this.projMaterial3.uniforms.oneOverWidth.value = oneOverWidth; 260 | this.projMaterial3.uniforms.oneOverHeight.value = oneOverHeight; 261 | 262 | this.projMesh3 = new THREE.Mesh( this.fieldGeometry, this.projMaterial3 ); 263 | this.fieldScene.add( this.projMesh3 ); 264 | 265 | // Color Shader 266 | 267 | if ( debug ) { 268 | 269 | shader = THREE.Fire.DebugShader; 270 | 271 | } else { 272 | 273 | shader = THREE.Fire.ColorShader; 274 | 275 | } 276 | this.material = new THREE.ShaderMaterial( { 277 | uniforms: shader.uniforms, 278 | vertexShader: shader.vertexShader, 279 | fragmentShader: shader.fragmentShader, 280 | transparent: true 281 | } ); 282 | 283 | this.material.uniforms.densityMap.value = this.field1.texture; 284 | 285 | this.configShaders = function ( dt ) { 286 | 287 | this.diffuseMaterial.uniforms.diffuse.value = dt * 0.05 * this.diffuse; 288 | this.diffuseMaterial.uniforms.viscosity.value = dt * 0.05 * this.viscosity; 289 | this.diffuseMaterial.uniforms.expansion.value = Math.exp( this.expansion * - 1.0 ); 290 | this.diffuseMaterial.uniforms.swirl.value = Math.exp( this.swirl * - 0.1 ); 291 | this.diffuseMaterial.uniforms.drag.value = Math.exp( this.drag * - 0.1 ); 292 | this.diffuseMaterial.uniforms.burnRate.value = this.burnRate * dt * 0.01; 293 | this.driftMaterial.uniforms.windVector.value = this.windVector; 294 | this.driftMaterial.uniforms.airSpeed.value = dt * this.airSpeed * 0.001 * textureHeight; 295 | this.material.uniforms.color1.value = this.color1; 296 | this.material.uniforms.color2.value = this.color2; 297 | this.material.uniforms.color3.value = this.color3; 298 | this.material.uniforms.colorBias.value = this.colorBias; 299 | 300 | }; 301 | 302 | this.clearDiffuse = function () { 303 | 304 | this.diffuseMaterial.uniforms.expansion.value = 1.0; 305 | this.diffuseMaterial.uniforms.swirl.value = 1.0; 306 | this.diffuseMaterial.uniforms.drag.value = 1.0; 307 | this.diffuseMaterial.uniforms.burnRate.value = 0.0; 308 | 309 | }; 310 | 311 | this.swapTextures = function () { 312 | 313 | var swap = this.field0; 314 | this.field0 = this.field1; 315 | this.field1 = swap; 316 | 317 | }; 318 | 319 | this.saveRenderState = function ( renderer ) { 320 | 321 | this.savedRenderTarget = renderer.getRenderTarget(); 322 | this.savedVrEnabled = renderer.xr.enabled; 323 | this.savedShadowAutoUpdate = renderer.shadowMap.autoUpdate; 324 | this.savedAntialias = renderer.antialias; 325 | this.savedToneMapping = renderer.toneMapping; 326 | 327 | }; 328 | 329 | this.restoreRenderState = function ( renderer ) { 330 | 331 | renderer.xr.enabled = this.savedVrEnabled; 332 | renderer.shadowMap.autoUpdate = this.savedShadowAutoUpdate; 333 | renderer.setRenderTarget( this.savedRenderTarget ); 334 | renderer.antialias = this.savedAntialias; 335 | renderer.toneMapping = this.savedToneMapping; 336 | 337 | }; 338 | 339 | this.renderSource = function ( renderer ) { 340 | 341 | this.sourceMesh.visible = true; 342 | 343 | this.sourceMaterial.uniforms.densityMap.value = this.field0.texture; 344 | 345 | renderer.setRenderTarget(this.field1) 346 | renderer.render( this.fieldScene, this.orthoCamera); 347 | 348 | this.sourceMesh.visible = false; 349 | 350 | this.swapTextures(); 351 | 352 | }; 353 | 354 | this.renderDiffuse = function ( renderer ) { 355 | 356 | this.diffuseMesh.visible = true; 357 | 358 | this.diffuseMaterial.uniforms.densityMap.value = this.field0.texture; 359 | 360 | renderer.setRenderTarget(this.field1) 361 | renderer.render( this.fieldScene, this.orthoCamera); 362 | 363 | this.diffuseMesh.visible = false; 364 | 365 | this.swapTextures(); 366 | 367 | }; 368 | 369 | this.renderDrift = function ( renderer ) { 370 | 371 | this.driftMesh.visible = true; 372 | 373 | this.driftMaterial.uniforms.densityMap.value = this.field0.texture; 374 | 375 | renderer.setRenderTarget(this.field1) 376 | renderer.render( this.fieldScene, this.orthoCamera); 377 | 378 | this.driftMesh.visible = false; 379 | 380 | this.swapTextures(); 381 | 382 | }; 383 | 384 | this.renderProject = function ( renderer ) { 385 | 386 | // Projection pass 1 387 | 388 | this.projMesh1.visible = true; 389 | 390 | this.projMaterial1.uniforms.densityMap.value = this.field0.texture; 391 | 392 | renderer.setRenderTarget(this.field1) 393 | renderer.render( this.fieldScene, this.orthoCamera); 394 | 395 | this.projMesh1.visible = false; 396 | 397 | this.projMaterial2.uniforms.densityMap.value = this.fieldProj.texture; 398 | 399 | // Projection pass 2 400 | 401 | this.projMesh2.visible = true; 402 | 403 | for ( var i = 0; i < 20; i ++ ) { 404 | 405 | renderer.setRenderTarget(this.field1) 406 | renderer.render( this.fieldScene, this.orthoCamera); 407 | 408 | var temp = this.field1; 409 | this.field1 = this.fieldProj; 410 | this.fieldProj = temp; 411 | 412 | this.projMaterial2.uniforms.densityMap.value = this.fieldProj.texture; 413 | 414 | } 415 | 416 | this.projMesh2.visible = false; 417 | 418 | this.projMaterial3.uniforms.densityMap.value = this.field0.texture; 419 | this.projMaterial3.uniforms.projMap.value = this.fieldProj.texture; 420 | 421 | // Projection pass 3 422 | 423 | this.projMesh3.visible = true; 424 | 425 | renderer.setRenderTarget(this.field1) 426 | renderer.render( this.fieldScene, this.orthoCamera); 427 | 428 | this.projMesh3.visible = false; 429 | 430 | this.swapTextures(); 431 | 432 | }; 433 | 434 | this.onBeforeRender = function ( renderer, scene, camera ) { 435 | 436 | var delta = this.clock.getDelta(); 437 | if ( delta > 0.1 ) { 438 | 439 | delta = 0.1; 440 | 441 | } 442 | var dt = delta * ( this.speed * 0.1 ); 443 | 444 | this.configShaders( dt ); 445 | 446 | this.saveRenderState( renderer ); 447 | 448 | renderer.xr.enabled = false; // Avoid camera modification and recursion 449 | renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows 450 | renderer.antialias = false; 451 | renderer.toneMapping = THREE.NoToneMapping; 452 | 453 | this.sourceMesh.visible = false; 454 | this.diffuseMesh.visible = false; 455 | this.driftMesh.visible = false; 456 | this.projMesh1.visible = false; 457 | this.projMesh2.visible = false; 458 | this.projMesh3.visible = false; 459 | 460 | this.renderSource( renderer ); 461 | 462 | this.clearDiffuse(); 463 | for ( var i = 0; i < 21; i ++ ) { 464 | 465 | this.renderDiffuse( renderer ); 466 | 467 | } 468 | this.configShaders( dt ); 469 | this.renderDiffuse( renderer ); 470 | 471 | this.renderDrift( renderer ); 472 | 473 | if ( this.massConservation ) { 474 | 475 | this.renderProject( renderer ); 476 | this.renderProject( renderer ); 477 | 478 | } 479 | 480 | // Final result out for coloring 481 | 482 | this.material.map = this.field1.texture; 483 | this.material.transparent = true; 484 | this.material.minFilter = THREE.LinearFilter, 485 | this.material.magFilter = THREE.LinearFilter, 486 | 487 | this.restoreRenderState( renderer ); 488 | 489 | }; 490 | 491 | }; 492 | 493 | 494 | THREE.Fire.prototype = Object.create( THREE.Mesh.prototype ); 495 | THREE.Fire.prototype.constructor = THREE.Fire; 496 | 497 | THREE.Fire.SourceShader = { 498 | 499 | uniforms: { 500 | 'sourceMap': { 501 | type: 't', 502 | value: null 503 | }, 504 | 'densityMap': { 505 | type: 't', 506 | value: null 507 | } 508 | }, 509 | 510 | vertexShader: [ 511 | 'varying vec2 vUv;', 512 | 513 | 'void main() {', 514 | 515 | ' vUv = uv;', 516 | 517 | ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );', 518 | ' gl_Position = projectionMatrix * mvPosition;', 519 | 520 | '}' 521 | 522 | ].join( "\n" ), 523 | 524 | fragmentShader: [ 525 | 'uniform sampler2D sourceMap;', 526 | 'uniform sampler2D densityMap;', 527 | 528 | 'varying vec2 vUv;', 529 | 530 | 'void main() {', 531 | ' vec4 source = texture2D( sourceMap, vUv );', 532 | ' vec4 current = texture2D( densityMap, vUv );', 533 | 534 | ' vec2 v0 = (current.gb - step(0.5, current.gb)) * 2.0;', 535 | ' vec2 v1 = (source.gb - step(0.5, source.gb)) * 2.0;', 536 | 537 | ' vec2 newVel = v0 + v1;', 538 | 539 | ' newVel = clamp(newVel, -0.99, 0.99);', 540 | ' newVel = newVel * 0.5 + step(0.0, -newVel);', 541 | 542 | ' float newDensity = source.r + current.a;', 543 | ' float newTemp = source.r + current.r;', 544 | 545 | ' newDensity = clamp(newDensity, 0.0, 1.0);', 546 | ' newTemp = clamp(newTemp, 0.0, 1.0);', 547 | 548 | ' gl_FragColor = vec4(newTemp, newVel.xy, newDensity);', 549 | 550 | '}' 551 | 552 | ].join( "\n" ) 553 | }; 554 | 555 | 556 | THREE.Fire.DiffuseShader = { 557 | 558 | uniforms: { 559 | 'oneOverWidth': { 560 | type: 'f', 561 | value: null 562 | }, 563 | 'oneOverHeight': { 564 | type: 'f', 565 | value: null 566 | }, 567 | 'diffuse': { 568 | type: 'f', 569 | value: null 570 | }, 571 | 'viscosity': { 572 | type: 'f', 573 | value: null 574 | }, 575 | 'expansion': { 576 | type: 'f', 577 | value: null 578 | }, 579 | 'swirl': { 580 | type: 'f', 581 | value: null 582 | }, 583 | 'drag': { 584 | type: 'f', 585 | value: null 586 | }, 587 | 'burnRate': { 588 | type: 'f', 589 | value: null 590 | }, 591 | 'densityMap': { 592 | type: 't', 593 | value: null 594 | } 595 | }, 596 | 597 | vertexShader: [ 598 | 'varying vec2 vUv;', 599 | 600 | 'void main() {', 601 | 602 | ' vUv = uv;', 603 | 604 | ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );', 605 | ' gl_Position = projectionMatrix * mvPosition;', 606 | 607 | '}' 608 | 609 | ].join( "\n" ), 610 | 611 | fragmentShader: [ 612 | 'uniform float oneOverWidth;', 613 | 'uniform float oneOverHeight;', 614 | 'uniform float diffuse;', 615 | 'uniform float viscosity;', 616 | 'uniform float expansion;', 617 | 'uniform float swirl;', 618 | 'uniform float burnRate;', 619 | 'uniform float drag;', 620 | 'uniform sampler2D densityMap;', 621 | 622 | 'varying vec2 vUv;', 623 | 624 | 'void main() {', 625 | 626 | ' vec4 dC = texture2D( densityMap, vUv );', 627 | ' vec4 dL = texture2D( densityMap, vec2(vUv.x - oneOverWidth, vUv.y) );', 628 | ' vec4 dR = texture2D( densityMap, vec2(vUv.x + oneOverWidth, vUv.y) );', 629 | ' vec4 dU = texture2D( densityMap, vec2(vUv.x, vUv.y - oneOverHeight) );', 630 | ' vec4 dD = texture2D( densityMap, vec2(vUv.x, vUv.y + oneOverHeight) );', 631 | ' vec4 dUL = texture2D( densityMap, vec2(vUv.x - oneOverWidth, vUv.y - oneOverHeight) );', 632 | ' vec4 dUR = texture2D( densityMap, vec2(vUv.x + oneOverWidth, vUv.y - oneOverHeight) );', 633 | ' vec4 dDL = texture2D( densityMap, vec2(vUv.x - oneOverWidth, vUv.y + oneOverHeight) );', 634 | ' vec4 dDR = texture2D( densityMap, vec2(vUv.x + oneOverWidth, vUv.y + oneOverHeight) );', 635 | 636 | ' dC.yz = (dC.yz - step(0.5, dC.yz)) * 2.0;', 637 | ' dL.yz = (dL.yz - step(0.5, dL.yz)) * 2.0;', 638 | ' dR.yz = (dR.yz - step(0.5, dR.yz)) * 2.0;', 639 | ' dU.yz = (dU.yz - step(0.5, dU.yz)) * 2.0;', 640 | ' dD.yz = (dD.yz - step(0.5, dD.yz)) * 2.0;', 641 | ' dUL.yz = (dUL.yz - step(0.5, dUL.yz)) * 2.0;', 642 | ' dUR.yz = (dUR.yz - step(0.5, dUR.yz)) * 2.0;', 643 | ' dDL.yz = (dDL.yz - step(0.5, dDL.yz)) * 2.0;', 644 | ' dDR.yz = (dDR.yz - step(0.5, dDR.yz)) * 2.0;', 645 | 646 | ' vec4 result = (dC + vec4(diffuse, viscosity, viscosity, diffuse) * ( dL + dR + dU + dD + dUL + dUR + dDL + dDR )) / (1.0 + 8.0 * vec4(diffuse, viscosity, viscosity, diffuse)) - vec4(0.0, 0.0, 0.0, 0.001);', 647 | 648 | ' float temperature = result.r;', 649 | ' temperature = clamp(temperature - burnRate, 0.0, 1.0);', 650 | 651 | ' vec2 velocity = result.yz;', 652 | 653 | ' vec2 expansionVec = vec2(dL.w - dR.w, dU.w - dD.w);', 654 | 655 | ' vec2 swirlVec = vec2((dL.z - dR.z) * 0.5, (dU.y - dD.y) * 0.5);', 656 | 657 | ' velocity = velocity + (1.0 - expansion) * expansionVec + (1.0 - swirl) * swirlVec;', 658 | 659 | ' velocity = velocity - (1.0 - drag) * velocity;', 660 | 661 | ' gl_FragColor = vec4(temperature, velocity * 0.5 + step(0.0, -velocity), result.w);', 662 | 663 | ' gl_FragColor = gl_FragColor * step(oneOverWidth, vUv.x);', 664 | ' gl_FragColor = gl_FragColor * step(oneOverHeight, vUv.y);', 665 | ' gl_FragColor = gl_FragColor * step(vUv.x, 1.0 - oneOverWidth);', 666 | ' gl_FragColor = gl_FragColor * step(vUv.y, 1.0 - oneOverHeight);', 667 | 668 | '}' 669 | 670 | ].join( "\n" ) 671 | }; 672 | 673 | THREE.Fire.DriftShader = { 674 | 675 | uniforms: { 676 | 'oneOverWidth': { 677 | type: 'f', 678 | value: null 679 | }, 680 | 'oneOverHeight': { 681 | type: 'f', 682 | value: null 683 | }, 684 | 'windVector': { 685 | type: 'v2', 686 | value: new THREE.Vector2( 0.0, 0.0 ) 687 | }, 688 | 'airSpeed': { 689 | type: 'f', 690 | value: null 691 | }, 692 | 'densityMap': { 693 | type: 't', 694 | value: null 695 | } 696 | }, 697 | 698 | vertexShader: [ 699 | 'varying vec2 vUv;', 700 | 701 | 'void main() {', 702 | 703 | ' vUv = uv;', 704 | 705 | ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );', 706 | ' gl_Position = projectionMatrix * mvPosition;', 707 | 708 | '}' 709 | 710 | ].join( "\n" ), 711 | 712 | fragmentShader: [ 713 | 'uniform float oneOverWidth;', 714 | 'uniform float oneOverHeight;', 715 | 'uniform vec2 windVector;', 716 | 'uniform float airSpeed;', 717 | 'uniform sampler2D densityMap;', 718 | 719 | 'varying vec2 vUv;', 720 | 721 | 'void main() {', 722 | ' vec2 velocity = texture2D( densityMap, vUv ).gb;', 723 | ' velocity = (velocity - step(0.5, velocity)) * 2.0;', 724 | 725 | ' velocity = velocity + windVector;', 726 | 727 | ' vec2 sourcePos = vUv - airSpeed * vec2(oneOverWidth, oneOverHeight) * velocity;', 728 | 729 | ' vec2 units = sourcePos / vec2(oneOverWidth, oneOverHeight);', 730 | 731 | ' vec2 intPos = floor(units);', 732 | ' vec2 frac = units - intPos;', 733 | ' intPos = intPos * vec2(oneOverWidth, oneOverHeight);', 734 | 735 | ' vec4 dX0Y0 = texture2D( densityMap, intPos + vec2(0.0, -oneOverHeight) );', 736 | ' vec4 dX1Y0 = texture2D( densityMap, intPos + vec2(oneOverWidth, 0.0) );', 737 | ' vec4 dX0Y1 = texture2D( densityMap, intPos + vec2(0.0, oneOverHeight) );', 738 | ' vec4 dX1Y1 = texture2D( densityMap, intPos + vec2(oneOverWidth, oneOverHeight) );', 739 | 740 | 741 | ' dX0Y0.gb = (dX0Y0.gb - step(0.5, dX0Y0.gb)) * 2.0;', 742 | ' dX1Y0.gb = (dX1Y0.gb - step(0.5, dX1Y0.gb)) * 2.0;', 743 | ' dX0Y1.gb = (dX0Y1.gb - step(0.5, dX0Y1.gb)) * 2.0;', 744 | ' dX1Y1.gb = (dX1Y1.gb - step(0.5, dX1Y1.gb)) * 2.0;', 745 | 746 | ' vec4 source = mix(mix(dX0Y0, dX1Y0, frac.x), mix(dX0Y1, dX1Y1, frac.x), frac.y);', 747 | 748 | ' source.gb = source.gb * 0.5 + step(0.0, -source.gb);', 749 | 750 | ' gl_FragColor = source;', 751 | 752 | '}' 753 | 754 | ].join( "\n" ) 755 | }; 756 | 757 | 758 | THREE.Fire.ProjectionShader1 = { 759 | 760 | uniforms: { 761 | 'oneOverWidth': { 762 | type: 'f', 763 | value: null 764 | }, 765 | 'oneOverHeight': { 766 | type: 'f', 767 | value: null 768 | }, 769 | 'densityMap': { 770 | type: 't', 771 | value: null 772 | } 773 | }, 774 | 775 | vertexShader: [ 776 | 'varying vec2 vUv;', 777 | 778 | 'void main() {', 779 | 780 | ' vUv = uv;', 781 | 782 | ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );', 783 | ' gl_Position = projectionMatrix * mvPosition;', 784 | 785 | '}' 786 | 787 | ].join( "\n" ), 788 | 789 | fragmentShader: [ 790 | 'uniform float oneOverWidth;', 791 | 'uniform float oneOverHeight;', 792 | 'uniform sampler2D densityMap;', 793 | 794 | 'varying vec2 vUv;', 795 | 796 | 'void main() {', 797 | ' float dL = texture2D( densityMap, vec2(vUv.x - oneOverWidth, vUv.y) ).g;', 798 | ' float dR = texture2D( densityMap, vec2(vUv.x + oneOverWidth, vUv.y) ).g;', 799 | ' float dU = texture2D( densityMap, vec2(vUv.x, vUv.y - oneOverHeight) ).b;', 800 | ' float dD = texture2D( densityMap, vec2(vUv.x, vUv.y + oneOverHeight) ).b;', 801 | 802 | ' dL = (dL - step(0.5, dL)) * 2.0;', 803 | ' dR = (dR - step(0.5, dR)) * 2.0;', 804 | ' dU = (dU - step(0.5, dU)) * 2.0;', 805 | ' dD = (dD - step(0.5, dD)) * 2.0;', 806 | 807 | ' float h = (oneOverWidth + oneOverHeight) * 0.5;', 808 | ' float div = -0.5 * h * (dR - dL + dD - dU);', 809 | 810 | ' gl_FragColor = vec4( 0.0, 0.0, div * 0.5 + step(0.0, -div), 0.0);', 811 | 812 | '}' 813 | 814 | ].join( "\n" ) 815 | }; 816 | 817 | 818 | THREE.Fire.ProjectionShader2 = { 819 | 820 | uniforms: { 821 | 'oneOverWidth': { 822 | type: 'f', 823 | value: null 824 | }, 825 | 'oneOverHeight': { 826 | type: 'f', 827 | value: null 828 | }, 829 | 'densityMap': { 830 | type: 't', 831 | value: null 832 | } 833 | }, 834 | 835 | vertexShader: [ 836 | 'varying vec2 vUv;', 837 | 838 | 'void main() {', 839 | 840 | ' vUv = uv;', 841 | 842 | ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );', 843 | ' gl_Position = projectionMatrix * mvPosition;', 844 | 845 | '}' 846 | 847 | ].join( "\n" ), 848 | 849 | fragmentShader: [ 850 | 'uniform float oneOverWidth;', 851 | 'uniform float oneOverHeight;', 852 | 'uniform sampler2D densityMap;', 853 | 854 | 'varying vec2 vUv;', 855 | 856 | 'void main() {', 857 | ' float div = texture2D( densityMap, vUv ).b;', 858 | ' float pL = texture2D( densityMap, vec2(vUv.x - oneOverWidth, vUv.y) ).g;', 859 | ' float pR = texture2D( densityMap, vec2(vUv.x + oneOverWidth, vUv.y) ).g;', 860 | ' float pU = texture2D( densityMap, vec2(vUv.x, vUv.y - oneOverHeight) ).g;', 861 | ' float pD = texture2D( densityMap, vec2(vUv.x, vUv.y + oneOverHeight) ).g;', 862 | 863 | ' float divNorm = (div - step(0.5, div)) * 2.0;', 864 | ' pL = (pL - step(0.5, pL)) * 2.0;', 865 | ' pR = (pR - step(0.5, pR)) * 2.0;', 866 | ' pU = (pU - step(0.5, pU)) * 2.0;', 867 | ' pD = (pD - step(0.5, pD)) * 2.0;', 868 | 869 | ' float p = (divNorm + pR + pL + pD + pU) * 0.25;', 870 | 871 | ' gl_FragColor = vec4( 0.0, p * 0.5 + step(0.0, -p), div, 0.0);', 872 | 873 | '}' 874 | 875 | ].join( "\n" ) 876 | }; 877 | 878 | 879 | THREE.Fire.ProjectionShader3 = { 880 | 881 | uniforms: { 882 | 'oneOverWidth': { 883 | type: 'f', 884 | value: null 885 | }, 886 | 'oneOverHeight': { 887 | type: 'f', 888 | value: null 889 | }, 890 | 'densityMap': { 891 | type: 't', 892 | value: null 893 | }, 894 | 'projMap': { 895 | type: 't', 896 | value: null 897 | } 898 | }, 899 | 900 | vertexShader: [ 901 | 'varying vec2 vUv;', 902 | 903 | 'void main() {', 904 | 905 | ' vUv = uv;', 906 | 907 | ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );', 908 | ' gl_Position = projectionMatrix * mvPosition;', 909 | 910 | '}' 911 | 912 | ].join( "\n" ), 913 | 914 | fragmentShader: [ 915 | 'uniform float oneOverWidth;', 916 | 'uniform float oneOverHeight;', 917 | 'uniform sampler2D densityMap;', 918 | 'uniform sampler2D projMap;', 919 | 920 | 'varying vec2 vUv;', 921 | 922 | 'void main() {', 923 | ' vec4 orig = texture2D(densityMap, vUv);', 924 | 925 | ' float pL = texture2D( projMap, vec2(vUv.x - oneOverWidth, vUv.y) ).g;', 926 | ' float pR = texture2D( projMap, vec2(vUv.x + oneOverWidth, vUv.y) ).g;', 927 | ' float pU = texture2D( projMap, vec2(vUv.x, vUv.y - oneOverHeight) ).g;', 928 | ' float pD = texture2D( projMap, vec2(vUv.x, vUv.y + oneOverHeight) ).g;', 929 | 930 | ' float uNorm = (orig.g - step(0.5, orig.g)) * 2.0;', 931 | ' float vNorm = (orig.b - step(0.5, orig.b)) * 2.0;', 932 | 933 | ' pL = (pL - step(0.5, pL)) * 2.0;', 934 | ' pR = (pR - step(0.5, pR)) * 2.0;', 935 | ' pU = (pU - step(0.5, pU)) * 2.0;', 936 | ' pD = (pD - step(0.5, pD)) * 2.0;', 937 | 938 | ' float h = (oneOverWidth + oneOverHeight) * 0.5;', 939 | ' float u = uNorm - (0.5 * (pR - pL) / h);', 940 | ' float v = vNorm - (0.5 * (pD - pU) / h);', 941 | 942 | ' gl_FragColor = vec4( orig.r, u * 0.5 + step(0.0, -u), v * 0.5 + step(0.0, -v), orig.a);', 943 | 944 | '}' 945 | 946 | ].join( "\n" ) 947 | }; 948 | 949 | THREE.Fire.ColorShader = { 950 | 951 | uniforms: { 952 | 'color1': { 953 | type: 'c', 954 | value: null 955 | }, 956 | 'color2': { 957 | type: 'c', 958 | value: null 959 | }, 960 | 'color3': { 961 | type: 'c', 962 | value: null 963 | }, 964 | 'colorBias': { 965 | type: 'f', 966 | value: null 967 | }, 968 | 'densityMap': { 969 | type: 't', 970 | value: null 971 | } 972 | }, 973 | 974 | vertexShader: [ 975 | 'varying vec2 vUv;', 976 | 977 | 'void main() {', 978 | 979 | ' vUv = uv;', 980 | 981 | ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );', 982 | ' gl_Position = projectionMatrix * mvPosition;', 983 | 984 | '}' 985 | 986 | ].join( "\n" ), 987 | 988 | fragmentShader: [ 989 | 'uniform vec3 color1;', 990 | 'uniform vec3 color2;', 991 | 'uniform vec3 color3;', 992 | 'uniform float colorBias;', 993 | 'uniform sampler2D densityMap;', 994 | 995 | 'varying vec2 vUv;', 996 | 997 | 'void main() {', 998 | ' float density = texture2D( densityMap, vUv ).a;', 999 | ' float temperature = texture2D( densityMap, vUv ).r;', 1000 | 1001 | ' float bias = clamp(colorBias, 0.0001, 0.9999);', 1002 | 1003 | ' vec3 blend1 = mix(color3, color2, temperature / bias) * (1.0 - step(bias, temperature));', 1004 | ' vec3 blend2 = mix(color2, color1, (temperature - bias) / (1.0 - bias) ) * step(bias, temperature);', 1005 | 1006 | ' gl_FragColor = vec4(blend1 + blend2, density);', 1007 | '}' 1008 | 1009 | ].join( "\n" ) 1010 | }; 1011 | 1012 | 1013 | THREE.Fire.DebugShader = { 1014 | 1015 | uniforms: { 1016 | 'color1': { 1017 | type: 'c', 1018 | value: null 1019 | }, 1020 | 'color2': { 1021 | type: 'c', 1022 | value: null 1023 | }, 1024 | 'color3': { 1025 | type: 'c', 1026 | value: null 1027 | }, 1028 | 'colorBias': { 1029 | type: 'f', 1030 | value: null 1031 | }, 1032 | 'densityMap': { 1033 | type: 't', 1034 | value: null 1035 | } 1036 | }, 1037 | 1038 | vertexShader: [ 1039 | 'varying vec2 vUv;', 1040 | 1041 | 'void main() {', 1042 | 1043 | ' vUv = uv;', 1044 | 1045 | ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );', 1046 | ' gl_Position = projectionMatrix * mvPosition;', 1047 | 1048 | '}' 1049 | 1050 | ].join( "\n" ), 1051 | 1052 | fragmentShader: [ 1053 | 'uniform sampler2D densityMap;', 1054 | 1055 | 'varying vec2 vUv;', 1056 | 1057 | 'void main() {', 1058 | ' float density;', 1059 | ' density = texture2D( densityMap, vUv ).a;', 1060 | 1061 | ' vec2 vel = texture2D( densityMap, vUv ).gb;', 1062 | 1063 | ' vel = (vel - step(0.5, vel)) * 2.0;', 1064 | 1065 | ' float r = density;', 1066 | ' float g = max(abs(vel.x), density * 0.5);', 1067 | ' float b = max(abs(vel.y), density * 0.5);', 1068 | ' float a = max(density * 0.5, max(abs(vel.x), abs(vel.y)));', 1069 | 1070 | ' gl_FragColor = vec4(r, g, b, a);', 1071 | 1072 | '}' 1073 | 1074 | ].join( "\n" ) 1075 | }; 1076 | -------------------------------------------------------------------------------- /src/MTLLoaderPhysical.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Loads a Wavefront .mtl file specifying materials 3 | * 4 | * @author angelxuanchang 5 | */ 6 | 7 | THREE.MTLLoader = function(manager) { 8 | this.manager = 9 | manager !== undefined ? manager : THREE.DefaultLoadingManager; 10 | }; 11 | 12 | THREE.MTLLoader.prototype = { 13 | constructor: THREE.MTLLoader, 14 | 15 | /** 16 | * Loads and parses a MTL asset from a URL. 17 | * 18 | * @param {String} url - URL to the MTL file. 19 | * @param {Function} [onLoad] - Callback invoked with the loaded object. 20 | * @param {Function} [onProgress] - Callback for download progress. 21 | * @param {Function} [onError] - Callback for download errors. 22 | * 23 | * @see setPath setResourcePath 24 | * 25 | * @note In order for relative texture references to resolve correctly 26 | * you must call setResourcePath() explicitly prior to load. 27 | */ 28 | load: function(url, onLoad, onProgress, onError) { 29 | var scope = this; 30 | 31 | var path = 32 | this.path === undefined 33 | ? THREE.LoaderUtils.extractUrlBase(url) 34 | : this.path; 35 | 36 | var loader = new THREE.FileLoader(this.manager); 37 | loader.setPath(this.path); 38 | loader.load( 39 | url, 40 | function(text) { 41 | onLoad(scope.parse(text, path)); 42 | }, 43 | onProgress, 44 | onError 45 | ); 46 | }, 47 | 48 | /** 49 | * Set base path for resolving references. 50 | * If set this path will be prepended to each loaded and found reference. 51 | * 52 | * @see setResourcePath 53 | * @param {String} path 54 | * @return {THREE.MTLLoader} 55 | * 56 | * @example 57 | * mtlLoader.setPath( 'assets/obj/' ); 58 | * mtlLoader.load( 'my.mtl', ... ); 59 | */ 60 | setPath: function(path) { 61 | this.path = path; 62 | return this; 63 | }, 64 | 65 | /** 66 | * Set base path for additional resources like textures. 67 | * 68 | * @see setPath 69 | * @param {String} path 70 | * @return {THREE.MTLLoader} 71 | * 72 | * @example 73 | * mtlLoader.setPath( 'assets/obj/' ); 74 | * mtlLoader.setResourcePath( 'assets/textures/' ); 75 | * mtlLoader.load( 'my.mtl', ... ); 76 | */ 77 | setResourcePath: function(path) { 78 | this.resourcePath = path; 79 | return this; 80 | }, 81 | 82 | setTexturePath: function(path) { 83 | console.warn( 84 | "THREE.MTLLoader: .setTexturePath() has been renamed to .setResourcePath()." 85 | ); 86 | return this.setResourcePath(path); 87 | }, 88 | 89 | setCrossOrigin: function(value) { 90 | this.crossOrigin = value; 91 | return this; 92 | }, 93 | 94 | setMaterialOptions: function(value) { 95 | this.materialOptions = value; 96 | return this; 97 | }, 98 | 99 | /** 100 | * Parses a MTL file. 101 | * 102 | * @param {String} text - Content of MTL file 103 | * @return {THREE.MTLLoader.MaterialCreator} 104 | * 105 | * @see setPath setResourcePath 106 | * 107 | * @note In order for relative texture references to resolve correctly 108 | * you must call setResourcePath() explicitly prior to parse. 109 | */ 110 | parse: function(text, path) { 111 | var lines = text.split("\n"); 112 | var info = {}; 113 | var delimiter_pattern = /\s+/; 114 | var materialsInfo = {}; 115 | 116 | for (var i = 0; i < lines.length; i++) { 117 | var line = lines[i]; 118 | line = line.trim(); 119 | 120 | if (line.length === 0 || line.charAt(0) === "#") { 121 | // Blank line or comment ignore 122 | continue; 123 | } 124 | 125 | var pos = line.indexOf(" "); 126 | 127 | var key = pos >= 0 ? line.substring(0, pos) : line; 128 | key = key.toLowerCase(); 129 | 130 | var value = pos >= 0 ? line.substring(pos + 1) : ""; 131 | value = value.trim(); 132 | 133 | if (key === "newmtl") { 134 | // New material 135 | 136 | info = { name: value }; 137 | materialsInfo[value] = info; 138 | } else { 139 | if ( 140 | key === "ka" || 141 | key === "kd" || 142 | key === "ks" || 143 | key === "ke" 144 | ) { 145 | var ss = value.split(delimiter_pattern, 3); 146 | info[key] = [ 147 | parseFloat(ss[0]), 148 | parseFloat(ss[1]), 149 | parseFloat(ss[2]) 150 | ]; 151 | } else { 152 | info[key] = value; 153 | } 154 | } 155 | } 156 | 157 | var materialCreator = new THREE.MTLLoader.MaterialCreator( 158 | this.resourcePath || path, 159 | this.materialOptions 160 | ); 161 | materialCreator.setCrossOrigin(this.crossOrigin); 162 | materialCreator.setManager(this.manager); 163 | materialCreator.setMaterials(materialsInfo); 164 | return materialCreator; 165 | } 166 | }; 167 | 168 | /** 169 | * Create a new THREE-MTLLoader.MaterialCreator 170 | * @param baseUrl - Url relative to which textures are loaded 171 | * @param options - Set of options on how to construct the materials 172 | * side: Which side to apply the material 173 | * THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide 174 | * wrap: What type of wrapping to apply for textures 175 | * THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping 176 | * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255 177 | * Default: false, assumed to be already normalized 178 | * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's 179 | * Default: false 180 | * @constructor 181 | */ 182 | 183 | THREE.MTLLoader.MaterialCreator = function(baseUrl, options) { 184 | this.baseUrl = baseUrl || ""; 185 | this.options = options; 186 | this.materialsInfo = {}; 187 | this.materials = {}; 188 | this.materialsArray = []; 189 | this.nameLookup = {}; 190 | 191 | this.side = 192 | this.options && this.options.side ? this.options.side : THREE.FrontSide; 193 | this.wrap = 194 | this.options && this.options.wrap 195 | ? this.options.wrap 196 | : THREE.RepeatWrapping; 197 | }; 198 | 199 | THREE.MTLLoader.MaterialCreator.prototype = { 200 | constructor: THREE.MTLLoader.MaterialCreator, 201 | 202 | crossOrigin: "anonymous", 203 | 204 | setCrossOrigin: function(value) { 205 | this.crossOrigin = value; 206 | return this; 207 | }, 208 | 209 | setManager: function(value) { 210 | this.manager = value; 211 | }, 212 | 213 | setMaterials: function(materialsInfo) { 214 | this.materialsInfo = this.convert(materialsInfo); 215 | this.materials = {}; 216 | this.materialsArray = []; 217 | this.nameLookup = {}; 218 | }, 219 | 220 | convert: function(materialsInfo) { 221 | if (!this.options) return materialsInfo; 222 | 223 | var converted = {}; 224 | 225 | for (var mn in materialsInfo) { 226 | // Convert materials info into normalized form based on options 227 | 228 | var mat = materialsInfo[mn]; 229 | 230 | var covmat = {}; 231 | 232 | converted[mn] = covmat; 233 | 234 | for (var prop in mat) { 235 | var save = true; 236 | var value = mat[prop]; 237 | var lprop = prop.toLowerCase(); 238 | 239 | switch (lprop) { 240 | case "kd": 241 | case "ka": 242 | case "ks": 243 | // Diffuse color (color under white light) using RGB values 244 | 245 | if (this.options && this.options.normalizeRGB) { 246 | value = [ 247 | value[0] / 255, 248 | value[1] / 255, 249 | value[2] / 255 250 | ]; 251 | } 252 | 253 | if (this.options && this.options.ignoreZeroRGBs) { 254 | if ( 255 | value[0] === 0 && 256 | value[1] === 0 && 257 | value[2] === 0 258 | ) { 259 | // ignore 260 | 261 | save = false; 262 | } 263 | } 264 | 265 | break; 266 | 267 | default: 268 | break; 269 | } 270 | 271 | if (save) { 272 | covmat[lprop] = value; 273 | } 274 | } 275 | } 276 | 277 | return converted; 278 | }, 279 | 280 | preload: function() { 281 | for (var mn in this.materialsInfo) { 282 | this.create(mn); 283 | } 284 | }, 285 | 286 | getIndex: function(materialName) { 287 | return this.nameLookup[materialName]; 288 | }, 289 | 290 | getAsArray: function() { 291 | var index = 0; 292 | 293 | for (var mn in this.materialsInfo) { 294 | this.materialsArray[index] = this.create(mn); 295 | this.nameLookup[mn] = index; 296 | index++; 297 | } 298 | 299 | return this.materialsArray; 300 | }, 301 | 302 | create: function(materialName) { 303 | if (this.materials[materialName] === undefined) { 304 | this.createMaterial_(materialName); 305 | } 306 | 307 | return this.materials[materialName]; 308 | }, 309 | 310 | createMaterial_: function(materialName) { 311 | // Create material 312 | 313 | var scope = this; 314 | var mat = this.materialsInfo[materialName]; 315 | var params = { 316 | name: materialName, 317 | side: this.side 318 | }; 319 | 320 | function resolveURL(baseUrl, url) { 321 | if (typeof url !== "string" || url === "") return ""; 322 | 323 | // Absolute URL 324 | if (/^https?:\/\//i.test(url)) return url; 325 | 326 | return baseUrl + url; 327 | } 328 | 329 | function setMapForType(mapType, value) { 330 | if (params[mapType]) return; // Keep the first encountered texture 331 | 332 | var texParams = scope.getTextureParams(value, params); 333 | var map = scope.loadTexture( 334 | resolveURL(scope.baseUrl, texParams.url) 335 | ); 336 | 337 | map.repeat.copy(texParams.scale); 338 | map.offset.copy(texParams.offset); 339 | 340 | map.wrapS = scope.wrap; 341 | map.wrapT = scope.wrap; 342 | 343 | params[mapType] = map; 344 | } 345 | 346 | for (var prop in mat) { 347 | var value = mat[prop]; 348 | var n; 349 | 350 | if (value === "") continue; 351 | 352 | switch (prop.toLowerCase()) { 353 | // Ns is material specular exponent 354 | 355 | case "kd": 356 | // Diffuse color (color under white light) using RGB values 357 | 358 | params.color = new THREE.Color().fromArray(value); 359 | 360 | break; 361 | 362 | case "ks": 363 | // Specular color (color when light is reflected from shiny surface) using RGB values 364 | // params.specular = new THREE.Color().fromArray(value); 365 | 366 | break; 367 | 368 | case "ke": 369 | // Emissive using RGB values 370 | params.emissive = new THREE.Color().fromArray(value); 371 | 372 | break; 373 | 374 | case "map_kd": 375 | // Diffuse texture map 376 | 377 | setMapForType("map", value); 378 | 379 | break; 380 | 381 | case "map_ks": 382 | // Specular map 383 | 384 | setMapForType("specularMap", value); 385 | 386 | break; 387 | 388 | case "map_ke": 389 | // Emissive map 390 | 391 | setMapForType("emissiveMap", value); 392 | 393 | break; 394 | 395 | case "norm": 396 | setMapForType("normalMap", value); 397 | 398 | break; 399 | 400 | case "map_bump": 401 | case "bump": 402 | // Bump texture map 403 | 404 | setMapForType("bumpMap", value); 405 | 406 | break; 407 | 408 | case "map_d": 409 | // Alpha map 410 | 411 | setMapForType("alphaMap", value); 412 | params.transparent = true; 413 | 414 | break; 415 | 416 | case "ns": 417 | // The specular exponent (defines the focus of the specular highlight) 418 | // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000. 419 | 420 | params.reflectivity = parseFloat(value) / 100; 421 | 422 | break; 423 | 424 | case "d": 425 | n = parseFloat(value); 426 | 427 | if (n < 1) { 428 | params.opacity = n; 429 | params.transparent = true; 430 | } 431 | 432 | break; 433 | 434 | case "tr": 435 | n = parseFloat(value); 436 | 437 | if (this.options && this.options.invertTrProperty) 438 | n = 1 - n; 439 | 440 | if (n > 0) { 441 | params.opacity = 1 - n; 442 | params.transparent = true; 443 | } 444 | 445 | break; 446 | 447 | default: 448 | break; 449 | } 450 | } 451 | 452 | this.materials[materialName] = new THREE.MeshPhysicalMaterial(params); 453 | return this.materials[materialName]; 454 | }, 455 | 456 | getTextureParams: function(value, matParams) { 457 | var texParams = { 458 | scale: new THREE.Vector2(1, 1), 459 | offset: new THREE.Vector2(0, 0) 460 | }; 461 | 462 | var items = value.split(/\s+/); 463 | var pos; 464 | 465 | pos = items.indexOf("-bm"); 466 | 467 | if (pos >= 0) { 468 | matParams.bumpScale = parseFloat(items[pos + 1]); 469 | items.splice(pos, 2); 470 | } 471 | 472 | pos = items.indexOf("-s"); 473 | 474 | if (pos >= 0) { 475 | texParams.scale.set( 476 | parseFloat(items[pos + 1]), 477 | parseFloat(items[pos + 2]) 478 | ); 479 | items.splice(pos, 4); // we expect 3 parameters here! 480 | } 481 | 482 | pos = items.indexOf("-o"); 483 | 484 | if (pos >= 0) { 485 | texParams.offset.set( 486 | parseFloat(items[pos + 1]), 487 | parseFloat(items[pos + 2]) 488 | ); 489 | items.splice(pos, 4); // we expect 3 parameters here! 490 | } 491 | 492 | texParams.url = items.join(" ").trim(); 493 | return texParams; 494 | }, 495 | 496 | loadTexture: function(url, mapping, onLoad, onProgress, onError) { 497 | var texture; 498 | var loader = THREE.Loader.Handlers.get(url); 499 | var manager = 500 | this.manager !== undefined 501 | ? this.manager 502 | : THREE.DefaultLoadingManager; 503 | 504 | if (loader === null) { 505 | loader = new THREE.TextureLoader(manager); 506 | } 507 | 508 | if (loader.setCrossOrigin) loader.setCrossOrigin(this.crossOrigin); 509 | texture = loader.load(url, onLoad, onProgress, onError); 510 | 511 | if (mapping !== undefined) texture.mapping = mapping; 512 | 513 | return texture; 514 | } 515 | }; 516 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import STATES from "./states.js"; 2 | import keyCodes from "./keycodes.js"; 3 | import consoleCanvas from "./console-canvas.js"; 4 | import * as cmds from "./cmds.js"; 5 | import config from "./config.js"; 6 | 7 | // create some handy aliases for keycodes, for use with Vue's v-on directive. 8 | Vue.config.keyCodes = { 9 | enter: keyCodes.enter 10 | }; 11 | 12 | let ctrl_down = false; 13 | 14 | /** 15 | * @param {Number} kc the keyCode of the key pressed 16 | * @param {Array} leftChars the character to the left of the cursor, used to 17 | * determine whether left arrow is valid (left arrow can't cross over a 18 | * newline) 19 | */ 20 | function validKeycode(ev, leftChars, state) { 21 | const kc = ev.keyCode; 22 | 23 | // if ctrl is held down, ignore everything 24 | if (kc == keyCodes.ctrl) { 25 | ctrl_down = true; 26 | } 27 | 28 | if (ctrl_down) { 29 | return false; 30 | } 31 | 32 | // valid keys are alpha, numeric, punctuation, underscore, hyphen, enter, and right-arrow. 33 | // left-arrow and backspace areonly accepted when they doesn't cross over a newline 34 | // (ie, would have made the cursor to up one line). 35 | const alphanumeric = 36 | _.inRange(kc, keyCodes.nums.start, keyCodes.nums.end + 1) || 37 | _.inRange(kc, keyCodes.alpha.start, keyCodes.alpha.end + 1) || 38 | _.inRange(kc, keyCodes.punct.start, keyCodes.punct.end + 1); 39 | 40 | const valid_other = [keyCodes.enter, keyCodes.right_arrow].includes(kc); 41 | 42 | const on_newline = leftChars[0] === "\n"; 43 | const on_prompt = leftChars.reverse().join("") === "\n> "; 44 | const valid_backspace = 45 | kc === keyCodes.backspace && !(on_newline || on_prompt); 46 | 47 | // Allow spaces when people enter their name on high score list 48 | const valid_space = kc === keyCodes.space && state === STATES.highscore; 49 | 50 | return alphanumeric || valid_other || valid_backspace || valid_space; 51 | } 52 | 53 | const app = new Vue({ 54 | el: "#game", 55 | data: { 56 | state: STATES.loading, 57 | isMobile: isMobile.any, 58 | showTitle: false, 59 | showScore: false, 60 | cmd: "", 61 | typingPosition: 0, 62 | displayCmd: "", 63 | commands: [], 64 | displayScore: false, 65 | gameDuration: config.GAME_DURATION, 66 | timer: 0, 67 | allowTyping: false, 68 | score: 0, 69 | count: { 70 | js: 0, 71 | bash: 0, 72 | html: 0, 73 | py: 0, 74 | recentValidCharacters: 0, 75 | totalValidCharacters: 0, 76 | totalValidCommands: 0 77 | } 78 | }, 79 | watch: { 80 | displayCmd: function(val, oldVal) { 81 | // if receiving user input and on a newline, add a prompt to the main cmd 82 | if (this.allowTyping && val[val.length - 1] === "\n") { 83 | this.cmd += "> "; 84 | } 85 | }, 86 | cmd: function(val, oldVal) { 87 | // if typing is enabled, copy this directly into displayCmd and update typingPosition 88 | if (this.allowTyping) { 89 | this.displayCmd = _.clone(this.cmd); 90 | this.typingPosition = this.cmd.length; 91 | } 92 | // if the screen was blanked out, reset typing position 93 | if (!this.cmd.includes(oldVal)) { 94 | this.typingPosition = 0; 95 | } 96 | } 97 | }, 98 | methods: { 99 | toState: function(state) { 100 | const change = { from: this.state, to: state }; 101 | this.state = state; 102 | this.titleState = state === STATES.title; 103 | this.onStateChange(change); 104 | }, 105 | handlePaste: function(ev) { 106 | // disable pasting into the textarea 107 | ev.preventDefault(); 108 | }, 109 | // this keypress handler can be overridden and changed based on the state of the game. 110 | onKeyPress: _.noop, 111 | // this keypress handler is the primary one which controls interaction with the textarea. 112 | handleKeypress: function(ev) { 113 | // give onKeyPress first crack at this event 114 | this.onKeyPress(ev); 115 | 116 | if (!this.allowTyping) { 117 | ev.preventDefault(); 118 | return; 119 | } 120 | // first get the char to the left of the cursor (it's used when 121 | // left arrow is pressed to determine if left arrow is valid; left 122 | // arrow is valid except when it would cross over a newline and 123 | // move the cursor to the line above) 124 | const textarea = this.$el.querySelector("#cmd"); 125 | const leftChars = [ 126 | this.cmd[textarea.selectionStart - 1], 127 | this.cmd[textarea.selectionStart - 2], 128 | this.cmd[textarea.selectionStart - 3] 129 | ]; 130 | 131 | // if it's enter, test the input and return. also, preventDefault 132 | // so enter doesn't add a newline. Instead, add the newline 133 | // ourselves. This prevents Enter from splitting a word in half if 134 | // the cursor is inside a word, like hitting enter on "ca|t" would 135 | // result in "ca\nt". 136 | if (ev.keyCode === Vue.config.keyCodes.enter) { 137 | ev.preventDefault(); 138 | const result = this.testCmd(ev); 139 | result.lang.forEach(lang => app.count[lang]++); 140 | 141 | if (result.cmd.length !== 0) { 142 | // scroll to bottom of the textarea 143 | // gameplay, it just makes the textarea look nicer when the 144 | // textarea itself is visible during debugging) 145 | this.$nextTick(() => { 146 | textarea.blur(); 147 | textarea.focus(); 148 | }); 149 | } 150 | return; 151 | } 152 | 153 | // if keycode is invalid, drop the event. 154 | if (!validKeycode(ev, leftChars, this.state)) { 155 | ev.preventDefault(); 156 | } 157 | }, 158 | handleKeyup: function(ev) { 159 | if (ev.keyCode === keyCodes.ctrl) { 160 | ctrl_down = false; 161 | } 162 | }, 163 | testCmd: function(ev) { 164 | const cmd = _(this.cmd) 165 | .split("\n") 166 | .last() 167 | .trim() 168 | .replace(/^\> /, ""); // ignore the prompt 169 | const { cmd: matchedCmd, lang } = cmds.find(cmd); 170 | const result = { cmd, valid: !!matchedCmd, matchedCmd, lang }; 171 | this.$nextTick(() => { 172 | this.onResult(result); 173 | }); 174 | return result; 175 | }, 176 | onResult: _.noop, 177 | onStateChange: function(change) { 178 | console.log( 179 | `state changing from "${change.from}" to "${ 180 | change.to 181 | }" but no handler is registered.` 182 | ); 183 | }, 184 | 185 | /** 186 | * This function returns a json object with the set of golden command for this game 187 | */ 188 | pickGoldenCommands: function() { 189 | // General rules for golden commands 190 | // 1. 10 char or less 191 | // 2. don't start with _ 192 | // 3. don't end with () 193 | // 4. Pull from a list of well known commands for each lang 194 | // 5. pick configurable amount of commands from each language type that meet the above rules 195 | 196 | const filterCmds = function(cmds) { 197 | // filter by length 198 | let filteredCmds = cmds.filter( 199 | cmd => cmd.length <= config.GOLDEN_CMDS_MAX_LENGTH 200 | ); 201 | 202 | // Filter out starting with underscore 203 | filteredCmds = filteredCmds.filter(cmd => !cmd.startsWith("_")); 204 | 205 | // Filter out ending with parens ) 206 | filteredCmds = filteredCmds.filter(cmd => !cmd.endsWith(")")); 207 | 208 | return filteredCmds; 209 | }; 210 | 211 | let bashAll = filterCmds(cmds.cmdsByLang.bash.cmds); 212 | let bashCommon = cmds.cmdsByLang.bash.commonCmds; 213 | let jsAll = filterCmds(cmds.cmdsByLang.js.cmds); 214 | let jsCommon = cmds.cmdsByLang.js.commonCmds; 215 | let pyAll = filterCmds(cmds.cmdsByLang.py.cmds); 216 | let pyCommon = cmds.cmdsByLang.py.commonCmds; 217 | let htmlAll = filterCmds(cmds.cmdsByLang.html.cmds); 218 | let htmlCommon = filterCmds(cmds.cmdsByLang.html.commonCmds); 219 | 220 | let cn = config.GOLDEN_CMDS_COMMON_PER_LANG; 221 | let rn = config.GOLDEN_CMDS_RANDOM_PER_LANG; 222 | 223 | let goldenCommands = { 224 | bash: _.sampleSize(bashCommon, cn).concat( 225 | _.sampleSize(_.xor(bashCommon, bashAll), rn) 226 | ), 227 | js: _.sampleSize(jsCommon, cn).concat( 228 | _.sampleSize(_.xor(jsCommon, jsAll), rn) 229 | ), 230 | py: _.sampleSize(pyCommon, cn).concat( 231 | _.sampleSize(_.xor(pyCommon, pyAll), rn) 232 | ), 233 | html: _.sampleSize(htmlCommon, cn).concat( 234 | _.sampleSize(_.xor(htmlCommon, htmlAll), rn) 235 | ) 236 | }; 237 | goldenCommands.all = goldenCommands.bash.concat( 238 | goldenCommands.js, 239 | goldenCommands.py, 240 | goldenCommands.html 241 | ); 242 | 243 | return goldenCommands; 244 | }, 245 | /** 246 | * Get the golden commands for the console canvas. 247 | */ 248 | printGoldenCommands: function() { 249 | let out = ""; 250 | 251 | const halfScreen = Math.floor( 252 | consoleCanvas.conf.PLAY_CHARS_PER_LINE / 2 253 | ); 254 | const goldCmds = app.goldenCommands; 255 | const langs = _.keys(goldCmds); 256 | 257 | // title of first and second langs 258 | out += cmds.bash().name.padEnd(halfScreen); 259 | out += cmds.js().name + "\n"; 260 | 261 | // interleave commands of first and second langs 262 | out += _.zip( 263 | goldCmds.bash.map(c => ` - ${c}`.padEnd(halfScreen)), 264 | goldCmds.js.map(c => `${` - ${c}`.padEnd(halfScreen)}\n`) 265 | ) 266 | .map(cs => cs.join("")) 267 | .join(""); 268 | 269 | out += "\n"; 270 | 271 | // title of third and fourth langs 272 | out += cmds 273 | .py() 274 | .name.padEnd( 275 | Math.floor(consoleCanvas.conf.PLAY_CHARS_PER_LINE / 2) 276 | ); 277 | out += cmds.html().name + "\n"; 278 | 279 | // interleave commands of third and fourth langs 280 | out += _.zip( 281 | goldCmds.py.map(c => ` - ${c}`.padEnd(halfScreen)), 282 | goldCmds.html.map(c => `${` - ${c}`.padEnd(halfScreen)}\n`) 283 | ) 284 | .map(cs => cs.join("")) 285 | .join(""); 286 | 287 | return out; 288 | }, 289 | printHighScores: function(leaders) { 290 | if (leaders.isEmpty) { 291 | return ""; 292 | } 293 | 294 | let out = ""; 295 | 296 | // Only display the top 10 leaders 297 | leaders = leaders.slice(0, 10); 298 | 299 | // inject headings 300 | let leaderContent = _.concat( 301 | { name: "NAME", score: "SCORE", tribe: "TRIBE" }, 302 | { name: "----", score: "-----", tribe: "-----" }, 303 | leaders 304 | ); 305 | 306 | let longestScoreLength = leaders[0].score.toString().length; 307 | let longestTribeLength = _(leaderContent) 308 | .map("tribe") 309 | .maxBy(n => n.length).length; 310 | 311 | leaderContent.forEach(leader => { 312 | // pad for column formatting 313 | let score = leader.score 314 | .toString() 315 | .padEnd(longestScoreLength + 1); 316 | let tribe = leader.tribe.padEnd(longestTribeLength + 1); 317 | 318 | out += `${score} ${tribe} ${leader.name}\n`; 319 | }); 320 | 321 | return out; 322 | }, 323 | updateConsole: _.noop, 324 | writeToConsole: function() { 325 | this.$nextTick(() => { 326 | let args = [_.clone(this.displayCmd)]; 327 | const showCursor = 328 | this.allowTyping && performance.now() % 1200 < 600; 329 | if (showCursor) { 330 | args[0] += "█"; 331 | } 332 | if (this.showScore) { 333 | args.push(this.score); 334 | args.push(this.timer); 335 | } 336 | consoleCanvas.write(...args); 337 | }); 338 | }, 339 | typingLoop: function() { 340 | let delay = this.typingTimeChar( 341 | this.displayCmd[this.displayCmd - 1] 342 | ); 343 | 344 | if (!this.allowTyping) { 345 | this.displayCmd = this.cmd.substr(0, this.typingPosition); 346 | } 347 | // increment typing position but don't exceed the length of cmd 348 | this.typingPosition = Math.min( 349 | this.typingPosition + 1, 350 | this.cmd.length 351 | ); 352 | 353 | setTimeout(this.typingLoop, delay); 354 | }, 355 | // how long will it take to display the given character 356 | typingTimeChar: function(str) { 357 | let delay = config.CHAR_APPEAR_DELAY; 358 | 359 | if (/\s/.test(this.displayCmd[this.displayCmd.length - 1])) { 360 | delay /= 10; 361 | } 362 | 363 | return delay; 364 | }, 365 | // how long will it take to display the given string 366 | typingTime: function(str) { 367 | return ( 368 | config.CHAR_APPEAR_DELAY * str.replace(/\S/g, "").length + 369 | (config.CHAR_APPEAR_DELAY / 10) * str.replace(/\s/g, "").length 370 | ); 371 | }, 372 | resetState: function() { 373 | // Reset the score and other stat between games: 374 | this.timer = 0; 375 | this.allowTyping = false; 376 | this.score = 0; 377 | this.count.js = 0; 378 | this.count.bash = 0; 379 | this.count.html = 0; 380 | this.count.py = 0; 381 | this.count.recentValidCharacters = 0; 382 | this.count.totalValidCharacters = 0; 383 | this.count.totalValidCommands = 0; 384 | } 385 | }, 386 | mounted: function() { 387 | // after the entire view has rendered 388 | this.$nextTick(function() { 389 | // put focus on the text input 390 | this.$refs.cmd.focus(); 391 | // and also refocus on the input if the user clicks anywhere with 392 | // the mouse 393 | document.body.addEventListener("click", () => 394 | this.$refs.cmd.focus() 395 | ); 396 | }); 397 | } 398 | }); 399 | 400 | window.app = app; 401 | 402 | export default app; 403 | -------------------------------------------------------------------------------- /src/cmds.js: -------------------------------------------------------------------------------- 1 | // API for interrogating the command "database" 2 | 3 | import bashCmds from "../assets/cmds/bash.js"; 4 | import jsCmds from "../assets/cmds/js.js"; 5 | import pyCmds from "../assets/cmds/python.js"; 6 | import htmlCmds from "../assets/cmds/html.js"; 7 | 8 | const allCmds = _.union( 9 | bash().cmds, 10 | js().cmds, 11 | py().cmds, 12 | html().cmds /* and other langs as needed */ 13 | ); 14 | 15 | export const cmdsByLang = { 16 | bash: bash(), 17 | js: js(), 18 | py: py(), 19 | html: html() 20 | }; 21 | 22 | export function all() { 23 | return allCmds; 24 | } 25 | 26 | export function bash() { 27 | return bashCmds; 28 | } 29 | 30 | export function js() { 31 | return jsCmds; 32 | } 33 | 34 | export function py() { 35 | return pyCmds; 36 | } 37 | 38 | export function html() { 39 | return htmlCmds; 40 | } 41 | 42 | export function longest() { 43 | return allCmds.reduce(function(a, b) { 44 | return a.length > b.length ? a : b; 45 | }).length; 46 | } 47 | 48 | export function find(cmd) { 49 | const result = { 50 | lang: [] 51 | }; 52 | for (let lang in cmdsByLang) { 53 | if (cmdsByLang[lang].cmds.includes(cmd.trim())) { 54 | result.cmd = cmd; 55 | result.lang.push(lang); 56 | } 57 | } 58 | return result; 59 | } 60 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | PARSE_URL: "http://localhost:1337/parse", 3 | PARSE_APPID: "CLH", 4 | LEADERBOARD_NAMESPACE_DEFAULT: "leaderboard", 5 | 6 | GOLDEN_CMDS_COMMON_PER_LANG: 2, // Number of very common commands to include 7 | GOLDEN_CMDS_RANDOM_PER_LANG: 1, // Number of totally random commands to include per language 8 | GOLDEN_CMDS_MAX_LENGTH: 7, // Max string length that a golden command can be 9 | GOLDEN_CMDS_PREVIEW_TIME: 21300, 10 | SCORE_PER_COMMAND: 10, 11 | SCORE_OVERALL_MULTIPLIER: 100, 12 | SCORE_GOLDEN_COMMAND_MULTIPLIER: 10, 13 | GAME_DURATION: 60000, 14 | CHAR_APPEAR_DELAY: 60, // ms between characters appearing on screen 15 | MAX_LEADER_NAME_LENGTH: 20, // max length of names on leaderboard 16 | 17 | // General Fire settings 18 | FIRE_DELAY_BEFORE: 5000, // Minimum time the game has be be running before fire can display, 19 | FIRE_CHECK_INTERVAL: 5000, // how often to check if fire should be turned up 20 | FIRE_CPS_THRESHOLD: 1.75, // Number of valid characters per-second a player must average to get fire 21 | FIRE_STAGE_ZERO: 0, 22 | FIRE_STAGE_ONE: 1, 23 | FIRE_STAGE_TWO: 2, 24 | FIRE_STAGE_THREE: 3, 25 | FIRE_STAGE_TWEEN_TIME: 2000, // how long to tween between fire stages 26 | 27 | // Default Fire properties for medium to high 28 | FIRE_COLOR_1: 0xf7cf78, 29 | FIRE_COLOR_2: 0xef702b, 30 | FIRE_COLOR_3: 0xf7a060, 31 | FIRE_WIND_VECTOR_Y: -0.25, 32 | FIRE_COLOR_BIAS: 0.25, 33 | FIRE_BURN_RATE: 2.6, 34 | FIRE_DIFFUSE: 5, 35 | FIRE_VISCOSITY: 0.5, 36 | FIRE_EXPANSION: 0.75, 37 | FIRE_SWIRL: 30, 38 | FIRE_DRAG: 0.0, 39 | FIRE_AIR_SPEED: 40.0, 40 | FIRE_SPEED: 500.0, 41 | FIRE_STAGE_ONE_SCALE: { x: 0.5, y: 0.5, z: 1 }, 42 | FIRE_STAGE_TWO_SCALE: { x: 0.7, y: 0.7, z: 1 }, 43 | FIRE_STAGE_THREE_SCALE: { x: 1, y: 1, z: 1 }, 44 | 45 | // Fire Settings low FPS clients 46 | FIRE_LOW_FPS_COLOR_1: 0xf7cf78, 47 | FIRE_LOW_FPS_COLOR_2: 0xef702b, 48 | FIRE_LOW_FPS_COLOR_3: 0x420059, 49 | FIRE_LOW_FPS_WIND_VECTOR_Y: 0.4, 50 | FIRE_LOW_FPS_COLOR_BIAS: 0.1, 51 | FIRE_LOW_FPS_BURN_RATE: 5, 52 | FIRE_LOW_FPS_DIFFUSE: 5.0, 53 | FIRE_LOW_FPS_VISCOSITY: 0.5, 54 | FIRE_LOW_FPS_EXPANSION: 0.75, 55 | FIRE_LOW_FPS_SWIRL: 30.0, 56 | FIRE_LOW_FPS_DRAG: 0.0, 57 | FIRE_LOW_FPS_AIR_SPEED: 20.0, 58 | FIRE_LOW_FPS_SPEED: 200.0, 59 | FIRE_LOW_FPS_STAGE_ONE_SCALE: { x: 0.8, y: 0.8, z: 1 }, 60 | FIRE_LOW_FPS_STAGE_TWO_SCALE: { x: 1.1, y: 1.1, z: 1 }, 61 | FIRE_LOW_FPS_STAGE_THREE_SCALE: { x: 1.4, y: 1.3, z: 1 }, 62 | 63 | MAX_FRAME_TIME: 1000 / 30, // 30 FPS 64 | MAX_SLOW_FRAMES: 300 65 | }; 66 | -------------------------------------------------------------------------------- /src/console-canvas.js: -------------------------------------------------------------------------------- 1 | import palette from "./palette.js"; 2 | 3 | class ConsoleCanvas { 4 | constructor() { 5 | this.conf = { 6 | WIDTH: 2 * 2048, 7 | HEIGHT: 2 * 2048, 8 | ASPECT: 0.7222, 9 | PAD_LEFT: 4 * 54, 10 | PAD_BOTTOM: 4 * 82, 11 | FONT_SIZE: 4 * 64, // px 12 | FONT_FAM: "overpass-mono", 13 | FONT_WEIGHT: "bold", 14 | LINE_SPACING: 4 * 14, // px 15 | PLAY_CHARS_PER_LINE: 44 // this will need to change if the font size in play mode changes 16 | }; 17 | 18 | // find the maximum number of lines of text that can be drawn (to avoid 19 | // performance problems if there are hundreds of thousands of lines 20 | this.conf.MAX_LINES = Math.ceil( 21 | this.conf.WIDTH / (this.conf.FONT_SIZE + this.conf.LINE_SPACING) 22 | ); 23 | 24 | console.log(`maximum possible display lines: ${this.conf.MAX_LINES}`); 25 | 26 | // text on the screen 27 | 28 | let text = ""; 29 | 30 | // set up canvas element 31 | 32 | this.canvas = document.createElement("canvas"); 33 | this.canvas.width = this.conf.WIDTH; 34 | this.canvas.height = this.conf.HEIGHT; 35 | this.canvas.id = "console-canvas"; 36 | 37 | // set up canvas drawing context 38 | this.ctx = this.canvas.getContext("2d"); 39 | 40 | // scale the canvas pixel sizes so that the square canvas (must be sized to 41 | // powers of two) get scaled to the correct ratio for the 3D computer screen's 42 | // size. 43 | 44 | this.ctx.scale(this.conf.ASPECT, 1); 45 | 46 | document.body.appendChild(this.canvas); 47 | } 48 | 49 | /** 50 | * Write text onto the screen. Also draws the score. If you don't want the 51 | * score to appear at the top-right, pass in score `false` (for instance, 52 | * on the title screen or leaderboard screen). 53 | */ 54 | write(text, score = false, timer = false) { 55 | this.ctx.font = `${this.conf.FONT_WEIGHT} ${this.conf.FONT_SIZE}px ${ 56 | this.conf.FONT_FAM 57 | }`; 58 | this.ctx.fillStyle = palette.black; 59 | this.ctx.fillRect( 60 | 0, 61 | 0, 62 | this.canvas.width / this.conf.ASPECT, 63 | this.canvas.height 64 | ); 65 | this.ctx.fillStyle = palette.yellow_light; 66 | 67 | // fillText doesn't do multi-line, so split the text and call fill text 68 | // multiple times 69 | let y_offset = 0; 70 | let line_count = 0; 71 | for (let line of text.split("\n").reverse()) { 72 | this.ctx.fillText( 73 | line, 74 | this.conf.PAD_LEFT, 75 | this.canvas.height - this.conf.PAD_BOTTOM - y_offset 76 | ); 77 | y_offset += this.conf.FONT_SIZE + this.conf.LINE_SPACING; 78 | 79 | // break if we've drawn the max number of display lines 80 | line_count += 1; 81 | if (line_count > this.conf.MAX_LINES) break; 82 | } 83 | 84 | // black out the top line whenever score or timer is being displayed 85 | if (score !== false || timer !== false) { 86 | this.ctx.fillStyle = palette.black; 87 | this.ctx.fillRect( 88 | 0, 89 | 0, 90 | this.canvas.width * 2, 91 | this.conf.FONT_SIZE * 2 92 | ); 93 | // this.ctx.fillRect(0, 0, 1000, 1000); 94 | this.ctx.fillStyle = palette.yellow_light; 95 | } 96 | 97 | // draw score and time remaining 98 | if (score !== false) { 99 | this.ctx.fillText( 100 | `score: ${score}`, 101 | this.conf.PAD_LEFT, 102 | this.conf.PAD_BOTTOM 103 | ); 104 | } 105 | if (timer !== false) { 106 | this.ctx.fillText( 107 | `timer: ${timer}`, 108 | this.canvas.width - this.conf.PAD_LEFT, 109 | this.conf.PAD_BOTTOM 110 | ); 111 | } 112 | } 113 | } 114 | 115 | const consoleCanvas = new ConsoleCanvas(); 116 | window.consoleCanvas = consoleCanvas; 117 | export default consoleCanvas; 118 | -------------------------------------------------------------------------------- /src/keycodes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Useful keycodes! 3 | */ 4 | export default { 5 | // TODO: number row punctuation is included in one of these ranges. 6 | nums: { start: 48, end: 57 }, 7 | alpha: { start: 65, end: 90 }, 8 | punct: { start: 186, end: 222 }, 9 | enter: 13, 10 | left_arrow: 37, 11 | right_arrow: 39, 12 | backspace: 8, 13 | ctrl: 17, 14 | space: 32 15 | }; 16 | -------------------------------------------------------------------------------- /src/leaderboard.js: -------------------------------------------------------------------------------- 1 | import config from "./config.js"; 2 | 3 | const STORAGE_TYPES = { 4 | local: 0, 5 | parse: 1 6 | }; 7 | 8 | const state = { 9 | storage: STORAGE_TYPES.local, 10 | name: config.LEADERBOARD_NAMESPACE_DEFAULT 11 | }; 12 | 13 | function init() { 14 | const urlParams = new URLSearchParams(window.location.search); 15 | 16 | // handle the leaderboard namespace param 17 | if (urlParams.has("name")) { 18 | const qsName = urlParams.get("name"); 19 | console.assert( 20 | qsName.trim().length, 21 | "invalid ?name value: must not be empty" 22 | ); 23 | state.name = qsName; 24 | } 25 | 26 | // handle the leaderboard storage param 27 | if (urlParams.has("storage")) { 28 | const qsStorage = urlParams.get("storage"); 29 | console.assert( 30 | STORAGE_TYPES.hasOwnProperty(qsStorage), 31 | `invalid ?storage value provided, must be one of: ${Object.keys( 32 | STORAGE_TYPES 33 | )}` 34 | ); 35 | state.storage = STORAGE_TYPES[qsStorage]; 36 | } 37 | } 38 | 39 | async function record({ name, score, tribe }) { 40 | if (state.storage == STORAGE_TYPES.local) { 41 | return await recordLocal({ name, score, tribe }); 42 | } else if (state.storage == STORAGE_TYPES.parse) { 43 | return await recordParse({ name, score, tribe }); 44 | } 45 | } 46 | 47 | async function recordLocal({ name, score, tribe }) { 48 | console.log(`recording leaderboard entry in localstorage`, { 49 | name, 50 | score, 51 | tribe 52 | }); 53 | 54 | const leaders = JSON.parse(localStorage.getItem(state.name)) || []; 55 | leaders.push({ 56 | name: name, 57 | score: app.score, 58 | tribe: tribe 59 | }); 60 | localStorage.setItem(state.name, JSON.stringify(leaders)); 61 | } 62 | 63 | async function recordParse({ name, score, tribe }) { 64 | console.log(`recording leaderboard entry in parse`, { name, score, tribe }); 65 | 66 | const response = await fetch(`${config.PARSE_URL}/classes/${state.name}`, { 67 | method: "POST", 68 | headers: { 69 | "X-Parse-Application-Id": config.PARSE_APPID, 70 | "Content-Type": "application/json" 71 | }, 72 | body: JSON.stringify({ name, score, tribe }) 73 | }); 74 | 75 | const responseJson = await response.json(); 76 | } 77 | 78 | async function get() { 79 | if (state.storage == STORAGE_TYPES.local) { 80 | return await getLocal(); 81 | } else if (state.storage == STORAGE_TYPES.parse) { 82 | return await getParse(); 83 | } 84 | } 85 | 86 | async function getLocal() { 87 | // First get the current scores from localStorage 88 | let leaders = JSON.parse(localStorage.getItem(state.name)); 89 | return formatLeaders(leaders); 90 | } 91 | 92 | async function getParse() { 93 | const response = await fetch( 94 | `${config.PARSE_URL}/classes/${state.name}?limit=10000`, 95 | { 96 | method: "GET", 97 | headers: { 98 | "X-Parse-Application-Id": config.PARSE_APPID 99 | } 100 | } 101 | ); 102 | 103 | const scores = await response.json(); 104 | 105 | return formatLeaders(scores.results); 106 | } 107 | 108 | function formatLeaders(leaders) { 109 | leaders = _.reverse(_.sortBy(leaders, "score")); 110 | const hiScores = _(leaders) 111 | .sortBy("score") 112 | .reverse() 113 | .uniqBy("name") 114 | .take(10) 115 | .map("score") 116 | .value(); 117 | const lowestHiScore = _.min(hiScores); 118 | const topHiScore = _.max(hiScores); 119 | const isEmpty = leaders.length === 0; 120 | return { 121 | leaders, 122 | hiScores, 123 | topHiScore, 124 | lowestHiScore, 125 | isEmpty 126 | }; 127 | } 128 | 129 | export default { 130 | init, 131 | record, 132 | get 133 | }; 134 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import "../node_modules/three/examples/js/loaders/OBJLoader.js"; 2 | import "../node_modules/three/examples/js/controls/OrbitControls.js"; 3 | import "../node_modules/three/examples/js/controls/TrackballControls.js"; 4 | import "./Fire.js"; 5 | import "./MTLLoaderPhysical.js"; 6 | import app from "./app.js"; 7 | import tweenCamera from "./tween-camera.js"; 8 | import keyCodes from "./keycodes.js"; 9 | import { loadMesh } from "./three-utils.js"; 10 | import STATES from "./states.js"; 11 | import sleep from "./sleep.js"; 12 | import consoleCanvas from "./console-canvas.js"; 13 | import config from "./config.js"; 14 | import sfx from "./sfx.js"; 15 | import leaderboard from "./leaderboard.js"; 16 | 17 | let container; 18 | let camera, scene, renderer, controls; 19 | let mouseX = 0, 20 | mouseY = 0; 21 | let windowHalfX = window.innerWidth / 2; 22 | let windowHalfY = window.innerHeight / 2; 23 | let computer; 24 | 25 | // Fire vars 26 | let fire; 27 | let firePlane; 28 | let allowFire = false; 29 | 30 | let leaders; 31 | 32 | // FPS tracking 33 | let stats = new Stats(); 34 | // document.body.appendChild(stats.dom); 35 | let t, previousTime; 36 | let slowCount = 0; 37 | let maxSlowFrames = config.MAX_SLOW_FRAMES; 38 | let isLowFPS = false; 39 | window.isLowFPS = isLowFPS; 40 | t = previousTime = performance.now(); 41 | 42 | const states = { 43 | [STATES.title]: { 44 | enter: async function() { 45 | app.updateConsole = _.noop; 46 | app.resetState(); 47 | app.typingLoop(); 48 | app.cmd = ""; 49 | 50 | controls.enabled = false; 51 | 52 | slowCount = 0; 53 | 54 | // make font big enough to see from a distance 55 | consoleCanvas.conf.FONT_SIZE = 4 * 114; 56 | 57 | const camTween = tweenCamera(camera, { 58 | rotation: { 59 | x: -0.5832659522477153, 60 | y: 0.4513175431123964, 61 | z: 0.28022041929249414 62 | }, 63 | position: { 64 | x: 68.79903504601936, 65 | y: 218.79396932448483, 66 | z: 432.0475129782785 67 | }, 68 | duration: 4000 69 | }); 70 | // wait a short time so the CLH test pattern can be seen, then start drawing the console 71 | await sleep(300); 72 | app.updateConsole = app.writeToConsole; 73 | 74 | sfx.boot.play(); 75 | 76 | // let the camera zoom in for a while before moving on to displaying text on screen 77 | await sleep(1000); 78 | 79 | app.cmd = "LOADING..."; 80 | app.cmd += "\n\nTESTING ROUTINE\nINITIATED."; 81 | app.cmd += "\n\nType PLAY"; 82 | 83 | await camTween; 84 | 85 | sfx.menuMusic.play(); 86 | 87 | app.showTitle = true; 88 | 89 | app.cmd += "\n> "; 90 | 91 | await sleep(app.typingTime(app.cmd)); 92 | 93 | app.onResult = async result => { 94 | if (result.cmd.toLowerCase() === "play") { 95 | app.onResult = _.noop(); 96 | app.allowTyping = false; 97 | app.showTitle = false; 98 | app.cmd = ""; 99 | sfx.boot.fade(sfx.boot.originalVolume, 0, 600); 100 | sfx.menuMusic.fade(sfx.menuMusic.originalVolume, 0, 600); 101 | await sleep(200); 102 | app.toState(STATES.play); 103 | } else if (result.cmd.toLowerCase() === "leaderboard") { 104 | app.onResult = _.noop(); 105 | app.allowTyping = false; 106 | app.showTitle = false; 107 | app.cmd = ""; 108 | sfx.boot.fade(sfx.boot.originalVolume, 0, 600); 109 | sfx.menuMusic.fade(sfx.menuMusic.originalVolume, 0, 600); 110 | await sleep(200); 111 | app.toState(STATES.leaderboard); 112 | } else { 113 | app.cmd += "\nType PLAY\n"; 114 | } 115 | }; 116 | 117 | app.allowTyping = true; 118 | } 119 | }, 120 | [STATES.play]: { 121 | enter: async function() { 122 | // make font appropriate size for when camera is zoomed in 123 | consoleCanvas.conf.FONT_SIZE = 4 * 48; 124 | controls.enabled = false; 125 | 126 | // log play count 127 | let playCount = localStorage.getItem("clhPlayCount"); 128 | playCount++; 129 | localStorage.setItem("clhPlayCount", playCount); 130 | 131 | // wait for Enter to be pressed and then start the countdown 132 | app.onKeyPress = async ev => { 133 | // don't let any other event handlers run 134 | ev.preventDefault(); 135 | ev.stopPropagation(); 136 | 137 | if (ev.keyCode === keyCodes.enter) { 138 | app.onKeyPress = _.noop; 139 | showGolden(); 140 | } 141 | }; 142 | 143 | // Keep a record of entered valid commands 144 | let enteredValidCmds = []; 145 | 146 | app.cmd = "\nEntering game..."; 147 | 148 | app.goldenCommands = app.pickGoldenCommands(); 149 | 150 | // play golden command music 151 | sfx.play.play("golden"); 152 | 153 | await tweenCamera(camera, { 154 | rotation: { 155 | x: 0, 156 | y: 0, 157 | z: 0 158 | }, 159 | position: { 160 | x: -4.336209717881005, 161 | y: 39.566049707444186, 162 | z: 155.4934617372831 163 | } 164 | }); 165 | 166 | app.cmd = `You have ${config.GAME_DURATION / 167 | 1000} seconds to enter ANY 168 | of the following: 169 | 170 | - bash shell commands & linux built-ins 171 | - JavaScript keywords, objects, functions 172 | - Python keywords, objects, functions 173 | - HTML5 tags 174 | 175 | Press Enter to continue.`; 176 | 177 | async function showGolden() { 178 | // wait for Enter to be pressed and then start the countdown 179 | app.onKeyPress = async ev => { 180 | // don't let any other event handlers run 181 | ev.preventDefault(); 182 | ev.stopPropagation(); 183 | 184 | if (ev.keyCode === keyCodes.enter) { 185 | app.onKeyPress = _.noop; 186 | startPlaying(); 187 | } 188 | }; 189 | 190 | app.cmd = `\nThese commands are worth ${ 191 | config.SCORE_GOLDEN_COMMAND_MULTIPLIER 192 | }x BONUS points:\n\n`; 193 | app.cmd += app.printGoldenCommands(); 194 | app.cmd += "\nPress Enter to begin."; 195 | } 196 | 197 | async function startPlaying() { 198 | app.cmd = "\nGet ready to enter commands... "; 199 | await sleep(1000); 200 | let countdown = 3; 201 | while (countdown--) { 202 | app.cmd += `${1 + countdown} `; 203 | sfx.timerRelaxed.play(); 204 | await sleep(1000); 205 | } 206 | sfx.timerUrgent.play(); 207 | 208 | const blankChars = _.times( 209 | Math.floor(consoleCanvas.conf.PLAY_CHARS_PER_LINE / 2 - 2), 210 | _.constant(" ") 211 | ).join(""); 212 | const blankLines = _.times( 213 | Math.floor(consoleCanvas.conf.MAX_LINES / 2), 214 | _.constant("\n") 215 | ).join(""); 216 | app.cmd = `${blankChars}TYPE!${blankLines}`; 217 | app.onResult = async result => { 218 | if ( 219 | result.valid && 220 | !enteredValidCmds.includes(result.cmd) 221 | ) { 222 | let cmdScore = config.SCORE_PER_COMMAND; 223 | 224 | app.cmd += ` ✔ [${result.lang.join(" ")}]`; 225 | 226 | // See if the command entered was a golden command 227 | if (app.goldenCommands.all.includes(result.cmd)) { 228 | console.log("GOLDEN COMMAND ENTERED!"); 229 | sfx.cmdGold.play(); 230 | 231 | // Give BIG bonus for golden commands 232 | cmdScore *= config.SCORE_GOLDEN_COMMAND_MULTIPLIER; 233 | } else { 234 | sfx.cmdGood.play(); 235 | } 236 | 237 | // Increase score 238 | app.score += 239 | (cmdScore + result.cmd.length) * 240 | config.SCORE_OVERALL_MULTIPLIER; 241 | 242 | // Keep log of entered valid commands 243 | enteredValidCmds.push(result.cmd); 244 | 245 | // Valid command increment counters 246 | app.count.totalValidCommands++; 247 | app.count.totalValidCharacters += result.cmd.length; 248 | app.count.recentValidCharacters += result.cmd.length; 249 | } else { 250 | if ( 251 | result.valid && 252 | enteredValidCmds.includes(result.cmd) 253 | ) { 254 | app.cmd += " x [duplicate]"; 255 | } else { 256 | app.cmd += " x"; 257 | } 258 | 259 | sfx.cmdBad.play(); 260 | } 261 | 262 | // if the command submitted is not empty string, add a newline 263 | app.cmd += "\n"; 264 | 265 | console.log( 266 | `entered "${result.cmd}"... it's ${ 267 | result.valid ? "valid!" : "invalid :(" 268 | }` 269 | ); 270 | }; 271 | 272 | app.allowTyping = true; 273 | 274 | // play gameplay music command music 275 | // sfx.play.fade(1, 0, 600, "golden"); 276 | sfx.play.stop(); 277 | sfx.play.play("playing"); 278 | 279 | // Reset fire delay timer 280 | allowFire = false; 281 | setTimeout(() => (allowFire = true), config.FIRE_DELAY_BEFORE); 282 | 283 | await sleep(1000); 284 | 285 | app.showScore = true; 286 | 287 | app.timer = config.GAME_DURATION / 1000; 288 | const iid = setInterval(() => { 289 | app.timer -= 1; 290 | 291 | // play a sound for the last few seconds of the timer 292 | 293 | if (app.timer <= 10 && app.timer >= 3) { 294 | sfx.timerRelaxed.play(); 295 | } else if (app.timer < 3) { 296 | sfx.timerUrgent.play(); 297 | } 298 | if (app.timer <= 0) { 299 | clearInterval(iid); 300 | } 301 | }, 1000); 302 | 303 | const fireInterval = setInterval(() => { 304 | // See if we need to turn up the FIRE! 305 | let cps = 306 | app.count.recentValidCharacters / 307 | (config.FIRE_CHECK_INTERVAL / 1000); 308 | console.log( 309 | "CPS:", 310 | cps, 311 | "Recent Valid:", 312 | app.count.recentValidCharacters 313 | ); 314 | let stage; 315 | 316 | if (allowFire && cps >= config.FIRE_CPS_THRESHOLD) { 317 | stage = fire.userData.stage + 1; 318 | setFireStage(stage); // go up a stage 319 | } else if (cps < config.FIRE_CPS_THRESHOLD) { 320 | stage = fire.userData.stage - 1; 321 | setFireStage(stage); // go down a stage 322 | } 323 | 324 | console.log("Set fire stage:", stage); 325 | 326 | app.count.recentValidCharacters = 0; 327 | 328 | if (app.timer <= 0) { 329 | clearInterval(fireInterval); 330 | setFireStage(config.FIRE_STAGE_ZERO); 331 | } 332 | }, config.FIRE_CHECK_INTERVAL); 333 | 334 | console.log("starting game timer"); 335 | await sleep(app.gameDuration); 336 | console.log("game timer o'er"); 337 | 338 | sfx.play.fade(sfx.play.originalVolume, 0, 600); 339 | 340 | app.cmd = ""; 341 | app.showScore = false; 342 | app.onResult = _.noop(); 343 | app.allowTyping = false; 344 | app.toState(STATES.score); 345 | } 346 | } 347 | }, 348 | [STATES.score]: { 349 | enter: async function() { 350 | app.allowTyping = false; 351 | 352 | controls.enabled = true; 353 | 354 | // Make sure fire is off 355 | setFireStage(config.FIRE_STAGE_ZERO); 356 | 357 | // Get current leaders 358 | leaders = await leaderboard.get(); 359 | 360 | await tweenCamera(camera, { 361 | rotation: { 362 | x: 0, 363 | y: 0, 364 | z: 0 365 | }, 366 | position: { 367 | x: -4.336209717881005, 368 | y: 39.566049707444186, 369 | z: 255.4934617372831 370 | } 371 | }); 372 | 373 | await sleep(500); 374 | 375 | // make font appropriate size for when camera is zoomed in 376 | consoleCanvas.conf.FONT_SIZE = 4 * 90; 377 | app.cmd = "THANKS FOR PLAYING!\n\n"; 378 | app.cmd += `SCORE ${app.score}\n`; 379 | app.cmd += `BASH ${app.count.bash}\n`; 380 | app.cmd += `PYTHON ${app.count.py}\n`; 381 | app.cmd += `JAVASCRIPT ${app.count.js}\n`; 382 | app.cmd += `HTML5 ${app.count.html}\n`; 383 | 384 | app.cmd += `\nPress Enter to continue.`; 385 | 386 | // when any key is pressed, go back to the title screen 387 | app.onKeyPress = async ev => { 388 | // don't let any other event handlers run 389 | ev.preventDefault(); 390 | ev.stopPropagation(); 391 | 392 | if (ev.keyCode === keyCodes.enter) { 393 | app.onKeyPress = _.noop; 394 | app.cmd = ""; 395 | 396 | if (leaders.isEmpty || app.score > leaders.lowestHiScore) { 397 | app.toState(STATES.highscore); 398 | } else { 399 | app.toState(STATES.leaderboard); 400 | } 401 | } 402 | }; 403 | } 404 | }, 405 | [STATES.highscore]: { 406 | enter: async function() { 407 | app.allowTyping = false; 408 | app.showTitle = false; 409 | 410 | controls.enabled = true; 411 | 412 | // make font appropriate size 413 | consoleCanvas.conf.FONT_SIZE = 4 * 90; 414 | 415 | // Only tween if the camera is not close enough to screen 416 | if (Math.floor(camera.position.z) !== 255) { 417 | await tweenCamera(camera, { 418 | rotation: { 419 | x: 0, 420 | y: 0, 421 | z: 0 422 | }, 423 | position: { 424 | x: -4.336209717881005, 425 | y: 39.566049707444186, 426 | z: 255.4934617372831 427 | } 428 | }); 429 | } 430 | 431 | if (app.score > leaders.topHiScore) { 432 | app.cmd = "Top Score!\n"; 433 | console.log("New top score!", app.score); 434 | } else { 435 | app.cmd = "New High Score!\n"; 436 | console.log("New high score", app.score); 437 | } 438 | 439 | app.cmd += "\nEnter your name"; 440 | 441 | await sleep(app.typingTime(app.cmd)); 442 | 443 | app.allowTyping = true; 444 | app.cmd += "\n"; 445 | 446 | app.onResult = async result => { 447 | if (result.cmd.length > 0 && result.cmd !== ">") { 448 | app.onResult = _.noop(); 449 | app.allowTyping = false; 450 | let name = result.cmd; 451 | let tribe = deriveTribe(); 452 | 453 | // truncate name to max size 454 | name = name.substring(0, config.MAX_LEADER_NAME_LENGTH); 455 | 456 | // Store score and name pair in localStorage 457 | console.log("leader name: ", result.cmd); 458 | 459 | // this is an async function but we don't `await` it 460 | // because it's totally fine for it to run in the 461 | // background 462 | leaderboard.record({ 463 | name: name, 464 | score: app.score, 465 | tribe: tribe 466 | }); 467 | 468 | app.cmd = ""; 469 | 470 | await sleep(200); 471 | app.toState(STATES.leaderboard); 472 | } else { 473 | app.cmd += "\nEnter your name\n"; 474 | } 475 | }; 476 | } 477 | }, 478 | [STATES.leaderboard]: { 479 | enter: async function() { 480 | app.allowTyping = false; 481 | app.showTitle = false; 482 | 483 | controls.enabled = true; 484 | 485 | // Make smaller font size 486 | consoleCanvas.conf.FONT_SIZE = 4 * 48; 487 | 488 | // Only tween if the camera is not close enough to screen 489 | if (Math.floor(camera.position.z) !== 155) { 490 | await tweenCamera(camera, { 491 | rotation: { 492 | x: 0, 493 | y: 0, 494 | z: 0 495 | }, 496 | position: { 497 | x: -4.336209717881005, 498 | y: 39.566049707444186, 499 | z: 155.4934617372831 500 | } 501 | }); 502 | } 503 | 504 | // Fetch current leaders 505 | leaders = await leaderboard.get(); 506 | 507 | app.cmd = "HIGH SCORES\n\n"; 508 | 509 | if (leaders.isEmpty) { 510 | app.cmd += "None found!"; 511 | } else { 512 | app.cmd += app.printHighScores(leaders.leaders); 513 | } 514 | 515 | app.cmd += `\nPress Enter to continue.`; 516 | 517 | // when any key is pressed, go back to the title screen 518 | app.onKeyPress = async ev => { 519 | // don't let any other event handlers run 520 | ev.preventDefault(); 521 | ev.stopPropagation(); 522 | 523 | if (ev.keyCode === keyCodes.enter) { 524 | app.onKeyPress = _.noop; 525 | app.cmd = ""; 526 | app.toState(STATES.title); 527 | } 528 | }; 529 | } 530 | } 531 | }; 532 | window.states = states; 533 | 534 | async function start() { 535 | // Init localStorage 536 | leaderboard.init(); 537 | 538 | if (localStorage.getItem("clhPlayCount") === null) { 539 | localStorage.setItem("clhPlayCount", "0"); 540 | } 541 | 542 | // set up a state change listener so when the Vue app changes state, we 543 | // also run the 3D world state changes. 544 | app.onStateChange = change => { 545 | console.log(`state change: ${change.from} -> ${change.to}`); 546 | if (states[change.to]) { 547 | states[change.to].enter(); 548 | } else { 549 | throw new Error(`tried to enter nonexistant state ${change.to}`); 550 | } 551 | }; 552 | 553 | await init(); 554 | animate(0); 555 | 556 | app.toState(STATES.title); 557 | } 558 | 559 | async function init() { 560 | container = document.createElement("div"); 561 | document.body.appendChild(container); 562 | 563 | // init renderer 564 | renderer = new THREE.WebGLRenderer({ 565 | canvas: document.querySelector("#game-canvas"), 566 | antialias: true 567 | }); 568 | renderer.setPixelRatio(window.devicePixelRatio); 569 | renderer.setSize(window.innerWidth, window.innerHeight); 570 | renderer.shadowMap.enabled = true; 571 | renderer.shadowMap.type = THREE.PCFShadowMap; 572 | container.appendChild(renderer.domElement); 573 | 574 | // scene 575 | 576 | scene = new THREE.Scene(); 577 | scene.background = new THREE.Color( 578 | 0.52164000272751 / 4.3, 579 | 0.08910000324249 / 4.3, 580 | 0.81000000238419 / 4.3 581 | ); 582 | 583 | // camera 584 | 585 | camera = new THREE.PerspectiveCamera( 586 | 45, 587 | window.innerWidth / window.innerHeight, 588 | 1, 589 | 20000 590 | ); 591 | // camera.position.z = 350; 592 | camera.position.z = 2000; 593 | camera.position.y = 300; 594 | scene.add(camera); 595 | 596 | controls = new THREE.OrbitControls(camera, renderer.domElement); 597 | controls.enabled = false; 598 | // controls = new THREE.TrackballControls(camera); 599 | 600 | // lighting 601 | 602 | let ambientLight = new THREE.AmbientLight(0xffffff, 0.8); 603 | scene.add(ambientLight); 604 | 605 | // spotlights 606 | 607 | const SHADOW_MAP_WIDTH = 1024 * 2, 608 | SHADOW_MAP_HEIGHT = 1024 * 2; 609 | const whiteSpot = new THREE.SpotLight(0xffffff, 1.0); 610 | whiteSpot.position.set(-300, 600, 600); 611 | whiteSpot.angle = Math.PI / 8; 612 | whiteSpot.penumbra = 0.5; 613 | whiteSpot.decay = 2; 614 | whiteSpot.distance = 4000; 615 | scene.add(whiteSpot); 616 | 617 | const purpleSpot = new THREE.SpotLight(0xda8aff, 1.0); 618 | purpleSpot.position.set(200, 200, 200); 619 | purpleSpot.angle = Math.PI / 4; 620 | purpleSpot.penumbra = 0.5; 621 | purpleSpot.decay = 4; 622 | purpleSpot.distance = 2000; 623 | purpleSpot.castShadow = true; 624 | purpleSpot.shadow.mapSize.width = SHADOW_MAP_WIDTH; 625 | purpleSpot.shadow.mapSize.height = SHADOW_MAP_HEIGHT; 626 | purpleSpot.shadow.camera.near = 200; 627 | purpleSpot.shadow.camera.far = 1000; 628 | scene.add(purpleSpot); 629 | 630 | // models 631 | 632 | // load computer 633 | 634 | const comp = await loadMesh( 635 | "assets/models/", 636 | "CLH_ep2_computer_high_poly.mtl", 637 | "CLH_ep2_computer_high_poly.obj" 638 | ); 639 | // make the screen reflect a crisp image 640 | comp.materials.materials.screen.roughness = 0.08; 641 | comp.materials.materials.screen.roughness = 0.7; 642 | comp.object.position.y = -300; 643 | comp.object.position.x = 0; 644 | 645 | // enable shadows for each object in the set of computer meshes 646 | comp.object.children.forEach(c => { 647 | c.castShadow = true; 648 | c.receiveShadow = true; 649 | }); 650 | 651 | comp.object.castShadow = true; 652 | comp.object.receiveShadow = true; 653 | 654 | // set up a special canvas material for the screen 655 | 656 | const screen = _.find(comp.object.children, { 657 | name: "IBM_5150_Monitor_-_glass" 658 | }); 659 | screen.material = new THREE.MeshBasicMaterial(); 660 | screen.material.map = new THREE.CanvasTexture(consoleCanvas.canvas); 661 | window.screen = screen; 662 | 663 | computer = comp.object; 664 | window.comp = comp; 665 | scene.add(comp.object); 666 | camera.lookAt(comp.object.position); 667 | 668 | // create a TEMPORARY flat plane to draw the console on. 669 | 670 | // the comp model we have doesnt' have UV coordinates, so we can't draw a 671 | // texture onto it. for now, create a 3d plane, position it just 672 | // in front of the screen, and draw the console onto it. 673 | // (TODO: remove this once we have a comp model with UV coords) 674 | 675 | // get the screen position and dimensions and copy them 676 | screen.geometry.computeBoundingBox(); 677 | const screenSize = { 678 | width: 679 | screen.geometry.boundingBox.max.x - 680 | screen.geometry.boundingBox.min.x, 681 | height: 682 | screen.geometry.boundingBox.max.y - 683 | screen.geometry.boundingBox.min.y 684 | }; 685 | 686 | const consolePlaneGeo = new THREE.PlaneGeometry( 687 | screenSize.width, 688 | screenSize.height 689 | ); 690 | const consolePlane = new THREE.Mesh(consolePlaneGeo, screen.material); 691 | consolePlane.position.set(-5.5, 42.8, 26); 692 | consolePlane.rotation.x = -0.16; 693 | 694 | // Fixes bug where slight sliver was hollow at bottom of console 695 | consolePlane.scale.x = 1.012; 696 | consolePlane.scale.y = 1.012; 697 | 698 | screen.visible = false; 699 | window.consolePlane = consolePlane; 700 | 701 | scene.add(consolePlane); 702 | 703 | // Fire 704 | firePlane = new THREE.PlaneBufferGeometry( 705 | screenSize.width * 3.7, 706 | screenSize.height * 3.7 707 | ); 708 | fire = new THREE.Fire(firePlane, { 709 | textureWidth: 512, 710 | textureHeight: 512, 711 | debug: false 712 | }); 713 | let texture = new THREE.TextureLoader().load( 714 | "assets/images/monitor_fire_inner.png" 715 | ); 716 | texture.needsUpdate = true; 717 | fire.clearSources(); 718 | fire.setSourceMap(texture); 719 | fire.position.set(-5.5, 42.8, 25.5); 720 | fire.rotation.x = -0.16; 721 | setFireStage(config.FIRE_STAGE_ZERO); 722 | scene.add(fire); 723 | window.fire = fire; 724 | 725 | // load cyc wall 726 | 727 | const cyc = await loadMesh( 728 | "assets/models/", 729 | "CLH_ep2_cyc_wall.mtl", 730 | "CLH_ep2_cyc_wall.obj" 731 | ); 732 | window.cyc = cyc.object; 733 | cyc.object.position.y = 50; 734 | cyc.object.children[0].castShadow = true; 735 | cyc.object.children[0].receiveShadow = true; 736 | cyc.materials.materials.purple.metalness = 0.4; 737 | cyc.materials.materials.purple.roughness = 0.5; 738 | cyc.materials.materials.purple.color.setHex(0x621b9c); 739 | scene.add(cyc.object); 740 | 741 | // tweak the red and purple computer colors 742 | comp.materials.materials.red.color.setHex(0x881111); 743 | comp.materials.materials.red.clearcoat = 0.5 744 | comp.materials.materials.red.clearcoatRoughness = 0.3; 745 | 746 | comp.materials.materials.purple.color.setHex(0x7d2cd0); 747 | comp.materials.materials.purple.metalness = 0.4; 748 | comp.materials.materials.purple.roughness = 0.5; 749 | comp.materials.materials.purple.clearcoat = 0.5; 750 | comp.materials.materials.purple.clearcoatRoughness = 0.3; 751 | 752 | document.addEventListener("mousemove", onDocumentMouseMove, false); 753 | 754 | window.addEventListener("resize", onWindowResize, false); 755 | console.log("init complete"); 756 | } 757 | 758 | function deriveTribe() { 759 | let cmdCounts = [ 760 | { tribe: "bash", count: app.count.bash }, 761 | { tribe: "Python", count: app.count.py }, 762 | { tribe: "JavaScript", count: app.count.js }, 763 | { tribe: "HTML", count: app.count.html } 764 | ]; 765 | 766 | const tribesSorted = _.reverse(_.sortBy(cmdCounts, "count")); 767 | 768 | return tribesSorted[0].tribe; 769 | } 770 | 771 | function getFireScaleByStage(stage) { 772 | let scale = {}; 773 | let one = config.FIRE_STAGE_ONE_SCALE; 774 | let two = config.FIRE_STAGE_TWO_SCALE; 775 | let three = config.FIRE_STAGE_THREE_SCALE; 776 | 777 | if (isLowFPS) { 778 | one = config.FIRE_LOW_FPS_STAGE_ONE_SCALE; 779 | two = config.FIRE_LOW_FPS_STAGE_TWO_SCALE; 780 | three = config.FIRE_LOW_FPS_STAGE_THREE_SCALE; 781 | } 782 | 783 | switch (stage) { 784 | case config.FIRE_STAGE_ZERO: 785 | scale.x = 0.1; 786 | scale.y = 0.1; 787 | break; 788 | case config.FIRE_STAGE_ONE: 789 | scale.x = one.x; 790 | scale.y = one.y; 791 | break; 792 | case config.FIRE_STAGE_TWO: 793 | scale.x = two.x; 794 | scale.y = two.y; 795 | break; 796 | case config.FIRE_STAGE_THREE: 797 | scale.x = three.x; 798 | scale.y = three.y; 799 | break; 800 | default: 801 | scale.x = 0.1; 802 | scale.y = 0.1; 803 | } 804 | 805 | if (stage > config.FIRE_STAGE_THREE) { 806 | scale.x = 1; 807 | scale.y = 1; 808 | } 809 | 810 | return scale; 811 | } 812 | 813 | function setFireStage(stage) { 814 | if (stage > config.FIRE_STAGE_THREE) stage = config.FIRE_STAGE_THREE; 815 | else if (stage < 0) stage = 0; 816 | 817 | if (fire.userData.stage === stage) return; // already on this stage 818 | 819 | if (fire.userData.stage === undefined) fire.userData.stage = 0; 820 | 821 | if (!isLowFPS) { 822 | fire.color1.set(config.FIRE_COLOR_1); 823 | fire.color2.set(config.FIRE_COLOR_2); 824 | fire.color3.set(config.FIRE_COLOR_3); 825 | fire.windVector.y = config.FIRE_WIND_VECTOR_Y; 826 | fire.colorBias = config.FIRE_COLOR_BIAS; 827 | fire.burnRate = config.FIRE_BURN_RATE; 828 | fire.diffuse = config.FIRE_DIFFUSE; 829 | fire.viscosity = config.FIRE_VISCOSITY; 830 | fire.expansion = config.FIRE_EXPANSION; 831 | fire.swirl = config.FIRE_SWIRL; 832 | fire.drag = config.FIRE_DRAG; 833 | fire.airSpeed = config.FIRE_AIR_SPEED; 834 | fire.speed = config.FIRE_SPEED; 835 | } else { 836 | fire.color1.set(config.FIRE_LOW_FPS_COLOR_1); 837 | fire.color2.set(config.FIRE_LOW_FPS_COLOR_2); 838 | fire.color3.set(config.FIRE_LOW_FPS_COLOR_3); 839 | fire.windVector.y = config.FIRE_LOW_FPS_WIND_VECTOR_Y; 840 | fire.colorBias = config.FIRE_LOW_FPS_COLOR_BIAS; 841 | fire.burnRate = config.FIRE_LOW_FPS_BURN_RATE; 842 | fire.diffuse = config.FIRE_LOW_FPS_DIFFUSE; 843 | fire.viscosity = config.FIRE_LOW_FPS_VISCOSITY; 844 | fire.expansion = config.FIRE_LOW_FPS_EXPANSION; 845 | fire.swirl = config.FIRE_LOW_FPS_SWIRL; 846 | fire.drag = config.FIRE_LOW_FPS_DRAG; 847 | fire.airSpeed = config.FIRE_LOW_FPS_AIR_SPEED; 848 | fire.speed = config.FIRE_LOW_FPS_SPEED; 849 | } 850 | 851 | if (stage === config.FIRE_STAGE_ZERO) fire.userData.on = false; 852 | 853 | const stageScale = getFireScaleByStage(stage); 854 | let steps = Math.abs(fire.userData.stage - stage); 855 | console.log("stageScale:", stageScale); 856 | console.log("steps:", steps); 857 | 858 | new TWEEN.Tween(fire.scale) 859 | .to( 860 | { x: stageScale.x, y: stageScale.y }, 861 | steps * config.FIRE_STAGE_TWEEN_TIME 862 | ) 863 | .easing(TWEEN.Easing.Quartic.InOut) // Use an easing function to make the animation smooth. 864 | .start(); 865 | 866 | fire.userData.stage = stage; 867 | } 868 | window.setFireStage = setFireStage; 869 | 870 | function onWindowResize() { 871 | windowHalfX = window.innerWidth / 2; 872 | windowHalfY = window.innerHeight / 2; 873 | 874 | camera.aspect = window.innerWidth / window.innerHeight; 875 | camera.updateProjectionMatrix(); 876 | 877 | renderer.setSize(window.innerWidth, window.innerHeight); 878 | } 879 | 880 | function onDocumentMouseMove(event) { 881 | mouseX = (event.clientX - windowHalfX) / 2; 882 | mouseY = (event.clientY - windowHalfY) / 2; 883 | } 884 | 885 | function animate(time) { 886 | // FPS tracking 887 | let maximumFrameTime = config.MAX_FRAME_TIME; 888 | t = performance.now(); 889 | let elapsed = t - previousTime; 890 | previousTime = t; 891 | 892 | if (elapsed > maximumFrameTime) { 893 | slowCount++; 894 | } 895 | 896 | if (slowCount > maxSlowFrames && !isLowFPS) { 897 | // This client has slow FPS currently 898 | console.log("Low FPS detected: ", 1000 / elapsed, "FPS"); 899 | isLowFPS = true; 900 | } 901 | 902 | requestAnimationFrame(animate); 903 | render(time); 904 | } 905 | 906 | function render(time) { 907 | stats.begin(); 908 | 909 | TWEEN.update(time); 910 | 911 | // update the canvas-based material 912 | screen.material.map.needsUpdate = true; 913 | 914 | app.updateConsole(); 915 | 916 | renderer.render(scene, camera); 917 | 918 | stats.end(); 919 | } 920 | 921 | function setInstuctionsDisplay(display) { 922 | let instructions = document.getElementById("instructions"); 923 | let langs = document.getElementById("langs"); 924 | let tagline = document.getElementById("tagline"); 925 | let listen = document.getElementById("listen"); 926 | 927 | instructions.style.display = display; 928 | langs.style.display = display; 929 | tagline.style.display = display; 930 | listen.style.display = display; 931 | } 932 | window.setInstuctionsDisplay = setInstuctionsDisplay; 933 | 934 | function setCreditsDisplay(display) { 935 | let credits = document.getElementById("credits"); 936 | credits.style.display = display; 937 | } 938 | window.setCreditsDisplay = setCreditsDisplay; 939 | 940 | function showCredits() { 941 | setInstuctionsDisplay("none"); 942 | setCreditsDisplay("block"); 943 | } 944 | window.showCredits = showCredits; 945 | 946 | function showInstructions() { 947 | setInstuctionsDisplay("block"); 948 | setCreditsDisplay("none"); 949 | } 950 | window.showInstructions = showInstructions; 951 | 952 | if (isMobile.any) { 953 | console.log("aborting init, can't run on mobile"); 954 | } else { 955 | start(); 956 | } 957 | 958 | // disables right click in game to prevent the context menu from inturrupting game play 959 | window.addEventListener("contextmenu", event => event.preventDefault()); 960 | -------------------------------------------------------------------------------- /src/palette.js: -------------------------------------------------------------------------------- 1 | const palette = {}; 2 | 3 | function cssVar(prop) { 4 | return window 5 | .getComputedStyle(document.body) 6 | .getPropertyValue(prop) 7 | .trim(); 8 | } 9 | 10 | palette.white = cssVar("--clh-white"); 11 | palette.black = cssVar("--clh-black"); 12 | palette.purple = cssVar("--clh-purple"); 13 | palette.purple_light = cssVar("--clh-purple-light"); 14 | palette.yellow = cssVar("--clh-yellow"); 15 | palette.yellow_light = cssVar("--clh-yellow-light"); 16 | palette.orange = cssVar("--clh-orange"); 17 | palette.orange_light = cssVar("--clh-orange-light"); 18 | palette.blue = cssVar("--clh-blue"); 19 | palette.blue_light = cssVar("--clh-blue-light"); 20 | 21 | export default palette; 22 | -------------------------------------------------------------------------------- /src/sfx.js: -------------------------------------------------------------------------------- 1 | const sfx = { 2 | cmdGood: new Howl({ src: ["assets/sfx/cmd-good.mp3"], volume: 1 }), 3 | cmdGold: new Howl({ src: ["assets/sfx/cmd-gold.mp3"], volume: 1 }), 4 | cmdBad: new Howl({ src: ["assets/sfx/cmd-bad.mp3"], volume: 2 }), 5 | keypress: new Howl({ src: ["assets/sfx/keypress.mp3"] }), 6 | timerRelaxed: new Howl({ 7 | src: ["assets/sfx/timer-relaxed.mp3"], 8 | volume: 1 9 | }), 10 | timerUrgent: new Howl({ src: ["assets/sfx/timer-urgent.mp3"], volume: 1 }), 11 | boot: new Howl({ src: ["assets/sfx/boot.mp3"] }), 12 | menuMusic: new Howl({ 13 | src: ["assets/sfx/menu-music.mp3"], 14 | volume: 0.2, 15 | loop: true 16 | }), 17 | play: new Howl({ 18 | src: ["assets/sfx/play.mp3"], 19 | volume: 0.2, 20 | sprite: { 21 | golden: [0, 29648, true], 22 | playing: [29649, 60000 + 39456] 23 | } 24 | }) 25 | }; 26 | 27 | // preserve the original volume setting for each sfx 28 | _.forEach(sfx, s => (s.originalVolume = s.volume())); 29 | 30 | // when these sfx have finished fading out, stop playing and seek back to the beginning 31 | [sfx.boot, sfx.menuMusic, sfx.play].forEach(sound => { 32 | sound.on("fade", () => { 33 | sound.stop(); 34 | sound.seek(0); 35 | sound.volume(sound.originalVolume); 36 | }); 37 | }); 38 | 39 | window.sfx = sfx; 40 | 41 | export default sfx; 42 | -------------------------------------------------------------------------------- /src/sleep.js: -------------------------------------------------------------------------------- 1 | export default function(ms) { 2 | return new Promise((resolve, reject) => setTimeout(resolve, ms)); 3 | } 4 | -------------------------------------------------------------------------------- /src/states.js: -------------------------------------------------------------------------------- 1 | export default { 2 | loading: "loading", 3 | title: "title", 4 | play: "play", 5 | score: "score", 6 | leaderboard: "leaderboard", 7 | highscore: "highscore" 8 | }; 9 | -------------------------------------------------------------------------------- /src/three-utils.js: -------------------------------------------------------------------------------- 1 | export async function loadMesh(path, mtl, obj) { 2 | let onProgress = function(name) { 3 | return function(xhr) { 4 | if (xhr.lengthComputable) { 5 | let percentComplete = (xhr.loaded / xhr.total) * 100; 6 | console.log( 7 | `${name} ${Math.round(percentComplete, 2)} % downloaded` 8 | ); 9 | } 10 | }; 11 | }; 12 | 13 | return new Promise((resolve, reject) => { 14 | new THREE.MTLLoader().setPath(path).load( 15 | mtl, 16 | function(materials) { 17 | materials.preload(); 18 | 19 | new THREE.OBJLoader() 20 | .setMaterials(materials) 21 | .setPath(path) 22 | .load( 23 | obj, 24 | function(object) { 25 | resolve({ materials, object }); 26 | }, 27 | onProgress(obj), 28 | reject 29 | ); 30 | }, 31 | onProgress(mtl), 32 | reject 33 | ); 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /src/tween-camera.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tween the camera to a given position and rotation. Duration and easing are optional. 3 | */ 4 | export default function tweenCamera( 5 | camera, 6 | { position, rotation, duration = 2000, easing = TWEEN.Easing.Quartic.Out } 7 | ) { 8 | return new Promise((resolve, reject) => { 9 | // TODO show other title state stuff like text, logo, etc. 10 | 11 | // tween to camera position 12 | new TWEEN.Tween(camera.rotation) // Create a new tween that modifies 'coords'. 13 | .to(rotation, duration) // Move to (300, 200) in 1 second. 14 | .easing(easing) // Use an easing function to make the animation smooth. 15 | .start(); // Start the tween immediately. 16 | new TWEEN.Tween(camera.position) // Create a new tween that modifies 'coords'. 17 | .to(position, duration) // Move to (300, 200) in 1 second. 18 | .easing(easing) // Use an easing function to make the animation smooth. 19 | .onComplete(resolve) 20 | .start(); // Start the tween immediately. 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /src/unzip.js: -------------------------------------------------------------------------------- 1 | var zlib = require('zlib'); 2 | var fs = require('fs'); 3 | 4 | function decompress(inFilename, outFilename) { 5 | var unzip = zlib.createUnzip(); 6 | var input = fs.createReadStream(inFilename); 7 | var output = fs.createWriteStream(outFilename); 8 | 9 | input.pipe(unzip).pipe(output); 10 | } 11 | 12 | decompress('assets/models/CLH_Computer.mtl.gz', 'assets/models/CLH_Computer.mtl'); 13 | decompress('assets/models/CLH_Computer.obj.gz', 'assets/models/CLH_Computer.obj'); 14 | decompress('assets/models/CLH_ep2_computer_high_poly.mtl.gz', 'assets/models/CLH_ep2_computer_high_poly.mtl'); 15 | decompress('assets/models/CLH_ep2_computer_high_poly.obj.gz', 'assets/models/CLH_ep2_computer_high_poly.obj'); 16 | decompress('assets/models/CLH_ep2_cyc_wall.mtl.gz', 'assets/models/CLH_ep2_cyc_wall.mtl'); 17 | decompress('assets/models/CLH_ep2_cyc_wall.obj.gz', 'assets/models/CLH_ep2_cyc_wall.obj'); 18 | decompress('assets/models/CLH_Shuttle.mtl.gz', 'assets/models/CLH_Shuttle.mtl'); 19 | decompress('assets/models/CLH_Shuttle.obj.gz', 'assets/models/CLH_Shuttle.obj'); -------------------------------------------------------------------------------- /start.js: -------------------------------------------------------------------------------- 1 | const compression = require("compression"); 2 | const bs = require("browser-sync").create(); 3 | 4 | bs.init({ 5 | server: "./", 6 | watch: true, 7 | middleware: [compression({filter: () => true })] 8 | }); 9 | --------------------------------------------------------------------------------