├── .gitignore ├── screenshots ├── menu.png ├── initial.png ├── on-fire.png ├── perfect.png ├── menu-mobile.png ├── glitchy-game.png ├── night-mobile.png ├── awesome-mobile.png ├── busted-in-night.png ├── morning-mobile.png ├── perfect-mobile.png ├── afternoon-destruction.png └── flying-in-daytime-air.png ├── js13kgames-logo ├── save-the-forest-160-160.png └── save-the-forest-400-250.png ├── dist ├── game.min.css ├── game.css ├── index.html ├── game.min.js └── game.js ├── src ├── css │ └── game.css ├── index.html ├── particles.js ├── sound.js ├── utils.js ├── main.js ├── jsfxr.js ├── tree.js ├── flame.js ├── player.js ├── game.js ├── background.js ├── weather.js └── menu.js ├── package.json ├── README.md ├── LICENSE └── gulpfile.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.zip 3 | npm-debug.log 4 | .DS_STORE 5 | -------------------------------------------------------------------------------- /screenshots/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softvar/save-the-forest/HEAD/screenshots/menu.png -------------------------------------------------------------------------------- /screenshots/initial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softvar/save-the-forest/HEAD/screenshots/initial.png -------------------------------------------------------------------------------- /screenshots/on-fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softvar/save-the-forest/HEAD/screenshots/on-fire.png -------------------------------------------------------------------------------- /screenshots/perfect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softvar/save-the-forest/HEAD/screenshots/perfect.png -------------------------------------------------------------------------------- /screenshots/menu-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softvar/save-the-forest/HEAD/screenshots/menu-mobile.png -------------------------------------------------------------------------------- /screenshots/glitchy-game.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softvar/save-the-forest/HEAD/screenshots/glitchy-game.png -------------------------------------------------------------------------------- /screenshots/night-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softvar/save-the-forest/HEAD/screenshots/night-mobile.png -------------------------------------------------------------------------------- /screenshots/awesome-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softvar/save-the-forest/HEAD/screenshots/awesome-mobile.png -------------------------------------------------------------------------------- /screenshots/busted-in-night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softvar/save-the-forest/HEAD/screenshots/busted-in-night.png -------------------------------------------------------------------------------- /screenshots/morning-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softvar/save-the-forest/HEAD/screenshots/morning-mobile.png -------------------------------------------------------------------------------- /screenshots/perfect-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softvar/save-the-forest/HEAD/screenshots/perfect-mobile.png -------------------------------------------------------------------------------- /screenshots/afternoon-destruction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softvar/save-the-forest/HEAD/screenshots/afternoon-destruction.png -------------------------------------------------------------------------------- /screenshots/flying-in-daytime-air.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softvar/save-the-forest/HEAD/screenshots/flying-in-daytime-air.png -------------------------------------------------------------------------------- /js13kgames-logo/save-the-forest-160-160.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softvar/save-the-forest/HEAD/js13kgames-logo/save-the-forest-160-160.png -------------------------------------------------------------------------------- /js13kgames-logo/save-the-forest-400-250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/softvar/save-the-forest/HEAD/js13kgames-logo/save-the-forest-400-250.png -------------------------------------------------------------------------------- /dist/game.min.css: -------------------------------------------------------------------------------- 1 | *,body{margin:0}#table,canvas{width:100%;height:100%}#table,body,canvas,html{height:100%}body{font-family:Verdana;font-size:14px}*{padding:0}#table{display:table}#cell{display:table-cell;vertical-align:middle}#canvascontainer{margin:auto;outline:#fff solid 1px}canvas{display:block} -------------------------------------------------------------------------------- /dist/game.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Verdana; 3 | font-size: 14px; 4 | margin: 0; 5 | } 6 | 7 | * { 8 | margin: 0;padding: 0; 9 | } 10 | body,html { 11 | height:100%; 12 | } 13 | #table { 14 | display:table;width:100%;height:100%; 15 | } 16 | #cell { 17 | display:table-cell;vertical-align: middle 18 | } 19 | #canvascontainer { 20 | margin: auto;outline: 1px solid white; 21 | } 22 | canvas { 23 | width: 100%; 24 | height:100%; 25 | display:block; 26 | } -------------------------------------------------------------------------------- /src/css/game.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Verdana; 3 | font-size: 14px; 4 | margin: 0; 5 | } 6 | 7 | * { 8 | margin: 0;padding: 0; 9 | } 10 | body,html { 11 | height:100%; 12 | } 13 | #table { 14 | display:table;width:100%;height:100%; 15 | } 16 | #cell { 17 | display:table-cell;vertical-align: middle 18 | } 19 | #canvascontainer { 20 | margin: auto;outline: 1px solid white; 21 | } 22 | canvas { 23 | width: 100%; 24 | height:100%; 25 | display:block; 26 | } -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | Forest Fire
-------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "save_the_forest", 3 | "version": "1.0.0", 4 | "description": "Save burning of forests!", 5 | "main": "main.js", 6 | "author": "Varun Malhotra", 7 | "repository": "https://github.com/softvar/save-the-forest", 8 | "license": "MIT", 9 | "devDependencies": { 10 | "gulp": "^3.9.1", 11 | "gulp-clean": "^0.3.2", 12 | "gulp-concat": "^2.6.0", 13 | "gulp-cssmin": "^0.1.7", 14 | "gulp-htmlmin": "^2.0.0", 15 | "gulp-jshint": "^2.0.1", 16 | "gulp-rename": "^1.2.2", 17 | "gulp-replace-path": "^0.4.0", 18 | "gulp-uglify": "^2.0.0", 19 | "gulp-watch": "^4.3.9", 20 | "gulp-zip": "^3.2.0", 21 | "jshint": "^2.9.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Forest Fire 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Save The Forest 2 | =============== 3 | 4 | Entry game for [js13kGames](http://js13kgames.com/entries/2016) competition. 5 | 6 | A fully canvas based HTML5 game where a player has to extinguish fire on tress using his magical water tank in order to save the Burning Forests. 7 | 8 | Rules 9 | ----- 10 | 11 | 1. Press Space or tap to keep jumping the player. 12 | 2. Land on trees to save them and earn karma. 13 | 3. Don't let the glithces hinder the journey. 14 | 15 | Screenshots 16 | ----------- 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Theme - Glitch 32 | -------------- 33 | 34 | * Climatic Abrupt changes 35 | * Player struggles to jump off tree if tries to climb from corner 36 | * Glitchy screen after speed of game is > 1.6 mph 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2017 Varun Malhotra 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/particles.js: -------------------------------------------------------------------------------- 1 | var PS; 2 | function Particles(x, y) { 3 | PS = this; 4 | this.x = x - 10, 5 | this.y = y; 6 | this.vyL1 = 3; 7 | this.vyL2 = 2; 8 | this.vyL3 = 1; 9 | this.finished = false; 10 | this.particles = []; 11 | this.diff = 0; 12 | this.draw(); 13 | // setTimeout(function () { 14 | // PS.finished = true; 15 | // }, 1000); 16 | } 17 | 18 | Particles.prototype = { 19 | draw: function () { 20 | // ctx.globalCompositeOperation = 'source-over'; 21 | fs('red'); 22 | for (var i = 0; i < 10; i+= 2) { 23 | fr(PS.x + 4*i, M.min(PS.y + this.vyL1, CC.h - 50), utils.getRandomInt(4, 6), utils.getRandomInt(4, 6)); 24 | } 25 | for (var i = 1; i < 10; i+= 2) { 26 | fr(PS.x + 4*i, M.min(PS.y + 7 + this.vyL2, CC.h - 50), utils.getRandomInt(4, 6), utils.getRandomInt(4, 6)); 27 | } 28 | for (var i = 0; i < 10; i+= 2) { 29 | fr(PS.x + 4*i, M.min(PS.y + 15 + this.vyL3, CC.h - 50), utils.getRandomInt(4, 6), utils.getRandomInt(4, 6)); 30 | } 31 | 32 | this.diff = CC.h - PS.y; 33 | PS.y += this.diff * (10 / 15) * (fps / 1000); 34 | 35 | if (PS.y > CC.h - P.fireOffset) { 36 | PS.finished = true; 37 | } 38 | PS.x -= G.speed; 39 | } 40 | } -------------------------------------------------------------------------------- /src/sound.js: -------------------------------------------------------------------------------- 1 | // For sound Effects 2 | var soundUtils = SU = { 3 | rd: function (a, b){ 4 | if(!b){ 5 | b = a; 6 | a = 0; 7 | } 8 | return Math.random() * (b - a) + a; 9 | }, 10 | rp: function (a){ 11 | return a[~~this.rd(a.length)]; 12 | }, 13 | soundEffect: function(sid, settings){ 14 | SU[sid] = []; 15 | 16 | settings.forEach(function(sound){ 17 | var audio = new Audio(); 18 | audio.src = jsfxr(sound); 19 | 20 | SU[sid].push(audio); 21 | }); 22 | }, 23 | play: function(sid) { 24 | if (!G.isSound) { 25 | return; 26 | } 27 | SU[sid] && SU.rp(SU[sid]).play(); 28 | } 29 | }; 30 | 31 | 32 | SU.soundEffect('gameOver', [ 33 | [2,0.2,0.01,,0.83,0.24,,,,0.62,0.6,,,0.1248,0.4522,,,,0.4,,,,,0.6] 34 | ]); 35 | SU.soundEffect('moveAhead', [ 36 | [2,,0.2047,,0.3986,0.5855,0.2236,-0.1697,,,,,,0.7882,-0.2576,,,,1,,,,,0.43] 37 | ]); 38 | SU.soundEffect('highestScore', [ 39 | [0,,0.016,0.4953,0.3278,0.6502,,,,,,0.4439,0.6322,,,,,,1,,,,,1] 40 | ]); 41 | SU.soundEffect('explosion1', [ 42 | [3,,0.3729,0.6547,0.4138,0.0496,,,,,,,,,,,,,1,,,,,0.4] 43 | ]); 44 | SU.soundEffect('explosion2', [ 45 | [3,0.43,0.61,0.3794,0.86,0.17,0.17,0.1399,0.1,0.07,0.06,0.04,0.1,,,0.96,0.26,-0.16,1,,,,,0.15] 46 | ]); 47 | SU.soundEffect('info', [ 48 | [2,,0.1889,,0.111,0.2004,,,,,,,,0.1157,,,,,1,,,0.1,,1] 49 | ]); 50 | SU.soundEffect('soundOn', [ 51 | [2,,0.2,,0.1753,0.64,,-0.5261,,,,,,0.5522,-0.564,,,,1,,,,,0.5] 52 | ]); 53 | SU.soundEffect('playGame', [ 54 | [2,,0.261,0.2142,0.2005,0.4618,0.0137,-0.3602,,,,,,0.2249,0.0858,,,,1,,,0.0001,,0.44] 55 | ]); 56 | SU.soundEffect('glitch', [ 57 | [3,,0.0272,0.5654,0.1785,0.7424,,,,,,0.2984,0.5495,,,,,,1,,,,,0.43] 58 | ]) -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | // Vibration api 2 | navigator.vibrate = (function(){ 3 | return navigator.vibrate 4 | || navigator.mozVibrate 5 | || navigator.webkitVibrate 6 | || noop; 7 | })(); 8 | 9 | // Utility functions 10 | var utils = { 11 | /** 12 | * Get a random number between the specified values 13 | * @param {Number} min 14 | * @param {Number} max 15 | * @return {Number} 16 | */ 17 | getRandomInt: function (min, max) { 18 | return Math.floor(Math.random() * (max - min + 1)) + min; 19 | }, 20 | /** 21 | * Shorthand for parseInt with radix 10 22 | * @param {Number} value 23 | * @return {Number} 24 | */ 25 | pI: function (value) { 26 | return parseInt(value, 10); 27 | }, 28 | /** 29 | * Clamp value between specified limits 30 | * @param {Number} value - value to be clamped 31 | * @param {Number} min - min value 32 | * @param {Number} max - max value 33 | * @return {Number} - desired clamped value 34 | */ 35 | clamp: function (value, min, max) { 36 | if (typeof min !== 'number') { min = -Infinity; } 37 | if (typeof max !== 'number') { max = Infinity; } 38 | return Math.max(min, Math.min(max, value)); 39 | }, 40 | /** 41 | * Get local storage data decode it before using it 42 | * @param {Boolean} isSound 43 | * @return {String} 44 | */ 45 | getLocalStorageData: function (isSound) { 46 | if (!isSound) { 47 | return utils.pI(atob(localStorage.getItem('__js13k_game_karma'))) || 0; 48 | } 49 | return utils.pI(atob(localStorage.getItem('__js13k_game_sound'))); 50 | }, 51 | /** 52 | * Save data to local storage 53 | * @param {String/Number} data 54 | * @param {Boolean} isSoundData - for saving sound preferences 55 | */ 56 | setLocalStorageData: function (data, isSoundData) { 57 | if (!isSoundData) { 58 | localStorage.setItem('__js13k_game_karma', btoa(data)); 59 | } else { 60 | localStorage.setItem('__js13k_game_sound', btoa(data)) 61 | } 62 | 63 | } 64 | }; -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | var _ = window, 2 | raf = (function() { 3 | return _.requestAnimationFrame || 4 | _.webkitRequestAnimationFrame || 5 | _.mozRequestAnimationFrame || 6 | 7 | function(c){ 8 | setTimeout(c, 1000 / 60); 9 | }; 10 | })(), 11 | M = Math, 12 | abs = M.abs, 13 | min = M.min, 14 | max = M.max, 15 | to = setTimeout, 16 | fps = 60; 17 | 18 | // Shortcuts 19 | var p = CanvasRenderingContext2D.prototype; 20 | p.fr = p.fillRect; 21 | p.sv = p.save; 22 | p.rs = p.restore; 23 | p.lt = p.lineTo; 24 | p.mt = p.moveTo; 25 | p.sc = p.scale; 26 | p.bp = p.beginPath; 27 | p.cp = p.closePath; 28 | p.rt = p.rotate; 29 | p.ft = p.fillText; 30 | p.bct = p.bezierCurveTo; 31 | p.qct = p.quadraticCurveTo; 32 | p.st = p.stroke; 33 | p.ar = p.arc; 34 | p.fl = p.fill; 35 | 36 | // ctx.ellipsis wont work in firefox 37 | p.el = function drawEllipseWithQuatraticCurve(ctx, x, y, w, h, style) { 38 | var kappa = .5522848, 39 | ox = (w / 2) * kappa, // control point offset horizontal 40 | oy = (h / 2) * kappa, // control point offset vertical 41 | xe = x + w, // x-end 42 | ye = y + h, // y-end 43 | xm = x + w / 2, // x-middle 44 | ym = y + h / 2; // y-middle 45 | 46 | sv(); 47 | bp(); 48 | mt(x, ym); 49 | qct(x,y,xm,y); 50 | qct(xe,y,xe,ym); 51 | qct(xe,ye,xm,ye); 52 | qct(x,ye,x,ym); 53 | ctx.strokeStyle = style ? style : '#000'; 54 | ctx.lineWidth = 2; 55 | st(); 56 | rs(); 57 | fl(); 58 | } 59 | 60 | p.fs = function(p){ 61 | this.fillStyle = P.inverted ? invert(p) : p; 62 | }; 63 | p.sts = function(p){ 64 | this.strokeStyle = P.inverted ? invert(p) : p; 65 | }; 66 | 67 | // Adding all these functions to the global scope 68 | for(var i in p){ 69 | _[i] = (function(f){ 70 | return function(){ 71 | c[f].apply(c, arguments); 72 | } 73 | })(i); 74 | } 75 | 76 | var P = { 77 | w: 640, 78 | h: 760, 79 | g: 800, 80 | fireOffset: 70, 81 | spikesOffset: 50, 82 | tbOffset: 20 83 | }; 84 | 85 | function canvasToImage() { 86 | G.dataURL = document.getElementById('game-canvas').toDataURL('image/png'); 87 | } 88 | 89 | function downloadCanvas() { 90 | var windowRef = _.open(); 91 | if (windowRef) { 92 | windowRef.document.write(''); 93 | } else { 94 | alert('Your browser prevented the window from opening. Please allow to view game screenshot.') 95 | } 96 | } 97 | 98 | addEventListener('DOMContentLoaded',function(){ 99 | _._can = document.querySelector('canvas'); 100 | new Game(); 101 | }); 102 | 103 | -------------------------------------------------------------------------------- /src/jsfxr.js: -------------------------------------------------------------------------------- 1 | /* Jsfxr lib for sound effects */ 2 | 3 | function J(){this.B=function(e){for(var f=0;24>f;f++)this[String.fromCharCode(97+f)]=e[f]||0;0.01>this.c&&(this.c=0.01);e=this.b+this.c+this.e;0.18>e&&(e=0.18/e,this.b*=e,this.c*=e,this.e*=e)}} 4 | var W=new function(){this.A=new J;var e,f,d,g,l,z,K,L,M,A,m,N;this.reset=function(){var c=this.A;g=100/(c.f*c.f+0.001);l=100/(c.g*c.g+0.001);z=1-0.01*c.h*c.h*c.h;K=1E-6*-c.i*c.i*c.i;c.a||(m=0.5-c.n/2,N=5E-5*-c.o);L=0a.q?-1020:1020),S=a.p?(2E4*(1-a.p)*(1-a.p)|0)+32:0,ba=a.d,T=a.j/2,ca=0.01*a.k*a.k,E=a.a,F=e,da=1/e,ea=1/f,fa=1/d,a=5/(1+20*a.u*a.u)*(0.01+n);0.8=S&&(V=0,this.reset());A&&++M>=A&&(A=0,g*=L);z+=K;g*=z;g>l&&(g=l,0<$&&(G=!0));h=g;0< 6 | T&&(I+=ca,h*=1+Math.sin(I)*T);h|=0;8>h&&(h=8);E||(m+=N,0>m?m=0:0.5F)switch(v=0,++U){case 1:F=f;break;case 2:F=d}switch(U){case 0:w=v*da;break;case 1:w=1+2*(1-v*ea)*ba;break;case 2:w=1-v*fa;break;case 3:w=0,G=!0}R&&(D+=aa,s=D|0,0>s?s=-s:1023r?r=1E-5:0.1=h&&(p%=h,3==E))for(x=y.length;x--;)y[x]=2*Math.random()-1;switch(E){case 0:b=p/hb?1.27323954*b+0.405284735*b*b:1.27323954*b-0.405284735*b*b;b=0>b?0.225*(b*-b-b)+b:0.225*(b*b-b)+b;break;case 3:b=y[Math.abs(32*p/h|0)]}P&&(x=u,n*=X,0>n?n=0:0.1=q?-32768:32767*q|0}return O}}; 8 | window.jsfxr=function(e){W.A.B(e);var f=W.D();e=new Uint8Array(4*((f+1)/2|0)+44);var f=2*W.C(new Uint16Array(e.buffer,44),f),d=new Uint32Array(e.buffer,0,44);d[0]=1179011410;d[1]=f+36;d[2]=1163280727;d[3]=544501094;d[4]=16;d[5]=65537;d[6]=44100;d[7]=88200;d[8]=1048578;d[9]=1635017060;d[10]=f;for(var f=f+44,d=0,g="data:audio/wav;base64,";d>18]+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[l>> 9 | 12&63]+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[l>>6&63]+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[l&63]);d-=f;return g.slice(0,g.length-d)+"==".slice(0,d)}; -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | zip = require('gulp-zip'), 3 | watch = require('gulp-watch'), 4 | clean = require('gulp-clean'), 5 | jshint = require('gulp-jshint'), 6 | concat = require('gulp-concat'), 7 | uglify = require('gulp-uglify'), 8 | cssmin = require('gulp-cssmin'), 9 | rename = require('gulp-rename'), 10 | htmlmin = require('gulp-htmlmin'), 11 | replace = require('gulp-replace-path'); 12 | 13 | var APP_NAME = 'save_the_forest'; 14 | var bases = { 15 | app: 'src/', 16 | dist: 'dist/', 17 | }; 18 | 19 | var paths = { 20 | scripts: [ 21 | 'src/utils.js', 22 | 'src/jsfxr.js', 23 | 'src/sound.js', 24 | 'src/flame.js', 25 | 'src/menu.js', 26 | 'src/weather.js', 27 | 'src/particles.js', 28 | 'src/background.js', 29 | 'src/player.js', 30 | 'src/tree.js', 31 | 'src/game.js', 32 | 'src/main.js' 33 | ], 34 | html: [ 'src/index.html' ], 35 | css: [ 'src/css/*.css' ], 36 | images: [ 'src/images/*.*' ] 37 | }; 38 | 39 | // Delete the dist directory 40 | gulp.task('clean', function() { 41 | return gulp.src(bases.dist, {read: false}) 42 | .pipe(clean()); 43 | }); 44 | 45 | // Process scripts and concatenate them into one output file 46 | gulp.task('scripts', ['clean'], function() { 47 | return gulp.src(paths.scripts) 48 | // .pipe(jshint()) 49 | // .pipe(jshint.reporter('default')) 50 | .pipe(concat('game.js')) 51 | .pipe(gulp.dest(bases.dist)) // for dev mode 52 | .pipe(uglify()) 53 | .pipe(rename({suffix: '.min'})) 54 | .pipe(gulp.dest(bases.dist)) // for prod 55 | }); 56 | 57 | // Copy all other files to dist directly 58 | gulp.task('copy', ['clean'], function() { 59 | // Copy images, maintaining the original directory structure 60 | return gulp.src(paths.images, {cwd: bases.app}) 61 | .pipe(gulp.dest(bases.dist)); 62 | }); 63 | 64 | gulp.task('uglify-html', ['clean'], function() { 65 | return gulp.src('src/*.html') 66 | .pipe(htmlmin({collapseWhitespace: true})) 67 | .pipe(replace(/..\/dist\//g, '')) 68 | .pipe(replace(/game.css/g, 'game.min.css')) 69 | .pipe(replace(/game.js/g, 'game.min.js')) 70 | .pipe(gulp.dest(bases.dist)); 71 | }); 72 | 73 | gulp.task('uglify-css', ['clean'], function () { 74 | return gulp.src('css/*.css', {cwd: bases.app}) 75 | .pipe(gulp.dest(bases.dist)) // for dev mode 76 | .pipe(cssmin()) 77 | .pipe(rename({suffix: '.min'})) 78 | .pipe(gulp.dest(bases.dist)); // for prod 79 | }); 80 | 81 | // Delete the dist directory 82 | gulp.task('zip', ['default'], function() { 83 | return gulp.src([ 'dist/**/*.min.*', 'dist/index.html' ]) 84 | .pipe(zip(APP_NAME + '.zip')) 85 | .pipe(gulp.dest('.')); 86 | }); 87 | 88 | gulp.task('watch' , function () { 89 | return gulp.watch([ 90 | paths.scripts, 91 | paths.html, 92 | paths.images, 93 | paths.css 94 | ], ['default']); 95 | }); 96 | 97 | // Define the default task as a sequence of the above tasks 98 | gulp.task('default', [ 99 | 'clean', 100 | 'copy', 101 | 'scripts', 102 | 'uglify-html', 103 | 'uglify-css' 104 | ]); 105 | -------------------------------------------------------------------------------- /src/tree.js: -------------------------------------------------------------------------------- 1 | var T, CC; 2 | var blw = 200, bw = 0; 3 | 4 | function Tree(config) { 5 | T = this; 6 | config = config || {}; 7 | // T.lw = T.w = 0; 8 | T.minW = 10; 9 | T.maxW = 80; 10 | T.minH = P.fireOffset; 11 | T.maxH = G.isMobile() ? 300 : 400; 12 | T.minDist = 50; 13 | T.maxDist = G.isMobile() ? 100 : 200; 14 | // T.branchThickness = 3; 15 | 16 | CC.w = utils.pI(G.can.width); 17 | CC.h = utils.pI(G.can.height); 18 | 19 | T.color = '#a77b44'; 20 | this.add(); 21 | if (!config.isNoFlame) { 22 | if (G.isMobile()) { 23 | this.flame = true; 24 | } else { 25 | this.flame = smoky; 26 | this.flame.addEntity(Flame); 27 | } 28 | } 29 | return T; 30 | } 31 | 32 | Tree.prototype = { 33 | /*drawFractalTree: function (x, y, width, height) { 34 | T.drawTree(x, y, width, height, -90, T.branchThickness); 35 | }, 36 | drawTree: function (x1, y1, width, height, angle, depth){ 37 | T.brLength = T.brLength || T.random(T.minW, T.maxW); 38 | T.angle = T.angle || T.random(15, 20); 39 | T.bb = (T.cos(angle) * depth * T.brLength); 40 | T.vv = (T.sin(angle) * depth * T.brLength); 41 | if (depth != 0){ 42 | var x2 = x1 + T.bb; 43 | var y2 = y1 - T.vv; 44 | 45 | T.drawLine(x1, y1, x2, y2, depth); 46 | 47 | T.drawTree(x2, y2, width, height, angle - T.angle, depth - 1); 48 | T.drawTree(x2, y2, width, height, angle + T.angle, depth - 1); 49 | // T.drawLine(x1, y1, x2, y2, depth); 50 | } 51 | }, 52 | random: function (min, max){ 53 | return min + Math.floor(Math.random()*(max+1-min)); 54 | }, 55 | drawLine: function (x1, y1, x2, y2, thickness){ 56 | ctx.fillStyle = '#000'; 57 | if(thickness > 2) 58 | ctx.strokeStyle = 'rgb(139,126, 102)'; //Brown 59 | else 60 | ctx.strokeStyle = 'rgb(34,139,34)'; //Green 61 | ctx.lineWidth = thickness * 1.5; 62 | bp(); 63 | mt(x1, y1); 64 | lt(x2, y2); 65 | cp(); 66 | st(); 67 | 68 | }, 69 | cos: function (angle) { 70 | return M.cos(T.deg_to_rad(angle)); 71 | }, 72 | sin: function (angle) { 73 | return M.sin(T.deg_to_rad(angle)); 74 | }, 75 | deg_to_rad: function (angle){ 76 | return angle*(M.PI/180.0); 77 | },*/ 78 | getWidth: function (val) { 79 | if (val !== undefined) { 80 | return val; 81 | } 82 | return utils.getRandomInt(T.minW, T.maxW); 83 | }, 84 | getHeight: function (val) { 85 | if (val !== undefined) { 86 | return val; 87 | } 88 | return utils.getRandomInt(T.minH, T.maxH); 89 | }, 90 | add: function (val) { 91 | T.preCompute(); 92 | T.x = blw + bw; 93 | T.y = CC.h - T.h - (P.fireOffset * 0.6), 94 | T.width = bw, 95 | T.height = T.h; 96 | // T.drawFractalTree(T.x, T.y, T.width, T.height) 97 | 98 | //T.update(T); 99 | return T; 100 | }, 101 | update: function (treeInstance) { 102 | var x = treeInstance.x, 103 | y = treeInstance.y, 104 | width = treeInstance.width, 105 | height = treeInstance.height; 106 | 107 | sv(); 108 | fs(T.color); 109 | 110 | bp(); 111 | mt(x, y); 112 | // left side 113 | bct(x , y + height, x - 25, y + height, x - 25, y + height); 114 | 115 | // left bottom curve 116 | bct(x, y + height, x + (width / 2), y + height / 1.2, x + (width / 2), y + (height / 1.2)) 117 | // right bottom curve 118 | bct(x + (width / 2), y + (height / 1.2), x + (width / 2), y + height / 1.2, x + width + 25, y + height); 119 | 120 | // right side 121 | bct(x + width, y + height, x + width, y, x + width, y); 122 | 123 | ctx.shadowColor = '#6b4e2a'; 124 | ctx.shadowOffsetX = -3; 125 | ctx.shadowOffsetY = 3; 126 | ctx.shadowBlur = 10; 127 | ctx.strokeStyle = '#6b4e2a'; 128 | ctx.lineWidth = 1; 129 | st(); 130 | cp(); 131 | fl(); 132 | rs(); 133 | 134 | fs('#444') 135 | el(ctx, x, y - 4, width, 10, '#6b4e2a'); 136 | 137 | if (treeInstance.flame) { 138 | if (G.isMobile()) { 139 | T.addCircle(x, y, width); 140 | } else { 141 | treeInstance.flame.update(x, y, width); 142 | } 143 | } 144 | }, 145 | addCircle: function (x, y, width) { 146 | bp(); 147 | ar(x + (width/2), y, width/2, 0, Math.PI*2, false); 148 | fs('rgba(255, 0, 0, 0.4)'); 149 | fl(); 150 | 151 | bp(); 152 | ar(x + (width/2), y, width/3, 0, Math.PI*2, false); 153 | fs('rgba(255, 165, 0, 0.4)'); 154 | fl(); 155 | 156 | bp(); 157 | ar(x + (width/2), y, width/6, 0, Math.PI*2, false); 158 | fs('rgba(255, 255, 0, ' + utils.getRandomInt(0.3, 0.5)/10 + ')'); 159 | fl(); 160 | }, 161 | preCompute: function () { 162 | T.lw = blw + bw + (bw === 0 ? 0 : utils.getRandomInt(T.minDist, T.maxDist)); 163 | blw = T.lw; 164 | T.w = utils.getRandomInt(T.minW, T.maxW); 165 | bw = T.w; 166 | T.h = utils.getRandomInt(T.minH, T.maxH); 167 | // console.log(blw, bw) 168 | // T.rw = CC.w - T.lw - T.w; 169 | }, 170 | removeFlame: function (that) { 171 | that.flame = undefined; 172 | } 173 | }; 174 | 175 | -------------------------------------------------------------------------------- /src/flame.js: -------------------------------------------------------------------------------- 1 | var SF, Flame, H, PI_2, Smoke, Trail, W, SmokyFlame, drawCircle, rand, w, slice = [].slice; 2 | 3 | PI_2 = 2 * Math.PI; 4 | rand = function (a, b) { 5 | return (b - a) * Math.random() + a; 6 | }; 7 | drawCircle = function (x, y, r, style) { 8 | bp(); 9 | ar(x, y, r, 0, PI_2, false); 10 | ctx.fillStyle = style; 11 | return fl(); 12 | }; 13 | 14 | Smoke = function () { 15 | function Smoke(x, y) { 16 | this.opacity = 0.8; 17 | this.x = x; 18 | this.y = y; 19 | this.r = 2.0; 20 | } 21 | Smoke.prototype.step = function (x, y, w) { 22 | y -= utils.getRandomInt(rand(60,70), rand(200,350)) 23 | // y -= rand(0, 3); 24 | x += rand(-2, 2); 25 | this.opacity -= 0.04; 26 | if (this.opacity <= 0) { 27 | return this.destroyed = true; 28 | } 29 | }; 30 | Smoke.prototype.draw = function (x, y, w) { 31 | y -= utils.getRandomInt(60, 150) 32 | x += utils.getRandomInt(rand(-w+w/2, 0), rand(0,w-w/2)) 33 | if (this.opacity <= 0) { 34 | return; 35 | } 36 | return drawCircle(x, y, this.r, 'rgba(60,60,60,' + this.opacity + ')'); 37 | }; 38 | return Smoke; 39 | }(); 40 | Trail = function () { 41 | function Trail(x, y) { 42 | this.opacity = 1; 43 | this.x = x; 44 | this.y = y; 45 | this.r = 12; 46 | } 47 | Trail.prototype.step = function (x, y, w) { 48 | this.r = w / 5; 49 | y -= rand(0, 8); 50 | x -= rand(-3, 3); 51 | this.opacity -= 0.03; 52 | if (this.opacity <= 0) { 53 | this.destroyed = true; 54 | if (rand(0, 1) < 0.5) { 55 | return SF.addEntity(Smoke, x, y - this.r); 56 | } 57 | } 58 | }; 59 | Trail.prototype.draw = function (x, y, w) { 60 | this.r = w / 6; 61 | y -= rand(rand(-45, 5), rand(25, 75)); 62 | x -= rand(-w/2 - 20, w/2 + 20); 63 | var color, color2, g, rg; 64 | if (this.opacity <= 0) { 65 | return; 66 | } 67 | color = 'rgba(255,' + ~~(240 * this.opacity) + ',0,' + this.opacity + ')'; 68 | color2 = 'rgba(255,' + ~~(240 * this.opacity) + ',0,0)'; 69 | rg = this.r * 1.5 + rand(0, 2); 70 | g = ctx.createRadialGradient(x, y, 0, x, y, rg); 71 | g.addColorStop(0, color); 72 | g.addColorStop(1, color2); 73 | drawCircle(x, y, this.r, g); 74 | return drawCircle(x, y, this.r * this.opacity, color); 75 | }; 76 | return Trail; 77 | }(); 78 | Flame = function () { 79 | function Flame() { 80 | this.x = G.can.width / 2; 81 | this.y = G.can.height / 2 + 90; 82 | this.r = 24; 83 | this.rg = 22; 84 | } 85 | Flame.prototype.step = function (x, y, w) { 86 | return false; 87 | }; 88 | Flame.prototype.draw = function (x, y, w) { 89 | this.g = ctx.createRadialGradient(x, y, 0, x, y, w * 1.2); 90 | this.g.addColorStop(0, 'rgba(255,255,255,1)'); 91 | this.g.addColorStop(1, 'rgba(255,120,0,0)'); 92 | 93 | 94 | var g, i, j; 95 | //for (i = j = 1; j <= 1; i = ++j) { 96 | SF.addEntity(Trail, x, y - this.r / 3); 97 | //} 98 | g = ctx.createRadialGradient(x, y, 0, x, y, this.rg); 99 | g.addColorStop(0, 'rgba(255,180,0,' + rand(0.2, 0.9) + ')'); 100 | g.addColorStop(1, 'rgba(255,180,0,0)'); 101 | drawCircle(x, y, this.rg, g); 102 | return drawCircle(x + rand(-1.5, 1.5), y + rand(-1.5, 1.5), w, this.g ); 103 | }; 104 | return Flame; 105 | }(); 106 | 107 | SmokyFlame = function () { 108 | function SmokyFlame() { 109 | SF = this; 110 | this.entities = {}; 111 | this.i = 0; 112 | this.ii = 0; 113 | // this.update(); 114 | } 115 | SmokyFlame.prototype.addEntity = function () { 116 | var args, klass; 117 | klass = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; 118 | this.entities[this.i] = function (func, args, ctor) { 119 | ctor.prototype = func.prototype; 120 | var child = new ctor(), result = func.apply(child, args); 121 | return Object(result) === result ? result : child; 122 | }(klass, args, function () { 123 | }); 124 | return this.i += 1; 125 | }; 126 | SmokyFlame.prototype.update = function (x, y, w) { 127 | var entity, k, ref; 128 | // ctx.clearRect(0, 0, W, H); 129 | ref = this.entities; 130 | for (k in ref) { 131 | entity = ref[k]; 132 | if (entity.destroyed === true) { 133 | delete this.entities[k]; 134 | continue; 135 | } 136 | 137 | if (this.ii % 5 === 0) { 138 | entity.step(x + (w / 2), y - 10, w); 139 | entity.draw(x + (w / 2), y - 10, w); 140 | this.ii = 0; 141 | } 142 | this.ii++; 143 | } 144 | }; 145 | return SmokyFlame; 146 | }(); -------------------------------------------------------------------------------- /src/player.js: -------------------------------------------------------------------------------- 1 | var Pl; 2 | function Player() { 3 | Pl = this; 4 | Pl.liesOn = 0; 5 | Pl.maxH = 66; 6 | Pl.bounceHeight = 5; 7 | Pl.w = 20; 8 | Pl.h = Pl.maxH; 9 | Pl.x = G.trees[0].x; 10 | Pl.y = G.trees[0].y - Pl.h; 11 | Pl.vel = 0; 12 | Pl.isJet = false; 13 | Pl.isRest = true; 14 | Pl.t = new Date(); 15 | Pl.update(); 16 | return Pl; 17 | } 18 | 19 | Player.prototype = { 20 | tears: function (x, y) { 21 | if (Pl.died) return; 22 | bp(); 23 | ctx.fillStyle = '#36b1f7'; 24 | mt(Pl.x + Pl.w + x - 3, Pl.y + y); 25 | lt(Pl.x + Pl.w + x, Pl.y + y - 4); 26 | lt(Pl.x + Pl.w + x + 3, Pl.y + y); 27 | ar(Pl.x + Pl.w + x, Pl.y + y, 3, 0, Math.PI); 28 | cp(); 29 | fl(); 30 | }, 31 | body: function () { 32 | sv(); 33 | ctx.shadowColor = '#000'; 34 | ctx.shadowOffsetX = -2; 35 | ctx.shadowOffsetY = 2; 36 | ctx.shadowBlur = 10; 37 | if (Pl.died) { 38 | fr(Pl.x - 2 * (Pl.h / 3), Pl.y, (2 * Pl.h) / 3, Pl.w); 39 | rs(); 40 | } else { 41 | fr(Pl.x, Pl.y, Pl.w, (2 * Pl.h) / 3); 42 | rs(); 43 | // shade 44 | fs('#777') 45 | fr(Pl.x + 3, Pl.y + 15, 3, 2 * (Pl.h / 3) - 30); 46 | } 47 | rs(); 48 | }, 49 | eyes: function () { 50 | if (!Pl.w && !Pl.h) return; 51 | 52 | var ct = new Date(), clr; 53 | // 0 means NO, 1 means starts blinking, 2 means blinked 54 | Pl.isBlink = ct - Pl.t > 2500 ? ++Pl.isBlink : 0; 55 | clr = Pl.isBlink > 5 ? '#000' : '#fff'; 56 | 57 | if (Pl.isBlink >= 8) { 58 | Pl.t = ct; Pl.isBlink = 0; 59 | } 60 | //fs(clr); 61 | bp(clr); 62 | 63 | if (Pl.died) { 64 | ar(Pl.x - 2 * (Pl.h / 3) + 6, Pl.y + 4, 2, 0, M.PI * 2, true); 65 | ar(Pl.x - 2 * (Pl.h / 3) + 6, Pl.y + 10, 2, 0, M.PI * 2, true); 66 | } else { 67 | ar(Pl.x + 2 * (Pl.w / 3) - 2, Pl.y + 5, 2, 0, M.PI * 2, true); 68 | ar(Pl.x + (Pl.w - 3), Pl.y + 5, 2, 0, M.PI * 2, true); 69 | } 70 | ctx.fillStyle = clr;//'#8effb6'; 71 | fl(); 72 | }, 73 | legs: function () { 74 | var lw = 3; // leg width 75 | fs('#000'); 76 | if (Pl.died) { 77 | // left leg 78 | fr(Pl.x, Pl.y + (Pl.w / 3), Pl.h / 3, lw); 79 | // right leg 80 | fr(Pl.x, Pl.y + 2 * (Pl.w / 3), Pl.h / 3, lw); 81 | } else { 82 | // left leg 83 | fr(Pl.x + (Pl.w / 3) - lw / 2, Pl.y + 2 * (Pl.h / 3), lw, Pl.h / 3); 84 | // right leg 85 | fr(Pl.x + 2 * (Pl.w / 3) - lw / 2, Pl.y + 2 * (Pl.h / 3), lw, Pl.h / 3); 86 | } 87 | }, 88 | fe: function () { // Fire Extinguisher 89 | fs('#eaeaea'); 90 | bp(); 91 | el(ctx, Pl.x - 30, Pl.y + Pl.h, 80, 10, '#000') 92 | cp(); 93 | fs('#EAEAEA'); 94 | fl(); 95 | ctx.lineWidth = 1; 96 | sts('#dedede'); 97 | st(); 98 | fs('#8ED6FF'); 99 | for (var i = -30; i < Pl.w + 30; i+=6) { 100 | bp(); 101 | mt(Pl.x + i, Pl.y + Pl.h); 102 | lt(Pl.x + i - 1, Pl.y + Pl.h + utils.getRandomInt(10, G.can.height/2)); 103 | lt(Pl.x + i + 3, Pl.y + Pl.h + utils.getRandomInt(10, G.can.height/2)); 104 | lt(Pl.x + i + 1, Pl.y + Pl.h); 105 | cp(); 106 | fl(); 107 | } 108 | 109 | }, 110 | burst: function () { 111 | // Particle effects 112 | if (Pl.w && Pl.h) { 113 | this.dieParticles = new Particles(Pl.x, Pl.y); 114 | } else if (PS.finished) { 115 | G.stopCycle(); 116 | PS.finished = false; 117 | } 118 | 119 | if (!Pl.w && !Pl.h) { 120 | this.dieParticles.draw(); 121 | } 122 | 123 | Pl.w = 0; 124 | Pl.h = 0; 125 | }, 126 | update: function () { 127 | Pl.x -= G.speed; 128 | if (Pl.isInAir && Pl.bounceFactor > 0) { 129 | Pl.y -= Pl.bounceHeight; 130 | Pl.x += 1.5 * G.speed; 131 | Pl.bounceFactor -= 2; 132 | } else if (Pl.isInAir && !Pl.bounceFactor) { // smooth curve parabola jump 133 | Pl.y -= Pl.bounceHeight / 2; 134 | Pl.x += 1.5 * G.speed; 135 | Pl.bounceFactor -= 2; 136 | } else if (Pl.isInAir && Pl.bounceFactor === -2) { // smooth curve parabola jump 137 | Pl.y -= 0; 138 | Pl.x += 1.5 * G.speed; 139 | Pl.bounceFactor -= 2; 140 | } else if (Pl.isInAir && Pl.bounceFactor === -4) { // smooth curve parabola jump 141 | Pl.y -= Pl.bounceHeight / 2; 142 | Pl.x += 1.5 * G.speed; 143 | Pl.bounceFactor -= 2; 144 | } else if (Pl.isInAir && Pl.bounceFactor === -6) { // revert back force rewind 145 | Pl.y += Pl.bounceHeight; 146 | Pl.x += 1.5 * G.speed; 147 | Pl.bounceFactor = -6; 148 | } 149 | 150 | if (Pl.died && !Pl.busted) { 151 | Pl.y += 10; 152 | if (Pl.isCornerStrike) { 153 | if (Pl.y > CC.h - 3*P.fireOffset) { 154 | if (!Pl.busted) { 155 | Pl.busted = true; 156 | } 157 | } 158 | } else if (!Pl.busted) { 159 | Pl.busted = true; 160 | } 161 | } 162 | 163 | fs('#000'); 164 | Pl.body(); 165 | Pl.legs(); 166 | Pl.eyes(); 167 | Pl.tears(2, 6); 168 | Pl.tears(5, 15); 169 | if (Pl.isInAir && !Pl.busted) Pl.fe(); 170 | if (Pl.busted) { 171 | Pl.burst(); 172 | } 173 | Pl.checkCollision(); 174 | }, 175 | keyDown: function (key) { 176 | if (Pl.busted) { return; } 177 | 178 | if (key === 32) { // 32 is space,38 is UP, 40 is DOWN 179 | Pl.irj = true; // isReadyToJump 180 | if (Pl.h < 50) { return; } 181 | Pl.h -= 2; 182 | Pl.y += 2; 183 | } else if (key === 39) { 184 | Pl.x += G.speed; 185 | } 186 | 187 | if (Pl.irj) { 188 | Pl.irj = false; 189 | Pl.isInAir = true; 190 | Pl.isKarmaLocked = false; 191 | Pl.bounceFactor = Pl.maxH - Pl.h; 192 | Pl.bounceFactor *= 4; 193 | Pl.h = Pl.maxH; 194 | } 195 | }, 196 | keyUp: function () { 197 | /*if (Pl.irj) { 198 | Pl.irj = false; 199 | Pl.isInAir = true; 200 | SU.play('moveAhead'); 201 | Pl.bounceFactor = Pl.maxH - Pl.h; 202 | Pl.bounceFactor *= 4; 203 | Pl.h = Pl.maxH; 204 | }*/ 205 | }, 206 | checkCollision: function () { 207 | if (Pl.x <= 0) { // leftmost collision 208 | Pl.died = true; 209 | Pl.isCornerStrike = true; 210 | return; 211 | } else if (Pl.y > CC.h - P.fireOffset) { // bottom fire collision 212 | Pl.died = true; 213 | Pl.isCornerStrike = true; 214 | return; 215 | } else if (Pl.x + Pl.w > CC.w) { // rightmost collision 216 | Pl.died = true; 217 | Pl.isCornerStrike = true; 218 | return; 219 | } else if (Pl.y < 0 ) { // topmost collision 220 | Pl.died = true; 221 | Pl.isCornerStrike = true; 222 | return; 223 | } 224 | 225 | var i, tree; 226 | for (i = 0; i < G.trees.length; i++) { // M.min(Pl.liesOn + 10, ) 227 | tree = G.trees[i]; 228 | 229 | var playerEnd = Pl.x + Pl.w - 4; // 4 bcz legs are placed (x coord)technically before body 230 | if ((playerEnd >= tree.x && playerEnd < (tree.x + tree.width + 4) && Pl.y + Pl.w + Pl.h >= tree.x) 231 | ) { 232 | for (var j = 0; j < Pl.bounceHeight; j++) { 233 | if (Pl.y + Pl.h + j >= tree.y) { 234 | G.trees[i].flame = null; 235 | Pl.isInAir = false; 236 | Pl.liesOn = i; 237 | if (!Pl.isKarmaLocked && Pl.liesOn !== Pl.lastLiesOn) { 238 | G.karma && SU.play('moveAhead'); 239 | G.karma += 1; 240 | } 241 | Pl.isKarmaLocked = true;; 242 | Pl.lastLiesOn = Pl.liesOn; 243 | // Pl.y += tree.y - (Pl.y + Pl.h) - 2; 244 | break; 245 | } 246 | } 247 | if (Pl.y >= tree.y && Pl.y < tree.y + tree.height) { 248 | Pl.died = true; 249 | break; 250 | } 251 | 252 | } 253 | } 254 | } 255 | }; -------------------------------------------------------------------------------- /src/game.js: -------------------------------------------------------------------------------- 1 | var G, ctx, CC, background, player, weather, smoky; 2 | function Game() { 3 | G = this; 4 | G.isInProgress = true; 5 | G.canSpeedBeIncreased = G.canExplode = true; 6 | G.backgroundColor = '#fff'; 7 | 8 | G.karma = 0; 9 | 10 | G.highscore = utils.getLocalStorageData() || 0; 11 | G.isSound = utils.getLocalStorageData(true); 12 | if (G.isSound !== 0) { 13 | G.isSound = 1; 14 | } 15 | 16 | G.resolution = 1; 17 | G.curPos = []; 18 | 19 | G.can = document.querySelector('canvas'); 20 | G.can.width = P.w; 21 | G.can.height = P.h; 22 | 23 | ctx = G.ctx = window.c = G.can.getContext('2d'); 24 | 25 | G.trees = []; 26 | 27 | // Resizing 28 | G.resize(); 29 | addEventListener('resize', G.resize, false); 30 | 31 | CC = document.getElementById('canvascontainer').style; 32 | 33 | document.body.addEventListener('touchstart', G.touchStart.bind(G), false); 34 | document.body.addEventListener('touchmove', G.touchMove.bind(G), false); 35 | document.body.addEventListener('touchend', G.touchEnd.bind(G), false); 36 | document.body.addEventListener('mousedown', G.mouseDown.bind(G), false); 37 | document.body.addEventListener('mousemove', G.mouseMove.bind(G), false); 38 | document.body.addEventListener('mouseup', G.mouseUp.bind(G), false); 39 | 40 | document.body.addEventListener('keydown', G.keyDown.bind(G), false); 41 | document.body.addEventListener('keyup', G.keyUp.bind(G), false); 42 | 43 | // Loop 44 | G.frameCount = 0; 45 | G.lastFrame = G.frameCountStart = Date.now(); 46 | 47 | var displayablePixels = _.innerWidth * _.innerHeight * _.devicePixelRatio, 48 | gamePixels = P.w * P.h, 49 | ratio = displayablePixels / gamePixels; 50 | 51 | if (ratio < 0.5){ 52 | G.setResolution(ratio * 2); 53 | } 54 | 55 | G.speed = 1; 56 | 57 | // background animation 58 | // background = new Background(); 59 | flameBack.canvas = G.can; 60 | //flameBack.init(); 61 | 62 | G.menu = true; 63 | } 64 | 65 | var tree, time; 66 | Game.prototype = { 67 | restart: function () { 68 | G.isGameOver = false; 69 | G.isInProgress = true; 70 | G.karma = 0; 71 | G.speed = 1; 72 | G.gameStartTime = new Date().getTime(); 73 | 74 | smoky = new SmokyFlame(); 75 | 76 | blw = 200, bw =0; 77 | G.addInitialtrees(); 78 | 79 | player = new Player(); 80 | Pl.x = G.trees[0].x; 81 | 82 | flameBack.init(); 83 | weather = new Weather(); 84 | G.raf = raf(function(){ 85 | if (G.raf) { 86 | G.cycle(); 87 | raf(arguments.callee); 88 | } 89 | }); 90 | }, 91 | stopCycle: function () { 92 | G.isGameOver = true; 93 | G.isInProgress = false; 94 | 95 | flameBack.update(); 96 | canvasToImage(); // get image before spash screen 97 | 98 | // console.log('Boom! DIE!'); 99 | // update high score 100 | if (G.karma > G.highscore) { 101 | SU.play('highestScore'); 102 | G.highscore = G.karma; 103 | utils.setLocalStorageData(G.karma); 104 | } 105 | 106 | SU.play('gameOver'); 107 | 108 | G.menu = new Menu(); 109 | }, 110 | cycle: function () { 111 | var now = new Date().getTime(); 112 | dt = now - time; 113 | 114 | if (dt < (1000 / fps)) 115 | return; // skip a frame 116 | 117 | //SU.play('game'); 118 | time = now; 119 | if (G.menu) { 120 | G.menu.update && G.menu.update(); 121 | return; 122 | } 123 | 124 | if (G.canExplode && M.ceil((now - G.gameStartTime) / 1000) % 6 === 0) { 125 | G.mildExplosion ? SU.play('explosion2') : SU.play('explosion1'); 126 | G.mildExplosion = !G.mildExplosion; 127 | G.canExplode = false; 128 | } else if (M.ceil((now - G.gameStartTime) / 1000) % 7 === 0) { 129 | G.canExplode = true; 130 | } 131 | 132 | if (G.canSpeedBeIncreased && M.ceil((now - G.gameStartTime) / 1000) % 10 === 0) { 133 | G.speed += G.isMobile() ? 0.1 : 0.2; 134 | WD.speed = utils.getRandomInt(1, 30); 135 | G.canSpeedBeIncreased = false; 136 | } else if (M.ceil((now - G.gameStartTime) / 1000) % 11 === 0) { 137 | // G.speed += 0.1; 138 | G.canSpeedBeIncreased = true; 139 | } 140 | 141 | fs(G.backgroundColor); 142 | fr(0, 0, CC.w, CC.h); 143 | 144 | var speedIncFactor = G.isMobile() ? 1.1 : 1.6; 145 | if (G.speed >= speedIncFactor && 146 | utils.getRandomInt(0, 10) === 10 147 | ) { 148 | G.showNoisyScreen(); 149 | utils.getRandomInt(0, 10) === 4 && SU.play('glitch'); 150 | } 151 | 152 | //background.burnBurnBurn(); 153 | weather.update(); 154 | 155 | ctx.font = '15px Comic Sans'; 156 | ctx.fillStyle = thisWeather.hexToRgb(thisWeather.getColor(true), 1.0); 157 | ctx.fillText('KARMA: ' + G.karma, 25, 25); 158 | ctx.fillText('SPEED: ' + G.speed.toFixed(1) + ' mph', G.can.width - 130, 25); 159 | ctx.fillText('WIND: ' + WD.speed.toFixed(1) + ' mph W', G.can.width - 130, 45); 160 | ctx.lineWidth = 3; 161 | 162 | if (G.trees.length) { 163 | for (var i = 0; i < G.trees.length; i++) { 164 | G.trees[i].x -= G.speed; 165 | G.trees[i].update(G.trees[i]); 166 | 167 | if (G.trees[i].x < 0 - G.trees[i].width) { 168 | G.trees[i] = new Tree(); 169 | } 170 | } 171 | player.update(); 172 | } 173 | flameBack.update(); 174 | }, 175 | showNoisyScreen: function () { 176 | var w = G.can.width, 177 | h = G.can.height, 178 | idata = ctx.createImageData(w, h), 179 | buffer32 = new Uint32Array(idata.data.buffer), 180 | len = buffer32.length, 181 | i = 0; 182 | 183 | for (; i < len;) { 184 | buffer32[i++] = ((255 * Math.random())|0) << 24; 185 | } 186 | 187 | ctx.putImageData(idata, 0, 0); 188 | }, 189 | addInitialtrees: function () { 190 | G.trees = []; 191 | G.trees.push(new Tree({isNoFlame: true})) 192 | for (var i = 0; i < 5; i++) { 193 | G.trees.push(new Tree()) 194 | } 195 | }, 196 | resize: function() { 197 | setTimeout(function(){ 198 | var maxWidth = innerWidth, 199 | maxHeight = innerHeight, 200 | 201 | availableRatio = maxWidth / maxHeight, 202 | baseRatio = P.w / P.h, 203 | ratioDifference = abs(availableRatio - baseRatio), 204 | width, 205 | height, 206 | s = document.getElementById('canvascontainer').style; 207 | 208 | if (availableRatio <= baseRatio){ 209 | width = maxWidth; 210 | height = maxHeight;//width / baseRatio; 211 | } else{ 212 | height = maxHeight; 213 | width = height * baseRatio; 214 | } 215 | 216 | s.width = width + 'px'; 217 | s.height = height + 'px'; 218 | 219 | ctx.globalCompositeOperation="lighter"; 220 | 221 | G.can.width = width; 222 | G.can.height = height; 223 | 224 | 225 | if (G.menu) { 226 | G.menu = new Menu(); 227 | G.raf = raf(function(){ 228 | if (G.raf) { 229 | G.cycle(); 230 | raf(arguments.callee); 231 | } 232 | }); 233 | return; 234 | } 235 | 236 | G.restart(); 237 | 238 | },100); 239 | }, 240 | isMobile: function () { 241 | if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i 242 | .test(navigator.userAgent)) { 243 | return true; 244 | } 245 | return false; 246 | }, 247 | pos : function(e){ 248 | var rect = G.can.getBoundingClientRect(), 249 | pos = []; 250 | 251 | e = e.touches || [e]; 252 | 253 | for(var i = 0 ; i < e.length ; i++){ 254 | pos.push({ 255 | x : (e[i].clientX),// - rect.left) / (rect.width / P.w), 256 | y : (e[i].clientY)// - rect.top) / (rect.height / P.h) 257 | }) 258 | } 259 | 260 | return pos; 261 | }, 262 | touchStart : function(e,m) { 263 | e.preventDefault(); 264 | G.touch = G.touch || !m; 265 | var p = G.pos(e); 266 | G.curPos = p; 267 | 268 | scrollTo(0, 1); 269 | 270 | if (G.menu) { 271 | var x = G.curPos[0].x - G.can.offsetLeft, 272 | y = G.curPos[0].y - G.can.offsetTop; 273 | 274 | G.menu.mouseDown && G.menu.mouseDown(e, x, y); 275 | } else { 276 | // G.isTouching = true; 277 | G.keyDown({keyCode: 32}); 278 | } 279 | 280 | if(!G.isInProgress) return; 281 | 282 | //G.world.touchStart(); 283 | }, 284 | touchMove : function(e) { 285 | e.preventDefault(); 286 | if (G.curPos){ 287 | G.curPos = G.pos(e); 288 | 289 | if(!G.isInProgress) return; 290 | //G.world.touchMove(); 291 | } 292 | }, 293 | touchEnd : function(e) { 294 | e.preventDefault(); 295 | 296 | var p = G.curPos[0]; 297 | G.curPos = G.pos(e); 298 | 299 | if (!G.isInProgress) { 300 | //!G.isInProgress.click(p.x, p.y); 301 | } else { 302 | //G.world.touchEnd(); 303 | } 304 | }, 305 | keyDown: function(e) { 306 | // 13 is enter 307 | if ((e.keyCode === 13 || e.keyCode === 32) && G.menu) { 308 | G.menu = null; 309 | G.restart(); 310 | SU.play('playGame'); 311 | return; 312 | } 313 | if (!G.isInProgress) { 314 | return; 315 | } 316 | 317 | // 39 is right, 40 is down, 38 is up 318 | if (e.keyCode === 39 || e.keyCode === 38 || e.keyCode === 32) { 319 | player && player.keyDown(e.keyCode); 320 | } 321 | }, 322 | keyUp: function(e) { 323 | if(!G.isInProgress) return; 324 | player && player.keyUp(e.keyCode); 325 | }, 326 | mouseDown: function(e) { 327 | /*if(!G.touch){ 328 | G.touchStart(e, true); 329 | }*/ 330 | if (G.menu) { 331 | var x = e.pageX - G.can.offsetLeft, 332 | y = e.pageY - G.can.offsetTop; 333 | 334 | G.menu.mouseDown && G.menu.mouseDown(e, x, y); 335 | } 336 | }, 337 | mouseMove: function(e) { 338 | /*if(!G.touch){ 339 | G.touchMove(e); 340 | }*/ 341 | }, 342 | mouseUp: function(e) { 343 | /*if(!G.touch){ 344 | G.touchEnd(e); 345 | }*/ 346 | }, 347 | setResolution: function(r) { 348 | G.can.width = P.w * r; 349 | G.can.height = P.h * r; 350 | 351 | G.resolution = r; 352 | } 353 | } -------------------------------------------------------------------------------- /src/background.js: -------------------------------------------------------------------------------- 1 | /*var BG; 2 | function Background() { 3 | BG = this; 4 | BG.animate(); 5 | } 6 | 7 | Background.prototype = { 8 | burnBurnBurn: function() { 9 | var x, y, bottomLine = BG.canvasWidth * (BG.canvasHeight - 1); 10 | 11 | // draw random pixels at the bottom line 12 | for (x = 0; x < BG.canvasWidth; x++) { 13 | var value = 0; 14 | 15 | if (Math.random() > BG.threshold) 16 | value = 255; 17 | 18 | BG.fire[bottomLine + x] = value; 19 | } 20 | 21 | // move flip upwards, start at bottom 22 | var value = 0; 23 | 24 | for (y = 0; y < BG.canvasHeight; ++y) { 25 | for (var x = 0; x < BG.canvasWidth; ++x) { 26 | if (x == 0) { 27 | value = BG.fire[bottomLine]; 28 | value += BG.fire[bottomLine]; 29 | value += BG.fire[bottomLine - BG.canvasWidth]; 30 | value /= 3; 31 | } else if (x == BG.canvasWidth -1) { 32 | value = BG.fire[bottomLine + x]; 33 | value += BG.fire[bottomLine - BG.canvasWidth + x]; 34 | value += BG.fire[bottomLine + x - 1]; 35 | value /= 3; 36 | } else { 37 | value = BG.fire[bottomLine + x]; 38 | value += BG.fire[bottomLine + x + 1]; 39 | value += BG.fire[bottomLine + x - 1]; 40 | value += BG.fire[bottomLine - BG.canvasWidth + x]; 41 | value /= 4; 42 | } 43 | 44 | if (value > 1) 45 | value -= 1; 46 | 47 | value = Math.floor(value); 48 | var index = bottomLine - BG.canvasWidth + x; 49 | BG.fire[index] = value; 50 | } 51 | 52 | bottomLine -= BG.canvasWidth; 53 | } 54 | 55 | var skipRows = 1; // skip the bottom 2 rows 56 | 57 | // render the flames using our color table 58 | for (var y = skipRows; y < BG.canvasHeight; ++y) { 59 | for (var x = 0; x < BG.canvasWidth; ++x) { 60 | var index = y * BG.canvasWidth * 4 + x * 4; 61 | var value = BG.fire[(y - skipRows) * BG.canvasWidth + x]; 62 | 63 | BG.data[index] = BG.colors[value][0]; 64 | BG.data[++index] = BG.colors[value][1]; 65 | BG.data[++index] = BG.colors[value][2]; 66 | BG.data[++index] = 255; 67 | } 68 | } 69 | 70 | // sometimes change BG.fire intensity 71 | if (BG.intensity == null) { 72 | if (Math.random() > 0.95) { 73 | BG.randomizeThreshold(); 74 | } 75 | } 76 | 77 | BG.ctx.putImageData(BG.imageData, 0, BG.CC.height - BG.canvasHeight); 78 | 79 | }, 80 | randomizeThreshold: function() { 81 | BG.threshold += Math.random() * 0.2 - 0.1; 82 | BG.threshold = Math.min(Math.max(BG.threshold, 0.5), 0.8); 83 | }, 84 | animate: function () { 85 | BG.intensity = null; 86 | BG.threshold = 0.5; 87 | BG.CC = document.querySelector('canvas'); 88 | BG.ctx = BG.CC.getContext('2d'); 89 | BG.canvasWidth = BG.CC.width; 90 | BG.canvasHeight = 50 || P.fireOffset; 91 | BG.imageData = BG.ctx.getImageData(0, BG.CC.height - BG.canvasHeight, BG.canvasWidth, BG.canvasHeight); 92 | BG.data = BG.imageData.data; 93 | //BG.numPixels = BG.data.length / 4; 94 | BG.colors = []; 95 | 96 | for (var i = 0; i < 256; i++) { 97 | var color = []; 98 | color[0] = color[1] = color[2] = 75; 99 | BG.colors[i] = color; 100 | } 101 | 102 | for (var i = 0; i < 32; ++i) { 103 | BG.colors[i][2] = i << 1; 104 | BG.colors[i + 32][0] = i << 3; 105 | BG.colors[i + 32][2] = 64 - (i << 1); 106 | BG.colors[i + 64][0] = 255; 107 | BG.colors[i + 64][1] = i << 3; 108 | BG.colors[i + 96][0] = 255; 109 | BG.colors[i + 96][1] = 255; 110 | BG.colors[i + 96][2] = i << 2; 111 | BG.colors[i + 128][0] = 255; 112 | BG.colors[i + 128][1] = 255; 113 | BG.colors[i + 128][2] = 64 + (i << 2); 114 | BG.colors[i + 160][0] = 255; 115 | BG.colors[i + 160][1] = 255; 116 | BG.colors[i + 160][2] = 128 + (i << 2); 117 | BG.colors[i + 192][0] = 255; 118 | BG.colors[i + 192][1] = 255; 119 | BG.colors[i + 192][2] = 192 + i; 120 | BG.colors[i + 224][0] = 255; 121 | BG.colors[i + 224][1] = 255; 122 | BG.colors[i + 224][2] = 224 + i; 123 | } 124 | 125 | BG.fire = []; 126 | // init BG.fire array 127 | for (var i = 0; i < BG.canvasWidth * BG.canvasHeight; i++) { 128 | BG.fire[i] = 75; 129 | } 130 | 131 | BG.burnBurnBurn(); 132 | 133 | // intercept key up event to change intensity on BG.fire effect 134 | document.body.onkeyup = function(event) { 135 | if (event.keyCode >= 97 && event.keyCode <= 105) { 136 | BG.intensity = (event.keyCode - 97); 137 | BG.intensity = BG.intensity / 8; 138 | BG.intensity = BG.intensity * 0.4; 139 | BG.intensity = BG.intensity + 0.2; 140 | BG.threshold = 1 - BG.intensity; 141 | } else if (event.keyCode == 96) { // 0 ==> randomize 142 | BG.intensity = 0; 143 | BG.randomizeThreshold(); 144 | } 145 | }; 146 | 147 | } 148 | };*/ 149 | 150 | var flameBack = new function() { 151 | var context; 152 | var buffer; 153 | var bufferContext; 154 | var imageData; 155 | var palette; 156 | var colorMap; 157 | var width; 158 | var height; 159 | var scale = 2; 160 | var fan = 2.5; 161 | var slack = 5; 162 | this.time = new Date(); 163 | 164 | this.canvas = undefined; 165 | 166 | this.init = function() { 167 | context = this.canvas.getContext('2d'); 168 | 169 | width = (this.canvas.width + 30) / scale; 170 | height = P.fireOffset / scale; 171 | 172 | width = Math.ceil(width); 173 | height = Math.ceil(height); 174 | 175 | colorMap = Array(width * height); 176 | 177 | for(var i = 0; i < colorMap.length; i++) 178 | colorMap[i] = 255; 179 | 180 | initPalette(); 181 | initBuffer(); 182 | 183 | this.update(); 184 | }; 185 | 186 | // init palette from warm to white hot colors 187 | var initPalette = function() { 188 | palette = Array(256); 189 | 190 | for(var i = 0; i < 64; i++) { 191 | palette[i] = [(i << 2), 0, 0]; 192 | palette[i + 64] = [255, (i << 2), 0]; 193 | palette[i + 128] = [255, 255, (i << 2)]; 194 | palette[i + 192] = [255, 255, 255]; 195 | } 196 | }; 197 | 198 | // offscreen buffer for rendering and scaling 199 | var initBuffer = function() { 200 | buffer = document.createElement('canvas'); 201 | buffer.width = width; 202 | buffer.height = height; 203 | buffer.style.visibility = 'hidden'; 204 | 205 | bufferContext = buffer.getContext('2d'); 206 | imageData = bufferContext.createImageData(width, height); 207 | }; 208 | 209 | // main render loop 210 | this.update = function() { 211 | if (!G.isMobile()) { 212 | smooth(); 213 | draw(); 214 | fan = utils.getRandomInt(0, 6); 215 | } else { 216 | var grd = ctx.createLinearGradient(0, CC.h - P.fireOffset , 0, G.can.height); 217 | grd.addColorStop(0, 'rgba(255, 0, 0, ' + utils.getRandomInt(8, 10)/10 + ')'); 218 | grd.addColorStop(0.7, 'rgba(255, 165, 0, ' + utils.getRandomInt(8, 10)/10 + ')'); 219 | grd.addColorStop(0.9, 'rgba(255, 255, 0, ' + utils.getRandomInt(8, 10)/10 + ')'); 220 | sv(); 221 | fs(grd); 222 | fr(0, CC.h - P.fireOffset, G.can.width, P.fireOffset) 223 | rs(); 224 | } 225 | }; 226 | 227 | var smooth = function() { 228 | for(var x = width - 1; x >= 1; x--) { 229 | for(var y = height; y--;) { 230 | var p = (( 231 | colorMap[toIndex(x - 1, y - 1)] + 232 | colorMap[toIndex(x, y - 1)] + 233 | colorMap[toIndex(x + 1, y - 1)] + 234 | colorMap[toIndex(x - 1, y)] + 235 | colorMap[toIndex(x + 1, y)] + 236 | colorMap[toIndex(x - 1, y + 1)] + 237 | colorMap[toIndex(x, y + 1)] + 238 | colorMap[toIndex(x + 1, y + 1)]) >> 3); 239 | 240 | p = Math.max(0, p - randomValue(fan)); 241 | 242 | colorMap[toIndex(x, y - 1)] = p; 243 | 244 | if (y < height - slack) { // don't draw random noise in bottom rows 245 | if (y < height - 2) { 246 | // set two lines of random palette noise at bottom of 247 | // colorMap 248 | colorMap[toIndex(x, height)] = 249 | randomValue(palette.length); 250 | colorMap[toIndex(x, height - 1)] = 251 | randomValue(palette.length); 252 | } 253 | 254 | drawPixel(x, y, palette[colorMap[toIndex(x, y)]]); 255 | } 256 | } 257 | } 258 | }; 259 | 260 | // draw colormap->palette values to screen 261 | var draw = function() { 262 | // render the image data to the offscreen buffer... 263 | bufferContext.putImageData(imageData, 0, 0); 264 | // ...then draw it to scale to the onscreen canvas 265 | context.drawImage(buffer, -20, CC.h - (height * scale), width * scale, height * scale + 10); 266 | }; 267 | 268 | // set pixels in imageData 269 | var drawPixel = function(x, y, color) { 270 | var offset = (x + y * imageData.width) * 4; 271 | imageData.data[offset] = color[0]; 272 | imageData.data[offset + 1] = color[1]; 273 | imageData.data[offset + 2] = color[2]; 274 | imageData.data[offset + 3] = 255; 275 | }; 276 | 277 | var randomValue = function(max) { 278 | // protip: a double bitwise not (~~) is much faster than 279 | // Math.floor() for truncating floating point values into "ints" 280 | return ~~(Math.random() * max); 281 | }; 282 | 283 | // because "two-dimensional" arrays in JavaScript suck 284 | var toIndex = function(x, y) { 285 | return (y * width + x); 286 | }; 287 | 288 | // draw a bunch of random embers onscreen 289 | this.drawEmbers = function() { 290 | for(var x = 1; x < width - 1; x++) { 291 | for(var y = 1; y < height; y++) { 292 | if(Math.random() < 0.11) 293 | colorMap[toIndex(x, y)] = randomValue(palette.length); 294 | } 295 | } 296 | }; 297 | }; 298 | -------------------------------------------------------------------------------- /src/weather.js: -------------------------------------------------------------------------------- 1 | var CC, RN, WD, SM, cloud, sunMoon, wind, rain; 2 | var diffInWeatherTime = 5; 3 | function Cloud() { 4 | this.color = 'blue'; 5 | this.x = G.can.width || P.w; 6 | this.y = 100; 7 | this.speed = 7; 8 | this.update(); 9 | } 10 | Cloud.prototype = { 11 | drawArcs: function (x, y, sx, sy) { 12 | bp(); 13 | mt(x/sx + 150, y - 15); // 188, 50 14 | qct( 15 | x/sx + 150 + 50, 16 | y - 15 + 0, 17 | x/sx + 150 + 40, 18 | y - 15 + 40 19 | ); 20 | ctx.lineWidth = 4; 21 | sts(thisWeather.hexToRgb(thisWeather.getColor(), 0.8)) 22 | st(); 23 | 24 | bp(); 25 | mt(x/sx + 20 + 30, y + 10); // 188, 50 26 | qct( 27 | x/sx + 30, 28 | y + 35 + 10, 29 | x/sx + 30 + 60, 30 | y + 35 + 15 31 | ); 32 | st(); 33 | }, 34 | draw: function (x, y, sx, sy) { 35 | var cloudColor = thisWeather.hexToRgb(thisWeather.getColor(), 0.5); 36 | 37 | ctx.scale(sx, sy); 38 | bp(); 39 | fs(cloudColor) 40 | mt(x/sx, y); 41 | bct(x/sx - 40, y + 20, x/sx - 40, y + 70, x/sx + 60, y + 70); 42 | bct(x/sx + 80, y + 100, x/sx + 150, y + 100, x/sx + 170, y + 70); 43 | bct(x/sx + 250, y + 70, x/sx + 250, y + 40, x/sx + 220, y + 20); 44 | bct(x/sx + 260, y - 40, x/sx + 200, y - 50, x/sx + 170, y - 30); 45 | bct(x/sx + 150, y - 75, x/sx + 80, y - 60, x/sx + 80, y - 30); 46 | bct(x/sx + 30, y - 75, x/sx - 20, y - 60, x/sx, y); 47 | cp(); 48 | ctx.shadowColor = thisWeather.hexToRgb(thisWeather.getColor(), 0.8); 49 | ctx.shadowOffsetX = -3; 50 | ctx.shadowOffsetY = 3; 51 | ctx.shadowBlur = 10; 52 | ctx.lineWidth = 3; 53 | sts(thisWeather.hexToRgb(thisWeather.getColor(), 0.8)) 54 | st(); 55 | fl(); 56 | 57 | this.drawArcs(x, y, sx, sy); 58 | }, 59 | update: function () { 60 | this.x -= this.speed; 61 | if (this.x + 250 < 0) { 62 | this.x = CC.w + 250; 63 | this.y = this.y + utils.getRandomInt(-10, 10); 64 | } 65 | 66 | var x = this.x, y = this.y; 67 | sv(); 68 | this.draw(x, y, 0.8, 0.7); 69 | this.draw(x, y, 0.7, 0.6); 70 | this.draw(x, y, 0.6, 0.6); 71 | this.draw(x, y, 0.5, 0.7); 72 | rs(); 73 | } 74 | } 75 | 76 | function SunMoon() { 77 | SM = this; 78 | this.isSun = true; // will act as moon too 79 | this.r = 20; 80 | this.x = 0; 81 | this.y = 100; 82 | this.speed = 1; 83 | G.period = 'morning'; 84 | this.update(); 85 | } 86 | SunMoon.prototype = { 87 | getColor: function () { 88 | var color; 89 | switch (G.period) { 90 | case 'morning': 91 | color = this.isSun ? '#ffff9e' : '#fff'; 92 | break; 93 | case 'afternoon': 94 | color = this.isSun ? 'yellow' : '#fff'; 95 | break; 96 | case 'evening': 97 | color = this.isSun ? '#e28800' : '#fff'; 98 | break; 99 | case 'night': 100 | color = this.isSun ? '#fff' : '#fff'; 101 | break; 102 | } 103 | return color; 104 | }, 105 | resetPos: function () { 106 | this.x = 0; 107 | this.y = 100; 108 | G.period = 'morning'; 109 | thisWeather.step = 0; 110 | }, 111 | update: function () { 112 | // lets assume 30 secs is 1 day, so 15-15 secs day-night 113 | if (Weather.dt / 1000 % (5*diffInWeatherTime) > 5*diffInWeatherTime || 114 | Weather.dt / 1000 % (5*diffInWeatherTime) > 4*diffInWeatherTime || 115 | Weather.dt / 1000 % (5*diffInWeatherTime) > 3*diffInWeatherTime 116 | ) { 117 | G.period = 'night'; 118 | } else if (Weather.dt / 1000 % (5*diffInWeatherTime) > 2*diffInWeatherTime) { 119 | G.period = 'evening'; 120 | } else if (Weather.dt / 1000 % (5*diffInWeatherTime) > 1*diffInWeatherTime) { 121 | G.period = 'afternoon'; 122 | } else { 123 | G.period = 'morning'; 124 | } 125 | 126 | this.x += ((G.can.width / (2 * diffInWeatherTime)) / fps); // this.speed; 127 | if (this.x > G.can.width) { 128 | this.resetPos(); 129 | this.isSun = !this.isSun; 130 | return; 131 | } 132 | 133 | this.y -= 0.1; 134 | sv(); 135 | ctx.shadowColor = this.getColor(); 136 | ctx.shadowOffsetX = -3; 137 | ctx.shadowOffsetY = 3; 138 | ctx.shadowBlur = 10; 139 | bp(); 140 | ar(this.x, this.y, this.r, 0, Math.PI * 2, true); 141 | cp(); 142 | ctx.fillStyle = this.getColor(); 143 | fl(); 144 | rs() 145 | 146 | sv(); 147 | bp(); 148 | ctx.fillStyle = thisWeather.hexToRgb('#444', 0.5); 149 | // moon curvature 150 | if (!this.isSun) { 151 | ar(this.x + 5, this.y - 5, 20, 0, Math.PI * 2, true); 152 | } 153 | cp(); 154 | fl(); 155 | rs(); 156 | 157 | thisWeather.updateGradient(); 158 | } 159 | } 160 | 161 | function WindParticle(i) { 162 | this.x = G.can.width + utils.getRandomInt(0, G.can.width); 163 | this.y = (i+1) * WD.pDist; 164 | this.color = '#d1e5ff'; 165 | this.speed = utils.getRandomInt(1, WD.speed); 166 | } 167 | function Wind() { 168 | WD = this; 169 | this.speed = 1; 170 | this.particlesCount = 15; 171 | this.particles = []; 172 | this.pDist = 10; 173 | this.create(); 174 | } 175 | Wind.prototype = { 176 | create: function () { 177 | for (var i = 0; i < WD.particlesCount; i++) { 178 | WD.particles.push(new WindParticle(i)); 179 | } 180 | }, 181 | update: function () { 182 | for (var i = 0; i < WD.particles.length; i++) { 183 | var wParticle = WD.particles[i]; 184 | // wParticle.y += wParticle.speed; 185 | wParticle.x -= M.max(wParticle.speed); 186 | wParticle.color = thisWeather.getColor(); 187 | fs(wParticle.color); 188 | var wParticleW = utils.getRandomInt(10, 50) 189 | bp(); 190 | mt(wParticle.x, wParticle.y); 191 | lt(wParticle.x - wParticleW, wParticle.y); 192 | cp(); 193 | fl(); 194 | ctx.lineWidth = 2; 195 | sts(wParticle.color); 196 | st(); 197 | 198 | if (wParticle.x < 0) { 199 | // reinitialize a new wParticle 200 | WD.particles[i] = new WindParticle(i); 201 | } 202 | } 203 | } 204 | } 205 | 206 | function Droplet(i) { 207 | this.x = RN.topDropletsDist * i; 208 | this.y = 0; 209 | this.color = '#d1e5ff'; 210 | this.speed = utils.getRandomInt(10, 30); 211 | } 212 | function Rain() { 213 | RN = this; 214 | this.particles = []; 215 | this.particlesCount = 33; 216 | this.topDroplets = this.particlesCount * 1.5; // 66 217 | this.rightDroplets = this.particlesCount / 3; // 33 218 | this.topDropletsDist = (G.can.width / this.topDroplets) * 2; 219 | this.rightDropletsDist = (G.can.width / this.rightDroplets) * 2; 220 | this.create(); 221 | } 222 | Rain.prototype = { 223 | create: function () { 224 | for (var i = 0; i < RN.particlesCount; i++) { 225 | RN.particles.push(new Droplet(i)); 226 | } 227 | }, 228 | update: function () { 229 | for (var i = 0; i < RN.particles.length; i++) { 230 | var droplet = RN.particles[i]; 231 | droplet.y += droplet.speed; 232 | droplet.x -= M.max(G.speed, wind.speed); 233 | droplet.color = thisWeather.getColor(); 234 | fs(droplet.color); 235 | var dropletH = utils.getRandomInt(6, 20) 236 | bp(); 237 | mt(droplet.x, droplet.y); 238 | lt(droplet.x - 2, droplet.y + dropletH); 239 | cp(); 240 | fl(); 241 | ctx.lineWidth = 2; 242 | sts(droplet.color); 243 | st(); 244 | 245 | if (droplet.y > CC.h) { 246 | // reinitialize a new droplet 247 | RN.particles[i] = new Droplet(i); 248 | } 249 | } 250 | } 251 | }; 252 | 253 | function Weather() { 254 | this.colors = [ 255 | [255,255,255], 256 | [142,214,255], 257 | [255, 254, 210], 258 | [153,153,153], 259 | [20,20,20], 260 | [20,20,20] 261 | ]; 262 | 263 | this.step = 0; 264 | this.i = 0; 265 | this.colorIndices = [0, 1 ,2, 3]; 266 | 267 | thisWeather = this; 268 | CC = document.getElementById('canvascontainer').style; 269 | this.init(); 270 | } 271 | 272 | Weather.prototype = { 273 | updateGradient: function () { 274 | var c0_0 = thisWeather.colors[thisWeather.colorIndices[0]], 275 | c0_1 = thisWeather.colors[thisWeather.colorIndices[1]], 276 | c1_0 = thisWeather.colors[thisWeather.colorIndices[2]], 277 | c1_1 = thisWeather.colors[thisWeather.colorIndices[3]], 278 | 279 | istep = 1 - thisWeather.step, 280 | r1 = Math.round(istep * c0_0[0] + thisWeather.step * c0_1[0]), 281 | g1 = Math.round(istep * c0_0[1] + thisWeather.step * c0_1[1]), 282 | b1 = Math.round(istep * c0_0[2] + thisWeather.step * c0_1[2]), 283 | color1 = 'rgb(' + r1 + ',' + g1 + ',' + b1 + ')', 284 | 285 | r2 = Math.round(istep * 255+ thisWeather.step * 255), 286 | g2 = Math.round(istep * 255+ thisWeather.step * 255), 287 | b2 = Math.round(istep * 255+ thisWeather.step * 255), 288 | color2 = 'rgb(' + r2 + ',' + g2 + ',' + b2 + ')'; 289 | 290 | var grd = ctx.createLinearGradient(0, 0, 0, G.can.height); 291 | grd.addColorStop(0, color1); 292 | grd.addColorStop(0.9, color2); 293 | G.backgroundColor = grd; 294 | 295 | thisWeather.step += SM.isSun ? 0.0076 : 0.0076/2.22; // 1 / (diffInWeatherTime * fps); 296 | if (thisWeather.step >= 1) { 297 | thisWeather.step = 0; 298 | for (var j = 0; j < thisWeather.colorIndices.length; j++) { 299 | thisWeather.colorIndices[j] = (thisWeather.i + 1) % thisWeather.colors.length; 300 | } 301 | thisWeather.i += 1; 302 | } 303 | }, 304 | hexToRgb: function (hexColor, alpha) { 305 | if (!hexColor) { return; } 306 | 307 | alpha = alpha || 1.0; 308 | // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") 309 | var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; 310 | hexColor = hexColor.replace(shorthandRegex, function(m, r, g, b) { 311 | return r + r + g + g + b + b; 312 | }); 313 | 314 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hexColor); 315 | return result ? 'rgba(' + 316 | parseInt(result[1], 16) + ',' + 317 | parseInt(result[2], 16) + ',' + 318 | parseInt(result[3], 16) + ',' + 319 | alpha + ')' : 'rgba(255,255,255,0)'; 320 | }, 321 | getColor: function (isKarmaText) { 322 | var color; 323 | switch (G.period) { 324 | case 'morning': 325 | color = isKarmaText ? SM.isSun ? '#444' : '#fff' : SM.isSun ? '#8ED6FF' : '#444'; 326 | break; 327 | case 'afternoon': 328 | color = isKarmaText ? SM.isSun ? '#444' : '#fff' : SM.isSun ? '#56baf3' : '#444'; 329 | break; 330 | case 'evening': 331 | color = isKarmaText ? SM.isSun ? '#444' : '#fff' : SM.isSun ? '#999' : '#444'; 332 | break; 333 | case 'night': 334 | color = isKarmaText ? SM.isSun ? '#444' : '#fff' : SM.isSun ? '#444' : '#444'; 335 | break; 336 | } 337 | return color; 338 | }, 339 | update: function () { 340 | var now = new Date().getTime(); 341 | Weather.dt = now - G.gameStartTime; 342 | 343 | sunMoon.update(); 344 | if (!G.isMobile()) { 345 | cloud.update(); 346 | 347 | // console.log(M.ceil(Weather.dt / 1000)) 348 | if (!this.canRain && M.ceil(Weather.dt / 1000) % 16 === 0) { 349 | this.canRain = true; 350 | this.isRaining = true; 351 | } else if (M.ceil(Weather.dt / 1000) % 33 === 0) { 352 | this.canRain = false; 353 | this.isRaining = false; 354 | } 355 | 356 | if (this.canRain && this.isRaining) { 357 | rain.update(); 358 | } 359 | 360 | wind.update(); 361 | } 362 | }, 363 | init: function () { 364 | cloud = new Cloud(); 365 | sunMoon = new SunMoon(); 366 | rain = new Rain(); 367 | wind = new Wind(); 368 | this.update(); 369 | } 370 | }; 371 | -------------------------------------------------------------------------------- /src/menu.js: -------------------------------------------------------------------------------- 1 | var MN; 2 | function Menu() { 3 | MN = this; 4 | this.y = 0; 5 | this.font = '50px Helvetica'; 6 | this.fireColor = 'rgb(255, 56, 8)'; 7 | 8 | ctx.fillStyle = '#fff' 9 | ctx.fillRect(0, 0, G.can.width, G.can.height); 10 | 11 | this.heat = MN.getHeatMap(); 12 | this.noise = null; 13 | this.noise = MN.getNoise(G.can.width, G.can.height*8); 14 | ctx.drawImage(this.heat, 0, 0); 15 | this.update(); 16 | } 17 | Menu.prototype = { 18 | getNoise: function () { 19 | var canvas = document.createElement('canvas'); 20 | canvas.width = G.can.width; 21 | canvas.height = G.can.height; 22 | var ctx = canvas.getContext('2d'); 23 | 24 | var w = canvas.width, h = canvas.height, 25 | img = ctx.createImageData(w, h), 26 | n = w * h * 4; 27 | 28 | for(var i = 0; i < n; i+=4) { 29 | img.data[i] = 15; 30 | img.data[i+1] = 3; 31 | img.data[i+2] = 1; 32 | img.data[i+3] = Math.floor(Math.random() * 128); 33 | } 34 | sv(); 35 | ctx.putImageData(img, 0, 0); 36 | ctx.drawImage(canvas, 0, 0, w * 64, h * 64); 37 | ctx.globalAlpha = 0.5; 38 | ctx.drawImage(canvas, 0, 0, w * 16, h * 16); 39 | var img = ctx.getImageData(0, 0, w, h); 40 | // increase contrast a bit by clamping values 41 | for (var i = 3; i < w * h * 4; i += 4){ 42 | if (img.data[i] > 220){ 43 | img.data[i] = 255; 44 | } 45 | if (img.data[i] < 40){ 46 | img.data[i] = 0; 47 | } 48 | } 49 | ctx.putImageData(img, 0, 0); 50 | rs(); 51 | return canvas; 52 | }, 53 | getHeatMap: function () { 54 | var canvas = document.createElement('canvas'); 55 | canvas.width = G.can.width; 56 | canvas.height = G.can.height; 57 | 58 | var ctx = canvas.getContext('2d'); 59 | sv(); 60 | var w = G.can.width, 61 | h = G.can.height, 62 | color = MN.fireColor, 63 | firstText = G.isGameOver ? 'GAME' : 'SAVE', 64 | secondText = G.isGameOver ? 'OVER' : 'THE'; 65 | thirdText = G.isGameOver ? '' : 'FOREST'; 66 | 67 | if (G.isMobile()) { 68 | firstText = firstText.split('').join(' '); 69 | secondText = secondText.split('').join(' '); 70 | thirdText = thirdText.split('').join(' '); 71 | } else { 72 | firstText = firstText.split('').join(' '); 73 | secondText = secondText.split('').join(' '); 74 | thirdText = thirdText.split('').join(' '); 75 | } 76 | 77 | ctx.fillStyle = color; 78 | ctx.strokeStyle = color; 79 | ctx.font = MN.font; 80 | 81 | var m1 = ctx.measureText(firstText); 82 | var m2 = ctx.measureText(secondText); 83 | var m3 = ctx.measureText(thirdText); 84 | ctx.fillText(firstText, (w - m1.width) / 2, h / 6); 85 | ctx.fillText(secondText, (w - m2.width) / 2, h / 4); 86 | ctx.fillText(thirdText, (w - m3.width) / 2, h / 3); 87 | ctx.lineWidth = 10; 88 | 89 | if (!G.isInfoMenu) { 90 | var highestScoreText = 'BEST: ' + G.highscore; 91 | if (G.isMobile()) { 92 | highestScoreText = highestScoreText.split('').join(' '); 93 | } else { 94 | highestScoreText = highestScoreText.split('').join(' '); 95 | } 96 | ctx.fillStyle = '#fff'; 97 | ctx.font = '35px Helvetica'; 98 | ctx.fillText(highestScoreText, (w - ctx.measureText(highestScoreText).width) / 2, h / 2.1); 99 | 100 | // Sound circle 101 | ctx.beginPath(); 102 | ctx.arc(w*(1/4), h/1.2, 30, 0, 2 * Math.PI, false); 103 | ctx.fillStyle = '#555'; 104 | ctx.closePath(); 105 | ctx.fill(); 106 | 107 | // Rules / Instructions circle 108 | ctx.beginPath(); 109 | ctx.arc(w*(3/4), h/1.2, 30, 0, 2 * Math.PI, false); 110 | ctx.fillStyle = '#555'; 111 | ctx.closePath(); 112 | ctx.fill(); 113 | 114 | // sound icon 115 | ctx.beginPath(); 116 | ctx.moveTo(w*(1/4) - 20, h/1.2 - 10); 117 | ctx.lineTo(w*(1/4) - 20, h/1.2 + 5); 118 | ctx.lineTo(w*(1/4) - 10, h/1.2 + 5); 119 | ctx.lineTo(w*(1/4) + 5, h/1.2 + 15); 120 | ctx.lineTo(w*(1/4) + 5, h/1.2 - 20); 121 | ctx.lineTo(w*(1/4) - 10, h/1.2 - 10); 122 | ctx.fillStyle = '#222'; 123 | ctx.closePath(); 124 | if (G.isSound) { 125 | ctx.fillRect(w*(1/4) + 10, h/1.2 - 5, 3, 10); 126 | ctx.fillRect(w*(1/4) + 15, h/1.2 - 7, 3, 15); 127 | ctx.fillRect(w*(1/4) + 20, h/1.2 - 10, 3, 20); 128 | } 129 | ctx.fill(); 130 | 131 | // if no sound, show / on icon 132 | if (!G.isSound) { 133 | ctx.save(); 134 | ctx.beginPath(); 135 | ctx.moveTo(w*(1/4) + 10, h/1.2 - 22); 136 | ctx.lineTo(w*(1/4) - 10, h/1.2 + 22); 137 | ctx.closePath(); 138 | ctx.fill(); 139 | ctx.lineWidth = 5; 140 | ctx.strokeStyle = '#000'; 141 | ctx.stroke(); 142 | ctx.restore(); 143 | } 144 | 145 | // instructions icon 146 | ctx.fillRect(w*(3/4) - 2, h/1.2, 5, 15); 147 | ctx.beginPath(); 148 | ctx.arc(w*(3/4), h/1.2 - 10, 5, 0, 2 * Math.PI, false); 149 | ctx.closePath(); 150 | ctx.fillStyle = '#222'; 151 | ctx.fill(); 152 | 153 | if (G.isGameOver) { 154 | ctx.fillStyle = '#fff'; 155 | ctx.font = '35px Helvetica'; 156 | var karmaText = 'KARMA: ' + G.karma; 157 | 158 | if (G.isMobile()) { 159 | karmaText = karmaText.split('').join(' '); 160 | } else { 161 | karmaText = karmaText.split('').join(' '); 162 | } 163 | 164 | ctx.fillText(karmaText, (w - ctx.measureText(karmaText).width) / 2, h / 2.5); 165 | ctx.lineWidth = 10; 166 | 167 | ctx.beginPath(); 168 | ctx.arc(w*(2/4), h/1.2, 30, 0, 2 * Math.PI, false); 169 | ctx.fillStyle = '#555'; 170 | ctx.closePath(); 171 | ctx.fill(); 172 | 173 | // download icon 174 | ctx.beginPath(); 175 | ctx.moveTo(w*(2/4) - 10, h/1.2 - 15); 176 | ctx.lineTo(w*(2/4) - 10, h/1.2 - 15 + 15); 177 | ctx.lineTo(w*(2/4) - 20, h/1.2 - 15 + 15); 178 | 179 | ctx.lineTo(w*(2/4), h/1.2 - 15 + 35); 180 | ctx.lineTo(w*(2/4) + 20, h/1.2 - 15 + 15); 181 | 182 | ctx.lineTo(w*(2/4) + 10, h/1.2 - 15 + 15); 183 | ctx.lineTo(w*(2/4) + 10, h/1.2 - 15); 184 | 185 | ctx.fillStyle = '#222'; 186 | ctx.closePath(); 187 | ctx.fill(); 188 | } 189 | 190 | // Play button 191 | ctx.beginPath(); 192 | ctx.arc(w/2, h/1.6, 50, 0, 2 * Math.PI, false); 193 | ctx.fillStyle = '#793f02'; 194 | ctx.closePath(); 195 | ctx.fill(); 196 | 197 | var tw = 20, th = h/1.6 - tw; 198 | ctx.beginPath(); 199 | ctx.moveTo(w/2 - tw/2, th); 200 | ctx.lineTo(w/2 + tw, th + 20); 201 | ctx.lineTo(w/2 - tw/2, th + 40); 202 | ctx.fillStyle = '#fff'; 203 | ctx.closePath(); 204 | ctx.fill(); 205 | } else { 206 | // back button 207 | var hFactor = G.isMobile() ? 10 : 4.4; 208 | 209 | ctx.beginPath(); 210 | ctx.arc(w/10, h/hFactor, 30, 0, 2 * Math.PI, false); 211 | ctx.fillStyle = '#555'; 212 | ctx.closePath(); 213 | ctx.fill(); 214 | 215 | ctx.beginPath(); 216 | ctx.moveTo(w/10, h/hFactor-5); 217 | ctx.lineTo(w/10, h/hFactor-5 - 10); 218 | ctx.lineTo(w/10 - 20, h/hFactor-5 + 5); 219 | ctx.lineTo(w/10, h/hFactor-5 + 20); 220 | ctx.lineTo(w/10, h/hFactor-5 + 10); 221 | ctx.lineTo(w/10 + 20, h/hFactor-5 + 10); 222 | ctx.lineTo(w/10 + 20, h/hFactor-5); 223 | ctx.closePath(); 224 | ctx.fillStyle = '#000'; 225 | ctx.fill(); 226 | 227 | // show info 228 | var instructionLines = [ 229 | 'Save our planet Earth!', 230 | 'Protect Forest! Don\'t burn them!', 231 | 'Abrupt climatic changes. Time to worry!', 232 | 'Extinguish fire on trees.', 233 | 'Hit spacebar or tap to jump player.', 234 | 'Earn Karma! Nature will show her love!', 235 | 'JS13KGames 16 - hidden glitches', 236 | 'Climate Abnormalities, Player Loves Trees', 237 | '(Player struggles to jump off tree)', 238 | 'More hinderances once speed > 1.6 mph' 239 | ]; 240 | ctx.font = G.isMobile() ? '15px Helvetica' : '20px Helvetica'; 241 | ctx.fillStyle = '#fff'; 242 | for (var l = 0; l < instructionLines.length; l++) { 243 | var line = instructionLines[l]; 244 | var hOffset = G.isMobile() ? l*40 : l*45; 245 | if (l === 0 || l === 2 || l === 4 || l === 6) { 246 | ctx.beginPath(); 247 | ctx.arc(w / 10, h/2.6 + hOffset, 10, 0, 2*Math.PI, false); 248 | ctx.fill(); 249 | ctx.closePath(); 250 | } 251 | ctx.fillText(line, w/10 + (G.isMobile() ? 25: 50), h/2.6 + hOffset); 252 | } 253 | } 254 | rs(); 255 | return canvas; 256 | }, 257 | process: function () { 258 | sv(); 259 | // cooldown factor 260 | ctx.globalAlpha = 0.35; 261 | ctx.globalCompositeOperation = 'source-over'; 262 | // movement speed of cooldown map 263 | MN.y = (MN.y + 3) % MN.noise.height; 264 | // flickering of cooldown map 265 | x = Math.round(Math.random() * 5) * 0; 266 | ctx.drawImage(MN.noise, x, MN.y); 267 | ctx.drawImage(MN.noise, x, MN.y - MN.noise.height); 268 | 269 | // spread of the flame 270 | ctx.globalAlpha = 1.0; 271 | // whind 272 | x = 1 - Math.random() * 2; 273 | // move flame up 274 | ctx.drawImage(G.can, x, -1); 275 | ctx.globalAlpha = 0.13; 276 | ctx.globalCompositeOperation = 'lighter'; 277 | ctx.drawImage(G.can, x, -1); 278 | 279 | // heat it up 280 | ctx.globalAlpha = 0.22; 281 | ctx.drawImage(MN.heat, 0, 0); 282 | fs(MN.fireColor); 283 | bp(); 284 | ctx.globalAlpha = 0.52; 285 | cp(); 286 | fl(); 287 | rs(); 288 | }, 289 | mouseDown: function (e, x, y) { 290 | var w = G.can.width, 291 | h = G.can.height, 292 | ctx = MN.heat.getContext('2d'); 293 | 294 | var hFactor = G.isMobile() ? 10 : 4.4; 295 | 296 | if (x >= w/2 - 50 && x <= w/2 + 50 && 297 | y >= h/1.6 - 50 && y <= h/1.6 + 50) { 298 | // play btn clicked 299 | G.menu = null; 300 | G.restart(); 301 | SU.play('playGame'); 302 | } else if (x >= w*(2/4) - 30 && x <= w*(2/4) + 30 && 303 | y >= h/1.2 - 30 && y <= h/1.2 + 30) { 304 | // download clicked 305 | downloadCanvas(); 306 | SU.play('download'); 307 | } else if (x >= w*(1/4) - 30 && x <= w*(1/4) + 30 && 308 | y >= h/1.2 - 30 && y <= h/1.2 + 30) { 309 | // sound clicked 310 | G.isSound = +(!G.isSound); 311 | G.isSound && SU.play('soundOn'); 312 | utils.setLocalStorageData(G.isSound, true); 313 | MN.heat = MN.getHeatMap(); 314 | } else if (x >= w*(3/4) - 30 && x <= w*(3/4) + 30 && 315 | y >= h/1.2 - 30 && y <= h/1.2 + 30) { 316 | // info clicked 317 | G.isInfoMenu = true; 318 | MN.heat = MN.getHeatMap(); 319 | SU.play('info'); 320 | } else if (x >= w*(1/10) - 30 && x <= w*(1/10) + 30 && 321 | y >= h/hFactor - 30 && y <= h/hFactor + 30) { 322 | // back btn clicked 323 | G.isInfoMenu = false; 324 | MN.heat = MN.getHeatMap(); 325 | SU.play('info'); 326 | } 327 | }, 328 | update: function () { 329 | // this.noise = MN.getNoise(G.can.width, G.can.height * 8); 330 | MN.process(); 331 | } 332 | }; 333 | -------------------------------------------------------------------------------- /dist/game.min.js: -------------------------------------------------------------------------------- 1 | function J(){this.B=function(t){for(var e=0;24>e;e++)this[String.fromCharCode(97+e)]=t[e]||0;.01>this.c&&(this.c=.01),t=this.b+this.c+this.e,.18>t&&(t=.18/t,this.b*=t,this.c*=t,this.e*=t)}}function Menu(){MN=this,this.y=0,this.font="50px Helvetica",this.fireColor="rgb(255, 56, 8)",ctx.fillStyle="#fff",ctx.fillRect(0,0,G.can.width,G.can.height),this.heat=MN.getHeatMap(),this.noise=null,this.noise=MN.getNoise(G.can.width,8*G.can.height),ctx.drawImage(this.heat,0,0),this.update()}function Cloud(){this.color="blue",this.x=G.can.width||P.w,this.y=100,this.speed=7,this.update()}function SunMoon(){SM=this,this.isSun=!0,this.r=20,this.x=0,this.y=100,this.speed=1,G.period="morning",this.update()}function WindParticle(t){this.x=G.can.width+utils.getRandomInt(0,G.can.width),this.y=(t+1)*WD.pDist,this.color="#d1e5ff",this.speed=utils.getRandomInt(1,WD.speed)}function Wind(){WD=this,this.speed=1,this.particlesCount=15,this.particles=[],this.pDist=10,this.create()}function Droplet(t){this.x=RN.topDropletsDist*t,this.y=0,this.color="#d1e5ff",this.speed=utils.getRandomInt(10,30)}function Rain(){RN=this,this.particles=[],this.particlesCount=33,this.topDroplets=1.5*this.particlesCount,this.rightDroplets=this.particlesCount/3,this.topDropletsDist=G.can.width/this.topDroplets*2,this.rightDropletsDist=G.can.width/this.rightDroplets*2,this.create()}function Weather(){this.colors=[[255,255,255],[142,214,255],[255,254,210],[153,153,153],[20,20,20],[20,20,20]],this.step=0,this.i=0,this.colorIndices=[0,1,2,3],thisWeather=this,CC=document.getElementById("canvascontainer").style,this.init()}function Particles(t,e){PS=this,this.x=t-10,this.y=e,this.vyL1=3,this.vyL2=2,this.vyL3=1,this.finished=!1,this.particles=[],this.diff=0,this.draw()}function Player(){return Pl=this,Pl.liesOn=0,Pl.maxH=66,Pl.bounceHeight=5,Pl.w=20,Pl.h=Pl.maxH,Pl.x=G.trees[0].x,Pl.y=G.trees[0].y-Pl.h,Pl.vel=0,Pl.isJet=!1,Pl.isRest=!0,Pl.t=new Date,Pl.update(),Pl}function Tree(t){return T=this,t=t||{},T.minW=10,T.maxW=80,T.minH=P.fireOffset,T.maxH=G.isMobile()?300:400,T.minDist=50,T.maxDist=G.isMobile()?100:200,CC.w=utils.pI(G.can.width),CC.h=utils.pI(G.can.height),T.color="#a77b44",this.add(),t.isNoFlame||(G.isMobile()?this.flame=!0:(this.flame=smoky,this.flame.addEntity(Flame))),T}function Game(){G=this,G.isInProgress=!0,G.canSpeedBeIncreased=G.canExplode=!0,G.backgroundColor="#fff",G.karma=0,G.highscore=utils.getLocalStorageData()||0,G.isSound=utils.getLocalStorageData(!0),0!==G.isSound&&(G.isSound=1),G.resolution=1,G.curPos=[],G.can=document.querySelector("canvas"),G.can.width=P.w,G.can.height=P.h,ctx=G.ctx=window.c=G.can.getContext("2d"),G.trees=[],G.resize(),addEventListener("resize",G.resize,!1),CC=document.getElementById("canvascontainer").style,document.body.addEventListener("touchstart",G.touchStart.bind(G),!1),document.body.addEventListener("touchmove",G.touchMove.bind(G),!1),document.body.addEventListener("touchend",G.touchEnd.bind(G),!1),document.body.addEventListener("mousedown",G.mouseDown.bind(G),!1),document.body.addEventListener("mousemove",G.mouseMove.bind(G),!1),document.body.addEventListener("mouseup",G.mouseUp.bind(G),!1),document.body.addEventListener("keydown",G.keyDown.bind(G),!1),document.body.addEventListener("keyup",G.keyUp.bind(G),!1),G.frameCount=0,G.lastFrame=G.frameCountStart=Date.now();var t=_.innerWidth*_.innerHeight*_.devicePixelRatio,e=P.w*P.h,i=t/e;i<.5&&G.setResolution(2*i),G.speed=1,flameBack.canvas=G.can,G.menu=!0}function canvasToImage(){G.dataURL=document.getElementById("game-canvas").toDataURL("image/png")}function downloadCanvas(){var t=_.open();t?t.document.write(''):alert("Your browser prevented the window from opening. Please allow to view game screenshot.")}navigator.vibrate=function(){return navigator.vibrate||navigator.mozVibrate||navigator.webkitVibrate||noop}();var utils={getRandomInt:function(t,e){return Math.floor(Math.random()*(e-t+1))+t},pI:function(t){return parseInt(t,10)},clamp:function(t,e,i){return"number"!=typeof e&&(e=-(1/0)),"number"!=typeof i&&(i=1/0),Math.max(e,Math.min(i,t))},getLocalStorageData:function(t){return t?utils.pI(atob(localStorage.getItem("__js13k_game_sound"))):utils.pI(atob(localStorage.getItem("__js13k_game_karma")))||0},setLocalStorageData:function(t,e){e?localStorage.setItem("__js13k_game_sound",btoa(t)):localStorage.setItem("__js13k_game_karma",btoa(t))}},W=new function(){this.A=new J;var t,e,i,a,n,r,o,s,l,h,c,d;this.reset=function(){var t=this.A;a=100/(t.f*t.f+.001),n=100/(t.g*t.g+.001),r=1-.01*t.h*t.h*t.h,o=1e-6*-t.i*t.i*t.i,t.a||(c=.5-t.n/2,d=5e-5*-t.o),s=0p.q?-1020:1020),C=p.p?(2e4*(1-p.p)*(1-p.p)|0)+32:0,T=p.d,I=p.j/2,W=.01*p.k*p.k,k=p.a,R=t,D=1/t,E=1/e,A=1/i,p=5/(1+20*p.u*p.u)*(.01+G);.8=C&&(J=0,this.reset()),h&&++l>=h&&(h=0,a*=s),r+=o,a*=r,a>n&&(a=n,0U&&(U=8),k||(c+=d,0>c?c=0:.5R)switch(_=0,++B){case 1:R=e;break;case 2:R=i}switch(B){case 0:j=_*D;break;case 1:j=1+2*(1-_*E)*T;break;case 2:j=1-_*A;break;case 3:j=0,H=!0}b&&(M+=S,N=0|M,0>N?N=-N:1023m?m=1e-5:.1=U&&(Y%=U,3==k))for(O=Z.length;O--;)Z[O]=2*Math.random()-1;switch(k){case 0:F=Y/UF?1.27323954*F+.405284735*F*F:1.27323954*F-.405284735*F*F,F=0>F?.225*(F*-F-F)+F:.225*(F*F-F)+F;break;case 3:F=Z[Math.abs(32*Y/U|0)]}P&&(O=K,G*=y,0>G?G=0:.1=L?-32768:32767*L|0}return u}};window.jsfxr=function(t){W.A.B(t);var e=W.D();t=new Uint8Array(4*((e+1)/2|0)+44);var e=2*W.C(new Uint16Array(t.buffer,44),e),i=new Uint32Array(t.buffer,0,44);i[0]=1179011410,i[1]=e+36,i[2]=1163280727,i[3]=544501094,i[4]=16,i[5]=65537,i[6]=44100,i[7]=88200,i[8]=1048578,i[9]=1635017060,i[10]=e;for(var e=e+44,i=0,a="data:audio/wav;base64,";i>18]+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[n>>12&63]+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[n>>6&63]+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[63&n]);return i-=e,a.slice(0,a.length-i)+"==".slice(0,i)};var soundUtils=SU={rd:function(t,e){return e||(e=t,t=0),Math.random()*(e-t)+t},rp:function(t){return t[~~this.rd(t.length)]},soundEffect:function(t,e){SU[t]=[],e.forEach(function(e){var i=new Audio;i.src=jsfxr(e),SU[t].push(i)})},play:function(t){G.isSound&&SU[t]&&SU.rp(SU[t]).play()}};SU.soundEffect("gameOver",[[2,.2,.01,,.83,.24,,,,.62,.6,,,.1248,.4522,,,,.4,,,,,.6]]),SU.soundEffect("moveAhead",[[2,,.2047,,.3986,.5855,.2236,-.1697,,,,,,.7882,-.2576,,,,1,,,,,.43]]),SU.soundEffect("highestScore",[[0,,.016,.4953,.3278,.6502,,,,,,.4439,.6322,,,,,,1,,,,,1]]),SU.soundEffect("explosion1",[[3,,.3729,.6547,.4138,.0496,,,,,,,,,,,,,1,,,,,.4]]),SU.soundEffect("explosion2",[[3,.43,.61,.3794,.86,.17,.17,.1399,.1,.07,.06,.04,.1,,,.96,.26,-.16,1,,,,,.15]]),SU.soundEffect("info",[[2,,.1889,,.111,.2004,,,,,,,,.1157,,,,,1,,,.1,,1]]),SU.soundEffect("soundOn",[[2,,.2,,.1753,.64,,-.5261,,,,,,.5522,-.564,,,,1,,,,,.5]]),SU.soundEffect("playGame",[[2,,.261,.2142,.2005,.4618,.0137,-.3602,,,,,,.2249,.0858,,,,1,,,1e-4,,.44]]),SU.soundEffect("glitch",[[3,,.0272,.5654,.1785,.7424,,,,,,.2984,.5495,,,,,,1,,,,,.43]]);var SF,Flame,H,PI_2,Smoke,Trail,W,SmokyFlame,drawCircle,rand,w,slice=[].slice;PI_2=2*Math.PI,rand=function(t,e){return(e-t)*Math.random()+t},drawCircle=function(t,e,i,a){return bp(),ar(t,e,i,0,PI_2,!1),ctx.fillStyle=a,fl()},Smoke=function(){function t(t,e){this.opacity=.8,this.x=t,this.y=e,this.r=2}return t.prototype.step=function(t,e,i){if(e-=utils.getRandomInt(rand(60,70),rand(200,350)),t+=rand(-2,2),this.opacity-=.04,this.opacity<=0)return this.destroyed=!0},t.prototype.draw=function(t,e,i){if(e-=utils.getRandomInt(60,150),t+=utils.getRandomInt(rand(-i+i/2,0),rand(0,i-i/2)),!(this.opacity<=0))return drawCircle(t,e,this.r,"rgba(60,60,60,"+this.opacity+")")},t}(),Trail=function(){function t(t,e){this.opacity=1,this.x=t,this.y=e,this.r=12}return t.prototype.step=function(t,e,i){if(this.r=i/5,e-=rand(0,8),t-=rand(-3,3),this.opacity-=.03,this.opacity<=0&&(this.destroyed=!0,rand(0,1)<.5))return SF.addEntity(Smoke,t,e-this.r)},t.prototype.draw=function(t,e,i){this.r=i/6,e-=rand(rand(-45,5),rand(25,75)),t-=rand(-i/2-20,i/2+20);var a,n,r,o;if(!(this.opacity<=0))return a="rgba(255,"+~~(240*this.opacity)+",0,"+this.opacity+")",n="rgba(255,"+~~(240*this.opacity)+",0,0)",o=1.5*this.r+rand(0,2),r=ctx.createRadialGradient(t,e,0,t,e,o),r.addColorStop(0,a),r.addColorStop(1,n),drawCircle(t,e,this.r,r),drawCircle(t,e,this.r*this.opacity,a)},t}(),Flame=function(){function t(){this.x=G.can.width/2,this.y=G.can.height/2+90,this.r=24,this.rg=22}return t.prototype.step=function(t,e,i){return!1},t.prototype.draw=function(t,e,i){this.g=ctx.createRadialGradient(t,e,0,t,e,1.2*i),this.g.addColorStop(0,"rgba(255,255,255,1)"),this.g.addColorStop(1,"rgba(255,120,0,0)");var a;return SF.addEntity(Trail,t,e-this.r/3),a=ctx.createRadialGradient(t,e,0,t,e,this.rg),a.addColorStop(0,"rgba(255,180,0,"+rand(.2,.9)+")"),a.addColorStop(1,"rgba(255,180,0,0)"),drawCircle(t,e,this.rg,a),drawCircle(t+rand(-1.5,1.5),e+rand(-1.5,1.5),i,this.g)},t}(),SmokyFlame=function(){function t(){SF=this,this.entities={},this.i=0,this.ii=0}return t.prototype.addEntity=function(){var t,e;return e=arguments[0],t=2<=arguments.length?slice.call(arguments,1):[],this.entities[this.i]=function(t,e,i){i.prototype=t.prototype;var a=new i,n=t.apply(a,e);return Object(n)===n?n:a}(e,t,function(){}),this.i+=1},t.prototype.update=function(t,e,i){var a,n,r;r=this.entities;for(n in r)a=r[n],a.destroyed!==!0?(this.ii%5===0&&(a.step(t+i/2,e-10,i),a.draw(t+i/2,e-10,i),this.ii=0),this.ii++):delete this.entities[n]},t}();var MN;Menu.prototype={getNoise:function(){var t=document.createElement("canvas");t.width=G.can.width,t.height=G.can.height;for(var e=t.getContext("2d"),i=t.width,a=t.height,n=e.createImageData(i,a),r=i*a*4,o=0;o220&&(n.data[o]=255),n.data[o]<40&&(n.data[o]=0);return e.putImageData(n,0,0),rs(),t},getHeatMap:function(){var t=document.createElement("canvas");t.width=G.can.width,t.height=G.can.height;var e=t.getContext("2d");sv();var i=G.can.width,a=G.can.height,n=MN.fireColor,r=G.isGameOver?"GAME":"SAVE",o=G.isGameOver?"OVER":"THE";thirdText=G.isGameOver?"":"FOREST",G.isMobile()?(r=r.split("").join(" "),o=o.split("").join(" "),thirdText=thirdText.split("").join(" ")):(r=r.split("").join(" "),o=o.split("").join(" "),thirdText=thirdText.split("").join(" ")),e.fillStyle=n,e.strokeStyle=n,e.font=MN.font;var s=e.measureText(r),l=e.measureText(o),h=e.measureText(thirdText);if(e.fillText(r,(i-s.width)/2,a/6),e.fillText(o,(i-l.width)/2,a/4),e.fillText(thirdText,(i-h.width)/2,a/3),e.lineWidth=10,G.isInfoMenu){var c=G.isMobile()?10:4.4;e.beginPath(),e.arc(i/10,a/c,30,0,2*Math.PI,!1),e.fillStyle="#555",e.closePath(),e.fill(),e.beginPath(),e.moveTo(i/10,a/c-5),e.lineTo(i/10,a/c-5-10),e.lineTo(i/10-20,a/c-5+5),e.lineTo(i/10,a/c-5+20),e.lineTo(i/10,a/c-5+10),e.lineTo(i/10+20,a/c-5+10),e.lineTo(i/10+20,a/c-5),e.closePath(),e.fillStyle="#000",e.fill();var d=["Save our planet Earth!","Protect Forest! Don't burn them!","Abrupt climatic changes. Time to worry!","Extinguish fire on trees.","Hit spacebar or tap to jump player.","Earn Karma! Nature will show her love!","JS13KGames 16 - hidden glitches","Climate Abnormalities, Player Loves Trees","(Player struggles to jump off tree)","More hinderances once speed > 1.6 mph"];e.font=G.isMobile()?"15px Helvetica":"20px Helvetica",e.fillStyle="#fff";for(var f=0;f=a/2-50&&e<=a/2+50&&i>=n/1.6-50&&i<=n/1.6+50?(G.menu=null,G.restart(),SU.play("playGame")):e>=.5*a-30&&e<=.5*a+30&&i>=n/1.2-30&&i<=n/1.2+30?(downloadCanvas(),SU.play("download")):e>=.25*a-30&&e<=.25*a+30&&i>=n/1.2-30&&i<=n/1.2+30?(G.isSound=+!G.isSound,G.isSound&&SU.play("soundOn"),utils.setLocalStorageData(G.isSound,!0),MN.heat=MN.getHeatMap()):e>=.75*a-30&&e<=.75*a+30&&i>=n/1.2-30&&i<=n/1.2+30?(G.isInfoMenu=!0,MN.heat=MN.getHeatMap(),SU.play("info")):e>=.1*a-30&&e<=.1*a+30&&i>=n/r-30&&i<=n/r+30&&(G.isInfoMenu=!1,MN.heat=MN.getHeatMap(),SU.play("info"))},update:function(){MN.process()}};var CC,RN,WD,SM,cloud,sunMoon,wind,rain,diffInWeatherTime=5;Cloud.prototype={drawArcs:function(t,e,i,a){bp(),mt(t/i+150,e-15),qct(t/i+150+50,e-15+0,t/i+150+40,e-15+40),ctx.lineWidth=4,sts(thisWeather.hexToRgb(thisWeather.getColor(),.8)),st(),bp(),mt(t/i+20+30,e+10),qct(t/i+30,e+35+10,t/i+30+60,e+35+15),st()},draw:function(t,e,i,a){var n=thisWeather.hexToRgb(thisWeather.getColor(),.5);ctx.scale(i,a),bp(),fs(n),mt(t/i,e),bct(t/i-40,e+20,t/i-40,e+70,t/i+60,e+70),bct(t/i+80,e+100,t/i+150,e+100,t/i+170,e+70),bct(t/i+250,e+70,t/i+250,e+40,t/i+220,e+20),bct(t/i+260,e-40,t/i+200,e-50,t/i+170,e-30),bct(t/i+150,e-75,t/i+80,e-60,t/i+80,e-30),bct(t/i+30,e-75,t/i-20,e-60,t/i,e),cp(),ctx.shadowColor=thisWeather.hexToRgb(thisWeather.getColor(),.8),ctx.shadowOffsetX=-3,ctx.shadowOffsetY=3,ctx.shadowBlur=10,ctx.lineWidth=3,sts(thisWeather.hexToRgb(thisWeather.getColor(),.8)),st(),fl(),this.drawArcs(t,e,i,a)},update:function(){this.x-=this.speed,this.x+250<0&&(this.x=CC.w+250,this.y=this.y+utils.getRandomInt(-10,10));var t=this.x,e=this.y;sv(),this.draw(t,e,.8,.7),this.draw(t,e,.7,.6),this.draw(t,e,.6,.6),this.draw(t,e,.5,.7),rs()}},SunMoon.prototype={getColor:function(){var t;switch(G.period){case"morning":t=this.isSun?"#ffff9e":"#fff";break;case"afternoon":t=this.isSun?"yellow":"#fff";break;case"evening":t=this.isSun?"#e28800":"#fff";break;case"night":this.isSun,t="#fff"}return t},resetPos:function(){this.x=0,this.y=100,G.period="morning",thisWeather.step=0},update:function(){return Weather.dt/1e3%(5*diffInWeatherTime)>5*diffInWeatherTime||Weather.dt/1e3%(5*diffInWeatherTime)>4*diffInWeatherTime||Weather.dt/1e3%(5*diffInWeatherTime)>3*diffInWeatherTime?G.period="night":Weather.dt/1e3%(5*diffInWeatherTime)>2*diffInWeatherTime?G.period="evening":Weather.dt/1e3%(5*diffInWeatherTime)>1*diffInWeatherTime?G.period="afternoon":G.period="morning",this.x+=G.can.width/(2*diffInWeatherTime)/fps,this.x>G.can.width?(this.resetPos(),void(this.isSun=!this.isSun)):(this.y-=.1,sv(),ctx.shadowColor=this.getColor(),ctx.shadowOffsetX=-3,ctx.shadowOffsetY=3,ctx.shadowBlur=10,bp(),ar(this.x,this.y,this.r,0,2*Math.PI,!0),cp(),ctx.fillStyle=this.getColor(),fl(),rs(),sv(),bp(),ctx.fillStyle=thisWeather.hexToRgb("#444",.5),this.isSun||ar(this.x+5,this.y-5,20,0,2*Math.PI,!0),cp(),fl(),rs(),void thisWeather.updateGradient())}},Wind.prototype={create:function(){for(var t=0;tCC.h&&(RN.particles[t]=new Droplet(t))}}},Weather.prototype={updateGradient:function(){var t=thisWeather.colors[thisWeather.colorIndices[0]],e=thisWeather.colors[thisWeather.colorIndices[1]],i=(thisWeather.colors[thisWeather.colorIndices[2]],thisWeather.colors[thisWeather.colorIndices[3]],1-thisWeather.step),a=Math.round(i*t[0]+thisWeather.step*e[0]),n=Math.round(i*t[1]+thisWeather.step*e[1]),r=Math.round(i*t[2]+thisWeather.step*e[2]),o="rgb("+a+","+n+","+r+")",s=Math.round(255*i+255*thisWeather.step),l=Math.round(255*i+255*thisWeather.step),h=Math.round(255*i+255*thisWeather.step),c="rgb("+s+","+l+","+h+")",d=ctx.createLinearGradient(0,0,0,G.can.height);if(d.addColorStop(0,o),d.addColorStop(.9,c),G.backgroundColor=d,thisWeather.step+=SM.isSun?.0076:.0076/2.22,thisWeather.step>=1){thisWeather.step=0;for(var f=0;fCC.h-P.fireOffset&&(PS.finished=!0),PS.x-=G.speed}};var flameBack=new function(){var t,e,i,a,n,r,o,s,l=2,h=2.5,c=5;this.time=new Date,this.canvas=void 0,this.init=function(){t=this.canvas.getContext("2d"),o=(this.canvas.width+30)/l,s=P.fireOffset/l,o=Math.ceil(o),s=Math.ceil(s),r=Array(o*s);for(var e=0;e=1;t--)for(var e=s;e--;){var i=r[y(t-1,e-1)]+r[y(t,e-1)]+r[y(t+1,e-1)]+r[y(t-1,e)]+r[y(t+1,e)]+r[y(t-1,e+1)]+r[y(t,e+1)]+r[y(t+1,e+1)]>>3;i=Math.max(0,i-g(h)),r[y(t,e-1)]=i,e2500?++Pl.isBlink:0,t=Pl.isBlink>5?"#000":"#fff",Pl.isBlink>=8&&(Pl.t=e,Pl.isBlink=0),bp(t),Pl.died?(ar(Pl.x-2*(Pl.h/3)+6,Pl.y+4,2,0,2*M.PI,!0),ar(Pl.x-2*(Pl.h/3)+6,Pl.y+10,2,0,2*M.PI,!0)):(ar(Pl.x+2*(Pl.w/3)-2,Pl.y+5,2,0,2*M.PI,!0),ar(Pl.x+(Pl.w-3),Pl.y+5,2,0,2*M.PI,!0)),ctx.fillStyle=t,fl()}},legs:function(){var t=3;fs("#000"),Pl.died?(fr(Pl.x,Pl.y+Pl.w/3,Pl.h/3,t),fr(Pl.x,Pl.y+2*(Pl.w/3),Pl.h/3,t)):(fr(Pl.x+Pl.w/3-t/2,Pl.y+2*(Pl.h/3),t,Pl.h/3),fr(Pl.x+2*(Pl.w/3)-t/2,Pl.y+2*(Pl.h/3),t,Pl.h/3))},fe:function(){fs("#eaeaea"),bp(),el(ctx,Pl.x-30,Pl.y+Pl.h,80,10,"#000"),cp(),fs("#EAEAEA"),fl(),ctx.lineWidth=1,sts("#dedede"),st(),fs("#8ED6FF");for(var t=-30;t0?(Pl.y-=Pl.bounceHeight,Pl.x+=1.5*G.speed,Pl.bounceFactor-=2):Pl.isInAir&&!Pl.bounceFactor?(Pl.y-=Pl.bounceHeight/2,Pl.x+=1.5*G.speed,Pl.bounceFactor-=2):Pl.isInAir&&Pl.bounceFactor===-2?(Pl.y-=0,Pl.x+=1.5*G.speed,Pl.bounceFactor-=2):Pl.isInAir&&Pl.bounceFactor===-4?(Pl.y-=Pl.bounceHeight/2,Pl.x+=1.5*G.speed,Pl.bounceFactor-=2):Pl.isInAir&&Pl.bounceFactor===-6&&(Pl.y+=Pl.bounceHeight,Pl.x+=1.5*G.speed,Pl.bounceFactor=-6),Pl.died&&!Pl.busted&&(Pl.y+=10,Pl.isCornerStrike?Pl.y>CC.h-3*P.fireOffset&&(Pl.busted||(Pl.busted=!0)):Pl.busted||(Pl.busted=!0)),fs("#000"),Pl.body(),Pl.legs(),Pl.eyes(),Pl.tears(2,6),Pl.tears(5,15),Pl.isInAir&&!Pl.busted&&Pl.fe(),Pl.busted&&Pl.burst(),Pl.checkCollision()},keyDown:function(t){if(!Pl.busted){if(32===t){if(Pl.irj=!0,Pl.h<50)return;Pl.h-=2,Pl.y+=2}else 39===t&&(Pl.x+=G.speed);Pl.irj&&(Pl.irj=!1,Pl.isInAir=!0,Pl.isKarmaLocked=!1,Pl.bounceFactor=Pl.maxH-Pl.h,Pl.bounceFactor*=4,Pl.h=Pl.maxH)}},keyUp:function(){},checkCollision:function(){if(Pl.x<=0)return Pl.died=!0,void(Pl.isCornerStrike=!0);if(Pl.y>CC.h-P.fireOffset)return Pl.died=!0,void(Pl.isCornerStrike=!0);if(Pl.x+Pl.w>CC.w)return Pl.died=!0,void(Pl.isCornerStrike=!0);if(Pl.y<0)return Pl.died=!0,void(Pl.isCornerStrike=!0);var t,e;for(t=0;t=e.x&&i=e.x){for(var a=0;a=e.y){G.trees[t].flame=null,Pl.isInAir=!1,Pl.liesOn=t,Pl.isKarmaLocked||Pl.liesOn===Pl.lastLiesOn||(G.karma&&SU.play("moveAhead"),G.karma+=1),Pl.isKarmaLocked=!0,Pl.lastLiesOn=Pl.liesOn;break}if(Pl.y>=e.y&&Pl.yG.highscore&&(SU.play("highestScore"),G.highscore=G.karma,utils.setLocalStorageData(G.karma)),SU.play("gameOver"),G.menu=new Menu},cycle:function(){var t=(new Date).getTime();if(dt=t-time,!(dt<1e3/fps)){if(time=t,G.menu)return void(G.menu.update&&G.menu.update());G.canExplode&&M.ceil((t-G.gameStartTime)/1e3)%6===0?(G.mildExplosion?SU.play("explosion2"):SU.play("explosion1"),G.mildExplosion=!G.mildExplosion,G.canExplode=!1):M.ceil((t-G.gameStartTime)/1e3)%7===0&&(G.canExplode=!0),G.canSpeedBeIncreased&&M.ceil((t-G.gameStartTime)/1e3)%10===0?(G.speed+=G.isMobile()?.1:.2,WD.speed=utils.getRandomInt(1,30),G.canSpeedBeIncreased=!1):M.ceil((t-G.gameStartTime)/1e3)%11===0&&(G.canSpeedBeIncreased=!0),fs(G.backgroundColor),fr(0,0,CC.w,CC.h);var e=G.isMobile()?1.1:1.6;if(G.speed>=e&&10===utils.getRandomInt(0,10)&&(G.showNoisyScreen(),4===utils.getRandomInt(0,10)&&SU.play("glitch")),weather.update(),ctx.font="15px Comic Sans",ctx.fillStyle=thisWeather.hexToRgb(thisWeather.getColor(!0),1),ctx.fillText("KARMA: "+G.karma,25,25),ctx.fillText("SPEED: "+G.speed.toFixed(1)+" mph",G.can.width-130,25),ctx.fillText("WIND: "+WD.speed.toFixed(1)+" mph W",G.can.width-130,45),ctx.lineWidth=3,G.trees.length){for(var i=0;if;f++)this[String.fromCharCode(97+f)]=e[f]||0;0.01>this.c&&(this.c=0.01);e=this.b+this.c+this.e;0.18>e&&(e=0.18/e,this.b*=e,this.c*=e,this.e*=e)}} 68 | var W=new function(){this.A=new J;var e,f,d,g,l,z,K,L,M,A,m,N;this.reset=function(){var c=this.A;g=100/(c.f*c.f+0.001);l=100/(c.g*c.g+0.001);z=1-0.01*c.h*c.h*c.h;K=1E-6*-c.i*c.i*c.i;c.a||(m=0.5-c.n/2,N=5E-5*-c.o);L=0a.q?-1020:1020),S=a.p?(2E4*(1-a.p)*(1-a.p)|0)+32:0,ba=a.d,T=a.j/2,ca=0.01*a.k*a.k,E=a.a,F=e,da=1/e,ea=1/f,fa=1/d,a=5/(1+20*a.u*a.u)*(0.01+n);0.8=S&&(V=0,this.reset());A&&++M>=A&&(A=0,g*=L);z+=K;g*=z;g>l&&(g=l,0<$&&(G=!0));h=g;0< 70 | T&&(I+=ca,h*=1+Math.sin(I)*T);h|=0;8>h&&(h=8);E||(m+=N,0>m?m=0:0.5F)switch(v=0,++U){case 1:F=f;break;case 2:F=d}switch(U){case 0:w=v*da;break;case 1:w=1+2*(1-v*ea)*ba;break;case 2:w=1-v*fa;break;case 3:w=0,G=!0}R&&(D+=aa,s=D|0,0>s?s=-s:1023r?r=1E-5:0.1=h&&(p%=h,3==E))for(x=y.length;x--;)y[x]=2*Math.random()-1;switch(E){case 0:b=p/hb?1.27323954*b+0.405284735*b*b:1.27323954*b-0.405284735*b*b;b=0>b?0.225*(b*-b-b)+b:0.225*(b*b-b)+b;break;case 3:b=y[Math.abs(32*p/h|0)]}P&&(x=u,n*=X,0>n?n=0:0.1=q?-32768:32767*q|0}return O}}; 72 | window.jsfxr=function(e){W.A.B(e);var f=W.D();e=new Uint8Array(4*((f+1)/2|0)+44);var f=2*W.C(new Uint16Array(e.buffer,44),f),d=new Uint32Array(e.buffer,0,44);d[0]=1179011410;d[1]=f+36;d[2]=1163280727;d[3]=544501094;d[4]=16;d[5]=65537;d[6]=44100;d[7]=88200;d[8]=1048578;d[9]=1635017060;d[10]=f;for(var f=f+44,d=0,g="data:audio/wav;base64,";d>18]+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[l>> 73 | 12&63]+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[l>>6&63]+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[l&63]);d-=f;return g.slice(0,g.length-d)+"==".slice(0,d)}; 74 | // For sound Effects 75 | var soundUtils = SU = { 76 | rd: function (a, b){ 77 | if(!b){ 78 | b = a; 79 | a = 0; 80 | } 81 | return Math.random() * (b - a) + a; 82 | }, 83 | rp: function (a){ 84 | return a[~~this.rd(a.length)]; 85 | }, 86 | soundEffect: function(sid, settings){ 87 | SU[sid] = []; 88 | 89 | settings.forEach(function(sound){ 90 | var audio = new Audio(); 91 | audio.src = jsfxr(sound); 92 | 93 | SU[sid].push(audio); 94 | }); 95 | }, 96 | play: function(sid) { 97 | if (!G.isSound) { 98 | return; 99 | } 100 | SU[sid] && SU.rp(SU[sid]).play(); 101 | } 102 | }; 103 | 104 | 105 | SU.soundEffect('gameOver', [ 106 | [2,0.2,0.01,,0.83,0.24,,,,0.62,0.6,,,0.1248,0.4522,,,,0.4,,,,,0.6] 107 | ]); 108 | SU.soundEffect('moveAhead', [ 109 | [2,,0.2047,,0.3986,0.5855,0.2236,-0.1697,,,,,,0.7882,-0.2576,,,,1,,,,,0.43] 110 | ]); 111 | SU.soundEffect('highestScore', [ 112 | [0,,0.016,0.4953,0.3278,0.6502,,,,,,0.4439,0.6322,,,,,,1,,,,,1] 113 | ]); 114 | SU.soundEffect('explosion1', [ 115 | [3,,0.3729,0.6547,0.4138,0.0496,,,,,,,,,,,,,1,,,,,0.4] 116 | ]); 117 | SU.soundEffect('explosion2', [ 118 | [3,0.43,0.61,0.3794,0.86,0.17,0.17,0.1399,0.1,0.07,0.06,0.04,0.1,,,0.96,0.26,-0.16,1,,,,,0.15] 119 | ]); 120 | SU.soundEffect('info', [ 121 | [2,,0.1889,,0.111,0.2004,,,,,,,,0.1157,,,,,1,,,0.1,,1] 122 | ]); 123 | SU.soundEffect('soundOn', [ 124 | [2,,0.2,,0.1753,0.64,,-0.5261,,,,,,0.5522,-0.564,,,,1,,,,,0.5] 125 | ]); 126 | SU.soundEffect('playGame', [ 127 | [2,,0.261,0.2142,0.2005,0.4618,0.0137,-0.3602,,,,,,0.2249,0.0858,,,,1,,,0.0001,,0.44] 128 | ]); 129 | SU.soundEffect('glitch', [ 130 | [3,,0.0272,0.5654,0.1785,0.7424,,,,,,0.2984,0.5495,,,,,,1,,,,,0.43] 131 | ]) 132 | var SF, Flame, H, PI_2, Smoke, Trail, W, SmokyFlame, drawCircle, rand, w, slice = [].slice; 133 | 134 | PI_2 = 2 * Math.PI; 135 | rand = function (a, b) { 136 | return (b - a) * Math.random() + a; 137 | }; 138 | drawCircle = function (x, y, r, style) { 139 | bp(); 140 | ar(x, y, r, 0, PI_2, false); 141 | ctx.fillStyle = style; 142 | return fl(); 143 | }; 144 | 145 | Smoke = function () { 146 | function Smoke(x, y) { 147 | this.opacity = 0.8; 148 | this.x = x; 149 | this.y = y; 150 | this.r = 2.0; 151 | } 152 | Smoke.prototype.step = function (x, y, w) { 153 | y -= utils.getRandomInt(rand(60,70), rand(200,350)) 154 | // y -= rand(0, 3); 155 | x += rand(-2, 2); 156 | this.opacity -= 0.04; 157 | if (this.opacity <= 0) { 158 | return this.destroyed = true; 159 | } 160 | }; 161 | Smoke.prototype.draw = function (x, y, w) { 162 | y -= utils.getRandomInt(60, 150) 163 | x += utils.getRandomInt(rand(-w+w/2, 0), rand(0,w-w/2)) 164 | if (this.opacity <= 0) { 165 | return; 166 | } 167 | return drawCircle(x, y, this.r, 'rgba(60,60,60,' + this.opacity + ')'); 168 | }; 169 | return Smoke; 170 | }(); 171 | Trail = function () { 172 | function Trail(x, y) { 173 | this.opacity = 1; 174 | this.x = x; 175 | this.y = y; 176 | this.r = 12; 177 | } 178 | Trail.prototype.step = function (x, y, w) { 179 | this.r = w / 5; 180 | y -= rand(0, 8); 181 | x -= rand(-3, 3); 182 | this.opacity -= 0.03; 183 | if (this.opacity <= 0) { 184 | this.destroyed = true; 185 | if (rand(0, 1) < 0.5) { 186 | return SF.addEntity(Smoke, x, y - this.r); 187 | } 188 | } 189 | }; 190 | Trail.prototype.draw = function (x, y, w) { 191 | this.r = w / 6; 192 | y -= rand(rand(-45, 5), rand(25, 75)); 193 | x -= rand(-w/2 - 20, w/2 + 20); 194 | var color, color2, g, rg; 195 | if (this.opacity <= 0) { 196 | return; 197 | } 198 | color = 'rgba(255,' + ~~(240 * this.opacity) + ',0,' + this.opacity + ')'; 199 | color2 = 'rgba(255,' + ~~(240 * this.opacity) + ',0,0)'; 200 | rg = this.r * 1.5 + rand(0, 2); 201 | g = ctx.createRadialGradient(x, y, 0, x, y, rg); 202 | g.addColorStop(0, color); 203 | g.addColorStop(1, color2); 204 | drawCircle(x, y, this.r, g); 205 | return drawCircle(x, y, this.r * this.opacity, color); 206 | }; 207 | return Trail; 208 | }(); 209 | Flame = function () { 210 | function Flame() { 211 | this.x = G.can.width / 2; 212 | this.y = G.can.height / 2 + 90; 213 | this.r = 24; 214 | this.rg = 22; 215 | } 216 | Flame.prototype.step = function (x, y, w) { 217 | return false; 218 | }; 219 | Flame.prototype.draw = function (x, y, w) { 220 | this.g = ctx.createRadialGradient(x, y, 0, x, y, w * 1.2); 221 | this.g.addColorStop(0, 'rgba(255,255,255,1)'); 222 | this.g.addColorStop(1, 'rgba(255,120,0,0)'); 223 | 224 | 225 | var g, i, j; 226 | //for (i = j = 1; j <= 1; i = ++j) { 227 | SF.addEntity(Trail, x, y - this.r / 3); 228 | //} 229 | g = ctx.createRadialGradient(x, y, 0, x, y, this.rg); 230 | g.addColorStop(0, 'rgba(255,180,0,' + rand(0.2, 0.9) + ')'); 231 | g.addColorStop(1, 'rgba(255,180,0,0)'); 232 | drawCircle(x, y, this.rg, g); 233 | return drawCircle(x + rand(-1.5, 1.5), y + rand(-1.5, 1.5), w, this.g ); 234 | }; 235 | return Flame; 236 | }(); 237 | 238 | SmokyFlame = function () { 239 | function SmokyFlame() { 240 | SF = this; 241 | this.entities = {}; 242 | this.i = 0; 243 | this.ii = 0; 244 | // this.update(); 245 | } 246 | SmokyFlame.prototype.addEntity = function () { 247 | var args, klass; 248 | klass = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; 249 | this.entities[this.i] = function (func, args, ctor) { 250 | ctor.prototype = func.prototype; 251 | var child = new ctor(), result = func.apply(child, args); 252 | return Object(result) === result ? result : child; 253 | }(klass, args, function () { 254 | }); 255 | return this.i += 1; 256 | }; 257 | SmokyFlame.prototype.update = function (x, y, w) { 258 | var entity, k, ref; 259 | // ctx.clearRect(0, 0, W, H); 260 | ref = this.entities; 261 | for (k in ref) { 262 | entity = ref[k]; 263 | if (entity.destroyed === true) { 264 | delete this.entities[k]; 265 | continue; 266 | } 267 | 268 | if (this.ii % 5 === 0) { 269 | entity.step(x + (w / 2), y - 10, w); 270 | entity.draw(x + (w / 2), y - 10, w); 271 | this.ii = 0; 272 | } 273 | this.ii++; 274 | } 275 | }; 276 | return SmokyFlame; 277 | }(); 278 | var MN; 279 | function Menu() { 280 | MN = this; 281 | this.y = 0; 282 | this.font = '50px Helvetica'; 283 | this.fireColor = 'rgb(255, 56, 8)'; 284 | 285 | ctx.fillStyle = '#fff' 286 | ctx.fillRect(0, 0, G.can.width, G.can.height); 287 | 288 | this.heat = MN.getHeatMap(); 289 | this.noise = null; 290 | this.noise = MN.getNoise(G.can.width, G.can.height*8); 291 | ctx.drawImage(this.heat, 0, 0); 292 | this.update(); 293 | } 294 | Menu.prototype = { 295 | getNoise: function () { 296 | var canvas = document.createElement('canvas'); 297 | canvas.width = G.can.width; 298 | canvas.height = G.can.height; 299 | var ctx = canvas.getContext('2d'); 300 | 301 | var w = canvas.width, h = canvas.height, 302 | img = ctx.createImageData(w, h), 303 | n = w * h * 4; 304 | 305 | for(var i = 0; i < n; i+=4) { 306 | img.data[i] = 15; 307 | img.data[i+1] = 3; 308 | img.data[i+2] = 1; 309 | img.data[i+3] = Math.floor(Math.random() * 128); 310 | } 311 | sv(); 312 | ctx.putImageData(img, 0, 0); 313 | ctx.drawImage(canvas, 0, 0, w * 64, h * 64); 314 | ctx.globalAlpha = 0.5; 315 | ctx.drawImage(canvas, 0, 0, w * 16, h * 16); 316 | var img = ctx.getImageData(0, 0, w, h); 317 | // increase contrast a bit by clamping values 318 | for (var i = 3; i < w * h * 4; i += 4){ 319 | if (img.data[i] > 220){ 320 | img.data[i] = 255; 321 | } 322 | if (img.data[i] < 40){ 323 | img.data[i] = 0; 324 | } 325 | } 326 | ctx.putImageData(img, 0, 0); 327 | rs(); 328 | return canvas; 329 | }, 330 | getHeatMap: function () { 331 | var canvas = document.createElement('canvas'); 332 | canvas.width = G.can.width; 333 | canvas.height = G.can.height; 334 | 335 | var ctx = canvas.getContext('2d'); 336 | sv(); 337 | var w = G.can.width, 338 | h = G.can.height, 339 | color = MN.fireColor, 340 | firstText = G.isGameOver ? 'GAME' : 'SAVE', 341 | secondText = G.isGameOver ? 'OVER' : 'THE'; 342 | thirdText = G.isGameOver ? '' : 'FOREST'; 343 | 344 | if (G.isMobile()) { 345 | firstText = firstText.split('').join(' '); 346 | secondText = secondText.split('').join(' '); 347 | thirdText = thirdText.split('').join(' '); 348 | } else { 349 | firstText = firstText.split('').join(' '); 350 | secondText = secondText.split('').join(' '); 351 | thirdText = thirdText.split('').join(' '); 352 | } 353 | 354 | ctx.fillStyle = color; 355 | ctx.strokeStyle = color; 356 | ctx.font = MN.font; 357 | 358 | var m1 = ctx.measureText(firstText); 359 | var m2 = ctx.measureText(secondText); 360 | var m3 = ctx.measureText(thirdText); 361 | ctx.fillText(firstText, (w - m1.width) / 2, h / 6); 362 | ctx.fillText(secondText, (w - m2.width) / 2, h / 4); 363 | ctx.fillText(thirdText, (w - m3.width) / 2, h / 3); 364 | ctx.lineWidth = 10; 365 | 366 | if (!G.isInfoMenu) { 367 | var highestScoreText = 'BEST: ' + G.highscore; 368 | if (G.isMobile()) { 369 | highestScoreText = highestScoreText.split('').join(' '); 370 | } else { 371 | highestScoreText = highestScoreText.split('').join(' '); 372 | } 373 | ctx.fillStyle = '#fff'; 374 | ctx.font = '35px Helvetica'; 375 | ctx.fillText(highestScoreText, (w - ctx.measureText(highestScoreText).width) / 2, h / 2.1); 376 | 377 | // Sound circle 378 | ctx.beginPath(); 379 | ctx.arc(w*(1/4), h/1.2, 30, 0, 2 * Math.PI, false); 380 | ctx.fillStyle = '#555'; 381 | ctx.closePath(); 382 | ctx.fill(); 383 | 384 | // Rules / Instructions circle 385 | ctx.beginPath(); 386 | ctx.arc(w*(3/4), h/1.2, 30, 0, 2 * Math.PI, false); 387 | ctx.fillStyle = '#555'; 388 | ctx.closePath(); 389 | ctx.fill(); 390 | 391 | // sound icon 392 | ctx.beginPath(); 393 | ctx.moveTo(w*(1/4) - 20, h/1.2 - 10); 394 | ctx.lineTo(w*(1/4) - 20, h/1.2 + 5); 395 | ctx.lineTo(w*(1/4) - 10, h/1.2 + 5); 396 | ctx.lineTo(w*(1/4) + 5, h/1.2 + 15); 397 | ctx.lineTo(w*(1/4) + 5, h/1.2 - 20); 398 | ctx.lineTo(w*(1/4) - 10, h/1.2 - 10); 399 | ctx.fillStyle = '#222'; 400 | ctx.closePath(); 401 | if (G.isSound) { 402 | ctx.fillRect(w*(1/4) + 10, h/1.2 - 5, 3, 10); 403 | ctx.fillRect(w*(1/4) + 15, h/1.2 - 7, 3, 15); 404 | ctx.fillRect(w*(1/4) + 20, h/1.2 - 10, 3, 20); 405 | } 406 | ctx.fill(); 407 | 408 | // if no sound, show / on icon 409 | if (!G.isSound) { 410 | ctx.save(); 411 | ctx.beginPath(); 412 | ctx.moveTo(w*(1/4) + 10, h/1.2 - 22); 413 | ctx.lineTo(w*(1/4) - 10, h/1.2 + 22); 414 | ctx.closePath(); 415 | ctx.fill(); 416 | ctx.lineWidth = 5; 417 | ctx.strokeStyle = '#000'; 418 | ctx.stroke(); 419 | ctx.restore(); 420 | } 421 | 422 | // instructions icon 423 | ctx.fillRect(w*(3/4) - 2, h/1.2, 5, 15); 424 | ctx.beginPath(); 425 | ctx.arc(w*(3/4), h/1.2 - 10, 5, 0, 2 * Math.PI, false); 426 | ctx.closePath(); 427 | ctx.fillStyle = '#222'; 428 | ctx.fill(); 429 | 430 | if (G.isGameOver) { 431 | ctx.fillStyle = '#fff'; 432 | ctx.font = '35px Helvetica'; 433 | var karmaText = 'KARMA: ' + G.karma; 434 | 435 | if (G.isMobile()) { 436 | karmaText = karmaText.split('').join(' '); 437 | } else { 438 | karmaText = karmaText.split('').join(' '); 439 | } 440 | 441 | ctx.fillText(karmaText, (w - ctx.measureText(karmaText).width) / 2, h / 2.5); 442 | ctx.lineWidth = 10; 443 | 444 | ctx.beginPath(); 445 | ctx.arc(w*(2/4), h/1.2, 30, 0, 2 * Math.PI, false); 446 | ctx.fillStyle = '#555'; 447 | ctx.closePath(); 448 | ctx.fill(); 449 | 450 | // download icon 451 | ctx.beginPath(); 452 | ctx.moveTo(w*(2/4) - 10, h/1.2 - 15); 453 | ctx.lineTo(w*(2/4) - 10, h/1.2 - 15 + 15); 454 | ctx.lineTo(w*(2/4) - 20, h/1.2 - 15 + 15); 455 | 456 | ctx.lineTo(w*(2/4), h/1.2 - 15 + 35); 457 | ctx.lineTo(w*(2/4) + 20, h/1.2 - 15 + 15); 458 | 459 | ctx.lineTo(w*(2/4) + 10, h/1.2 - 15 + 15); 460 | ctx.lineTo(w*(2/4) + 10, h/1.2 - 15); 461 | 462 | ctx.fillStyle = '#222'; 463 | ctx.closePath(); 464 | ctx.fill(); 465 | } 466 | 467 | // Play button 468 | ctx.beginPath(); 469 | ctx.arc(w/2, h/1.6, 50, 0, 2 * Math.PI, false); 470 | ctx.fillStyle = '#793f02'; 471 | ctx.closePath(); 472 | ctx.fill(); 473 | 474 | var tw = 20, th = h/1.6 - tw; 475 | ctx.beginPath(); 476 | ctx.moveTo(w/2 - tw/2, th); 477 | ctx.lineTo(w/2 + tw, th + 20); 478 | ctx.lineTo(w/2 - tw/2, th + 40); 479 | ctx.fillStyle = '#fff'; 480 | ctx.closePath(); 481 | ctx.fill(); 482 | } else { 483 | // back button 484 | var hFactor = G.isMobile() ? 10 : 4.4; 485 | 486 | ctx.beginPath(); 487 | ctx.arc(w/10, h/hFactor, 30, 0, 2 * Math.PI, false); 488 | ctx.fillStyle = '#555'; 489 | ctx.closePath(); 490 | ctx.fill(); 491 | 492 | ctx.beginPath(); 493 | ctx.moveTo(w/10, h/hFactor-5); 494 | ctx.lineTo(w/10, h/hFactor-5 - 10); 495 | ctx.lineTo(w/10 - 20, h/hFactor-5 + 5); 496 | ctx.lineTo(w/10, h/hFactor-5 + 20); 497 | ctx.lineTo(w/10, h/hFactor-5 + 10); 498 | ctx.lineTo(w/10 + 20, h/hFactor-5 + 10); 499 | ctx.lineTo(w/10 + 20, h/hFactor-5); 500 | ctx.closePath(); 501 | ctx.fillStyle = '#000'; 502 | ctx.fill(); 503 | 504 | // show info 505 | var instructionLines = [ 506 | 'Save our planet Earth!', 507 | 'Protect Forest! Don\'t burn them!', 508 | 'Abrupt climatic changes. Time to worry!', 509 | 'Extinguish fire on trees.', 510 | 'Hit spacebar or tap to jump player.', 511 | 'Earn Karma! Nature will show her love!', 512 | 'JS13KGames 16 - hidden glitches', 513 | 'Climate Abnormalities, Player Loves Trees', 514 | '(Player struggles to jump off tree)', 515 | 'More hinderances once speed > 1.6 mph' 516 | ]; 517 | ctx.font = G.isMobile() ? '15px Helvetica' : '20px Helvetica'; 518 | ctx.fillStyle = '#fff'; 519 | for (var l = 0; l < instructionLines.length; l++) { 520 | var line = instructionLines[l]; 521 | var hOffset = G.isMobile() ? l*40 : l*45; 522 | if (l === 0 || l === 2 || l === 4 || l === 6) { 523 | ctx.beginPath(); 524 | ctx.arc(w / 10, h/2.6 + hOffset, 10, 0, 2*Math.PI, false); 525 | ctx.fill(); 526 | ctx.closePath(); 527 | } 528 | ctx.fillText(line, w/10 + (G.isMobile() ? 25: 50), h/2.6 + hOffset); 529 | } 530 | } 531 | rs(); 532 | return canvas; 533 | }, 534 | process: function () { 535 | sv(); 536 | // cooldown factor 537 | ctx.globalAlpha = 0.35; 538 | ctx.globalCompositeOperation = 'source-over'; 539 | // movement speed of cooldown map 540 | MN.y = (MN.y + 3) % MN.noise.height; 541 | // flickering of cooldown map 542 | x = Math.round(Math.random() * 5) * 0; 543 | ctx.drawImage(MN.noise, x, MN.y); 544 | ctx.drawImage(MN.noise, x, MN.y - MN.noise.height); 545 | 546 | // spread of the flame 547 | ctx.globalAlpha = 1.0; 548 | // whind 549 | x = 1 - Math.random() * 2; 550 | // move flame up 551 | ctx.drawImage(G.can, x, -1); 552 | ctx.globalAlpha = 0.13; 553 | ctx.globalCompositeOperation = 'lighter'; 554 | ctx.drawImage(G.can, x, -1); 555 | 556 | // heat it up 557 | ctx.globalAlpha = 0.22; 558 | ctx.drawImage(MN.heat, 0, 0); 559 | fs(MN.fireColor); 560 | bp(); 561 | ctx.globalAlpha = 0.52; 562 | cp(); 563 | fl(); 564 | rs(); 565 | }, 566 | mouseDown: function (e, x, y) { 567 | var w = G.can.width, 568 | h = G.can.height, 569 | ctx = MN.heat.getContext('2d'); 570 | 571 | var hFactor = G.isMobile() ? 10 : 4.4; 572 | 573 | if (x >= w/2 - 50 && x <= w/2 + 50 && 574 | y >= h/1.6 - 50 && y <= h/1.6 + 50) { 575 | // play btn clicked 576 | G.menu = null; 577 | G.restart(); 578 | SU.play('playGame'); 579 | } else if (x >= w*(2/4) - 30 && x <= w*(2/4) + 30 && 580 | y >= h/1.2 - 30 && y <= h/1.2 + 30) { 581 | // download clicked 582 | downloadCanvas(); 583 | SU.play('download'); 584 | } else if (x >= w*(1/4) - 30 && x <= w*(1/4) + 30 && 585 | y >= h/1.2 - 30 && y <= h/1.2 + 30) { 586 | // sound clicked 587 | G.isSound = +(!G.isSound); 588 | G.isSound && SU.play('soundOn'); 589 | utils.setLocalStorageData(G.isSound, true); 590 | MN.heat = MN.getHeatMap(); 591 | } else if (x >= w*(3/4) - 30 && x <= w*(3/4) + 30 && 592 | y >= h/1.2 - 30 && y <= h/1.2 + 30) { 593 | // info clicked 594 | G.isInfoMenu = true; 595 | MN.heat = MN.getHeatMap(); 596 | SU.play('info'); 597 | } else if (x >= w*(1/10) - 30 && x <= w*(1/10) + 30 && 598 | y >= h/hFactor - 30 && y <= h/hFactor + 30) { 599 | // back btn clicked 600 | G.isInfoMenu = false; 601 | MN.heat = MN.getHeatMap(); 602 | SU.play('info'); 603 | } 604 | }, 605 | update: function () { 606 | // this.noise = MN.getNoise(G.can.width, G.can.height * 8); 607 | MN.process(); 608 | } 609 | }; 610 | 611 | var CC, RN, WD, SM, cloud, sunMoon, wind, rain; 612 | var diffInWeatherTime = 5; 613 | function Cloud() { 614 | this.color = 'blue'; 615 | this.x = G.can.width || P.w; 616 | this.y = 100; 617 | this.speed = 7; 618 | this.update(); 619 | } 620 | Cloud.prototype = { 621 | drawArcs: function (x, y, sx, sy) { 622 | bp(); 623 | mt(x/sx + 150, y - 15); // 188, 50 624 | qct( 625 | x/sx + 150 + 50, 626 | y - 15 + 0, 627 | x/sx + 150 + 40, 628 | y - 15 + 40 629 | ); 630 | ctx.lineWidth = 4; 631 | sts(thisWeather.hexToRgb(thisWeather.getColor(), 0.8)) 632 | st(); 633 | 634 | bp(); 635 | mt(x/sx + 20 + 30, y + 10); // 188, 50 636 | qct( 637 | x/sx + 30, 638 | y + 35 + 10, 639 | x/sx + 30 + 60, 640 | y + 35 + 15 641 | ); 642 | st(); 643 | }, 644 | draw: function (x, y, sx, sy) { 645 | var cloudColor = thisWeather.hexToRgb(thisWeather.getColor(), 0.5); 646 | 647 | ctx.scale(sx, sy); 648 | bp(); 649 | fs(cloudColor) 650 | mt(x/sx, y); 651 | bct(x/sx - 40, y + 20, x/sx - 40, y + 70, x/sx + 60, y + 70); 652 | bct(x/sx + 80, y + 100, x/sx + 150, y + 100, x/sx + 170, y + 70); 653 | bct(x/sx + 250, y + 70, x/sx + 250, y + 40, x/sx + 220, y + 20); 654 | bct(x/sx + 260, y - 40, x/sx + 200, y - 50, x/sx + 170, y - 30); 655 | bct(x/sx + 150, y - 75, x/sx + 80, y - 60, x/sx + 80, y - 30); 656 | bct(x/sx + 30, y - 75, x/sx - 20, y - 60, x/sx, y); 657 | cp(); 658 | ctx.shadowColor = thisWeather.hexToRgb(thisWeather.getColor(), 0.8); 659 | ctx.shadowOffsetX = -3; 660 | ctx.shadowOffsetY = 3; 661 | ctx.shadowBlur = 10; 662 | ctx.lineWidth = 3; 663 | sts(thisWeather.hexToRgb(thisWeather.getColor(), 0.8)) 664 | st(); 665 | fl(); 666 | 667 | this.drawArcs(x, y, sx, sy); 668 | }, 669 | update: function () { 670 | this.x -= this.speed; 671 | if (this.x + 250 < 0) { 672 | this.x = CC.w + 250; 673 | this.y = this.y + utils.getRandomInt(-10, 10); 674 | } 675 | 676 | var x = this.x, y = this.y; 677 | sv(); 678 | this.draw(x, y, 0.8, 0.7); 679 | this.draw(x, y, 0.7, 0.6); 680 | this.draw(x, y, 0.6, 0.6); 681 | this.draw(x, y, 0.5, 0.7); 682 | rs(); 683 | } 684 | } 685 | 686 | function SunMoon() { 687 | SM = this; 688 | this.isSun = true; // will act as moon too 689 | this.r = 20; 690 | this.x = 0; 691 | this.y = 100; 692 | this.speed = 1; 693 | G.period = 'morning'; 694 | this.update(); 695 | } 696 | SunMoon.prototype = { 697 | getColor: function () { 698 | var color; 699 | switch (G.period) { 700 | case 'morning': 701 | color = this.isSun ? '#ffff9e' : '#fff'; 702 | break; 703 | case 'afternoon': 704 | color = this.isSun ? 'yellow' : '#fff'; 705 | break; 706 | case 'evening': 707 | color = this.isSun ? '#e28800' : '#fff'; 708 | break; 709 | case 'night': 710 | color = this.isSun ? '#fff' : '#fff'; 711 | break; 712 | } 713 | return color; 714 | }, 715 | resetPos: function () { 716 | this.x = 0; 717 | this.y = 100; 718 | G.period = 'morning'; 719 | thisWeather.step = 0; 720 | }, 721 | update: function () { 722 | // lets assume 30 secs is 1 day, so 15-15 secs day-night 723 | if (Weather.dt / 1000 % (5*diffInWeatherTime) > 5*diffInWeatherTime || 724 | Weather.dt / 1000 % (5*diffInWeatherTime) > 4*diffInWeatherTime || 725 | Weather.dt / 1000 % (5*diffInWeatherTime) > 3*diffInWeatherTime 726 | ) { 727 | G.period = 'night'; 728 | } else if (Weather.dt / 1000 % (5*diffInWeatherTime) > 2*diffInWeatherTime) { 729 | G.period = 'evening'; 730 | } else if (Weather.dt / 1000 % (5*diffInWeatherTime) > 1*diffInWeatherTime) { 731 | G.period = 'afternoon'; 732 | } else { 733 | G.period = 'morning'; 734 | } 735 | 736 | this.x += ((G.can.width / (2 * diffInWeatherTime)) / fps); // this.speed; 737 | if (this.x > G.can.width) { 738 | this.resetPos(); 739 | this.isSun = !this.isSun; 740 | return; 741 | } 742 | 743 | this.y -= 0.1; 744 | sv(); 745 | ctx.shadowColor = this.getColor(); 746 | ctx.shadowOffsetX = -3; 747 | ctx.shadowOffsetY = 3; 748 | ctx.shadowBlur = 10; 749 | bp(); 750 | ar(this.x, this.y, this.r, 0, Math.PI * 2, true); 751 | cp(); 752 | ctx.fillStyle = this.getColor(); 753 | fl(); 754 | rs() 755 | 756 | sv(); 757 | bp(); 758 | ctx.fillStyle = thisWeather.hexToRgb('#444', 0.5); 759 | // moon curvature 760 | if (!this.isSun) { 761 | ar(this.x + 5, this.y - 5, 20, 0, Math.PI * 2, true); 762 | } 763 | cp(); 764 | fl(); 765 | rs(); 766 | 767 | thisWeather.updateGradient(); 768 | } 769 | } 770 | 771 | function WindParticle(i) { 772 | this.x = G.can.width + utils.getRandomInt(0, G.can.width); 773 | this.y = (i+1) * WD.pDist; 774 | this.color = '#d1e5ff'; 775 | this.speed = utils.getRandomInt(1, WD.speed); 776 | } 777 | function Wind() { 778 | WD = this; 779 | this.speed = 1; 780 | this.particlesCount = 15; 781 | this.particles = []; 782 | this.pDist = 10; 783 | this.create(); 784 | } 785 | Wind.prototype = { 786 | create: function () { 787 | for (var i = 0; i < WD.particlesCount; i++) { 788 | WD.particles.push(new WindParticle(i)); 789 | } 790 | }, 791 | update: function () { 792 | for (var i = 0; i < WD.particles.length; i++) { 793 | var wParticle = WD.particles[i]; 794 | // wParticle.y += wParticle.speed; 795 | wParticle.x -= M.max(wParticle.speed); 796 | wParticle.color = thisWeather.getColor(); 797 | fs(wParticle.color); 798 | var wParticleW = utils.getRandomInt(10, 50) 799 | bp(); 800 | mt(wParticle.x, wParticle.y); 801 | lt(wParticle.x - wParticleW, wParticle.y); 802 | cp(); 803 | fl(); 804 | ctx.lineWidth = 2; 805 | sts(wParticle.color); 806 | st(); 807 | 808 | if (wParticle.x < 0) { 809 | // reinitialize a new wParticle 810 | WD.particles[i] = new WindParticle(i); 811 | } 812 | } 813 | } 814 | } 815 | 816 | function Droplet(i) { 817 | this.x = RN.topDropletsDist * i; 818 | this.y = 0; 819 | this.color = '#d1e5ff'; 820 | this.speed = utils.getRandomInt(10, 30); 821 | } 822 | function Rain() { 823 | RN = this; 824 | this.particles = []; 825 | this.particlesCount = 33; 826 | this.topDroplets = this.particlesCount * 1.5; // 66 827 | this.rightDroplets = this.particlesCount / 3; // 33 828 | this.topDropletsDist = (G.can.width / this.topDroplets) * 2; 829 | this.rightDropletsDist = (G.can.width / this.rightDroplets) * 2; 830 | this.create(); 831 | } 832 | Rain.prototype = { 833 | create: function () { 834 | for (var i = 0; i < RN.particlesCount; i++) { 835 | RN.particles.push(new Droplet(i)); 836 | } 837 | }, 838 | update: function () { 839 | for (var i = 0; i < RN.particles.length; i++) { 840 | var droplet = RN.particles[i]; 841 | droplet.y += droplet.speed; 842 | droplet.x -= M.max(G.speed, wind.speed); 843 | droplet.color = thisWeather.getColor(); 844 | fs(droplet.color); 845 | var dropletH = utils.getRandomInt(6, 20) 846 | bp(); 847 | mt(droplet.x, droplet.y); 848 | lt(droplet.x - 2, droplet.y + dropletH); 849 | cp(); 850 | fl(); 851 | ctx.lineWidth = 2; 852 | sts(droplet.color); 853 | st(); 854 | 855 | if (droplet.y > CC.h) { 856 | // reinitialize a new droplet 857 | RN.particles[i] = new Droplet(i); 858 | } 859 | } 860 | } 861 | }; 862 | 863 | function Weather() { 864 | this.colors = [ 865 | [255,255,255], 866 | [142,214,255], 867 | [255, 254, 210], 868 | [153,153,153], 869 | [20,20,20], 870 | [20,20,20] 871 | ]; 872 | 873 | this.step = 0; 874 | this.i = 0; 875 | this.colorIndices = [0, 1 ,2, 3]; 876 | 877 | thisWeather = this; 878 | CC = document.getElementById('canvascontainer').style; 879 | this.init(); 880 | } 881 | 882 | Weather.prototype = { 883 | updateGradient: function () { 884 | var c0_0 = thisWeather.colors[thisWeather.colorIndices[0]], 885 | c0_1 = thisWeather.colors[thisWeather.colorIndices[1]], 886 | c1_0 = thisWeather.colors[thisWeather.colorIndices[2]], 887 | c1_1 = thisWeather.colors[thisWeather.colorIndices[3]], 888 | 889 | istep = 1 - thisWeather.step, 890 | r1 = Math.round(istep * c0_0[0] + thisWeather.step * c0_1[0]), 891 | g1 = Math.round(istep * c0_0[1] + thisWeather.step * c0_1[1]), 892 | b1 = Math.round(istep * c0_0[2] + thisWeather.step * c0_1[2]), 893 | color1 = 'rgb(' + r1 + ',' + g1 + ',' + b1 + ')', 894 | 895 | r2 = Math.round(istep * 255+ thisWeather.step * 255), 896 | g2 = Math.round(istep * 255+ thisWeather.step * 255), 897 | b2 = Math.round(istep * 255+ thisWeather.step * 255), 898 | color2 = 'rgb(' + r2 + ',' + g2 + ',' + b2 + ')'; 899 | 900 | var grd = ctx.createLinearGradient(0, 0, 0, G.can.height); 901 | grd.addColorStop(0, color1); 902 | grd.addColorStop(0.9, color2); 903 | G.backgroundColor = grd; 904 | 905 | thisWeather.step += SM.isSun ? 0.0076 : 0.0076/2.22; // 1 / (diffInWeatherTime * fps); 906 | if (thisWeather.step >= 1) { 907 | thisWeather.step = 0; 908 | for (var j = 0; j < thisWeather.colorIndices.length; j++) { 909 | thisWeather.colorIndices[j] = (thisWeather.i + 1) % thisWeather.colors.length; 910 | } 911 | thisWeather.i += 1; 912 | } 913 | }, 914 | hexToRgb: function (hexColor, alpha) { 915 | if (!hexColor) { return; } 916 | 917 | alpha = alpha || 1.0; 918 | // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") 919 | var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; 920 | hexColor = hexColor.replace(shorthandRegex, function(m, r, g, b) { 921 | return r + r + g + g + b + b; 922 | }); 923 | 924 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hexColor); 925 | return result ? 'rgba(' + 926 | parseInt(result[1], 16) + ',' + 927 | parseInt(result[2], 16) + ',' + 928 | parseInt(result[3], 16) + ',' + 929 | alpha + ')' : 'rgba(255,255,255,0)'; 930 | }, 931 | getColor: function (isKarmaText) { 932 | var color; 933 | switch (G.period) { 934 | case 'morning': 935 | color = isKarmaText ? SM.isSun ? '#444' : '#fff' : SM.isSun ? '#8ED6FF' : '#444'; 936 | break; 937 | case 'afternoon': 938 | color = isKarmaText ? SM.isSun ? '#444' : '#fff' : SM.isSun ? '#56baf3' : '#444'; 939 | break; 940 | case 'evening': 941 | color = isKarmaText ? SM.isSun ? '#444' : '#fff' : SM.isSun ? '#999' : '#444'; 942 | break; 943 | case 'night': 944 | color = isKarmaText ? SM.isSun ? '#444' : '#fff' : SM.isSun ? '#444' : '#444'; 945 | break; 946 | } 947 | return color; 948 | }, 949 | update: function () { 950 | var now = new Date().getTime(); 951 | Weather.dt = now - G.gameStartTime; 952 | 953 | sunMoon.update(); 954 | if (!G.isMobile()) { 955 | cloud.update(); 956 | 957 | // console.log(M.ceil(Weather.dt / 1000)) 958 | if (!this.canRain && M.ceil(Weather.dt / 1000) % 16 === 0) { 959 | this.canRain = true; 960 | this.isRaining = true; 961 | } else if (M.ceil(Weather.dt / 1000) % 33 === 0) { 962 | this.canRain = false; 963 | this.isRaining = false; 964 | } 965 | 966 | if (this.canRain && this.isRaining) { 967 | rain.update(); 968 | } 969 | 970 | wind.update(); 971 | } 972 | }, 973 | init: function () { 974 | cloud = new Cloud(); 975 | sunMoon = new SunMoon(); 976 | rain = new Rain(); 977 | wind = new Wind(); 978 | this.update(); 979 | } 980 | }; 981 | 982 | var PS; 983 | function Particles(x, y) { 984 | PS = this; 985 | this.x = x - 10, 986 | this.y = y; 987 | this.vyL1 = 3; 988 | this.vyL2 = 2; 989 | this.vyL3 = 1; 990 | this.finished = false; 991 | this.particles = []; 992 | this.diff = 0; 993 | this.draw(); 994 | // setTimeout(function () { 995 | // PS.finished = true; 996 | // }, 1000); 997 | } 998 | 999 | Particles.prototype = { 1000 | draw: function () { 1001 | // ctx.globalCompositeOperation = 'source-over'; 1002 | fs('red'); 1003 | for (var i = 0; i < 10; i+= 2) { 1004 | fr(PS.x + 4*i, M.min(PS.y + this.vyL1, CC.h - 50), utils.getRandomInt(4, 6), utils.getRandomInt(4, 6)); 1005 | } 1006 | for (var i = 1; i < 10; i+= 2) { 1007 | fr(PS.x + 4*i, M.min(PS.y + 7 + this.vyL2, CC.h - 50), utils.getRandomInt(4, 6), utils.getRandomInt(4, 6)); 1008 | } 1009 | for (var i = 0; i < 10; i+= 2) { 1010 | fr(PS.x + 4*i, M.min(PS.y + 15 + this.vyL3, CC.h - 50), utils.getRandomInt(4, 6), utils.getRandomInt(4, 6)); 1011 | } 1012 | 1013 | this.diff = CC.h - PS.y; 1014 | PS.y += this.diff * (10 / 15) * (fps / 1000); 1015 | 1016 | if (PS.y > CC.h - P.fireOffset) { 1017 | PS.finished = true; 1018 | } 1019 | PS.x -= G.speed; 1020 | } 1021 | } 1022 | /*var BG; 1023 | function Background() { 1024 | BG = this; 1025 | BG.animate(); 1026 | } 1027 | 1028 | Background.prototype = { 1029 | burnBurnBurn: function() { 1030 | var x, y, bottomLine = BG.canvasWidth * (BG.canvasHeight - 1); 1031 | 1032 | // draw random pixels at the bottom line 1033 | for (x = 0; x < BG.canvasWidth; x++) { 1034 | var value = 0; 1035 | 1036 | if (Math.random() > BG.threshold) 1037 | value = 255; 1038 | 1039 | BG.fire[bottomLine + x] = value; 1040 | } 1041 | 1042 | // move flip upwards, start at bottom 1043 | var value = 0; 1044 | 1045 | for (y = 0; y < BG.canvasHeight; ++y) { 1046 | for (var x = 0; x < BG.canvasWidth; ++x) { 1047 | if (x == 0) { 1048 | value = BG.fire[bottomLine]; 1049 | value += BG.fire[bottomLine]; 1050 | value += BG.fire[bottomLine - BG.canvasWidth]; 1051 | value /= 3; 1052 | } else if (x == BG.canvasWidth -1) { 1053 | value = BG.fire[bottomLine + x]; 1054 | value += BG.fire[bottomLine - BG.canvasWidth + x]; 1055 | value += BG.fire[bottomLine + x - 1]; 1056 | value /= 3; 1057 | } else { 1058 | value = BG.fire[bottomLine + x]; 1059 | value += BG.fire[bottomLine + x + 1]; 1060 | value += BG.fire[bottomLine + x - 1]; 1061 | value += BG.fire[bottomLine - BG.canvasWidth + x]; 1062 | value /= 4; 1063 | } 1064 | 1065 | if (value > 1) 1066 | value -= 1; 1067 | 1068 | value = Math.floor(value); 1069 | var index = bottomLine - BG.canvasWidth + x; 1070 | BG.fire[index] = value; 1071 | } 1072 | 1073 | bottomLine -= BG.canvasWidth; 1074 | } 1075 | 1076 | var skipRows = 1; // skip the bottom 2 rows 1077 | 1078 | // render the flames using our color table 1079 | for (var y = skipRows; y < BG.canvasHeight; ++y) { 1080 | for (var x = 0; x < BG.canvasWidth; ++x) { 1081 | var index = y * BG.canvasWidth * 4 + x * 4; 1082 | var value = BG.fire[(y - skipRows) * BG.canvasWidth + x]; 1083 | 1084 | BG.data[index] = BG.colors[value][0]; 1085 | BG.data[++index] = BG.colors[value][1]; 1086 | BG.data[++index] = BG.colors[value][2]; 1087 | BG.data[++index] = 255; 1088 | } 1089 | } 1090 | 1091 | // sometimes change BG.fire intensity 1092 | if (BG.intensity == null) { 1093 | if (Math.random() > 0.95) { 1094 | BG.randomizeThreshold(); 1095 | } 1096 | } 1097 | 1098 | BG.ctx.putImageData(BG.imageData, 0, BG.CC.height - BG.canvasHeight); 1099 | 1100 | }, 1101 | randomizeThreshold: function() { 1102 | BG.threshold += Math.random() * 0.2 - 0.1; 1103 | BG.threshold = Math.min(Math.max(BG.threshold, 0.5), 0.8); 1104 | }, 1105 | animate: function () { 1106 | BG.intensity = null; 1107 | BG.threshold = 0.5; 1108 | BG.CC = document.querySelector('canvas'); 1109 | BG.ctx = BG.CC.getContext('2d'); 1110 | BG.canvasWidth = BG.CC.width; 1111 | BG.canvasHeight = 50 || P.fireOffset; 1112 | BG.imageData = BG.ctx.getImageData(0, BG.CC.height - BG.canvasHeight, BG.canvasWidth, BG.canvasHeight); 1113 | BG.data = BG.imageData.data; 1114 | //BG.numPixels = BG.data.length / 4; 1115 | BG.colors = []; 1116 | 1117 | for (var i = 0; i < 256; i++) { 1118 | var color = []; 1119 | color[0] = color[1] = color[2] = 75; 1120 | BG.colors[i] = color; 1121 | } 1122 | 1123 | for (var i = 0; i < 32; ++i) { 1124 | BG.colors[i][2] = i << 1; 1125 | BG.colors[i + 32][0] = i << 3; 1126 | BG.colors[i + 32][2] = 64 - (i << 1); 1127 | BG.colors[i + 64][0] = 255; 1128 | BG.colors[i + 64][1] = i << 3; 1129 | BG.colors[i + 96][0] = 255; 1130 | BG.colors[i + 96][1] = 255; 1131 | BG.colors[i + 96][2] = i << 2; 1132 | BG.colors[i + 128][0] = 255; 1133 | BG.colors[i + 128][1] = 255; 1134 | BG.colors[i + 128][2] = 64 + (i << 2); 1135 | BG.colors[i + 160][0] = 255; 1136 | BG.colors[i + 160][1] = 255; 1137 | BG.colors[i + 160][2] = 128 + (i << 2); 1138 | BG.colors[i + 192][0] = 255; 1139 | BG.colors[i + 192][1] = 255; 1140 | BG.colors[i + 192][2] = 192 + i; 1141 | BG.colors[i + 224][0] = 255; 1142 | BG.colors[i + 224][1] = 255; 1143 | BG.colors[i + 224][2] = 224 + i; 1144 | } 1145 | 1146 | BG.fire = []; 1147 | // init BG.fire array 1148 | for (var i = 0; i < BG.canvasWidth * BG.canvasHeight; i++) { 1149 | BG.fire[i] = 75; 1150 | } 1151 | 1152 | BG.burnBurnBurn(); 1153 | 1154 | // intercept key up event to change intensity on BG.fire effect 1155 | document.body.onkeyup = function(event) { 1156 | if (event.keyCode >= 97 && event.keyCode <= 105) { 1157 | BG.intensity = (event.keyCode - 97); 1158 | BG.intensity = BG.intensity / 8; 1159 | BG.intensity = BG.intensity * 0.4; 1160 | BG.intensity = BG.intensity + 0.2; 1161 | BG.threshold = 1 - BG.intensity; 1162 | } else if (event.keyCode == 96) { // 0 ==> randomize 1163 | BG.intensity = 0; 1164 | BG.randomizeThreshold(); 1165 | } 1166 | }; 1167 | 1168 | } 1169 | };*/ 1170 | 1171 | var flameBack = new function() { 1172 | var context; 1173 | var buffer; 1174 | var bufferContext; 1175 | var imageData; 1176 | var palette; 1177 | var colorMap; 1178 | var width; 1179 | var height; 1180 | var scale = 2; 1181 | var fan = 2.5; 1182 | var slack = 5; 1183 | this.time = new Date(); 1184 | 1185 | this.canvas = undefined; 1186 | 1187 | this.init = function() { 1188 | context = this.canvas.getContext('2d'); 1189 | 1190 | width = (this.canvas.width + 30) / scale; 1191 | height = P.fireOffset / scale; 1192 | 1193 | width = Math.ceil(width); 1194 | height = Math.ceil(height); 1195 | 1196 | colorMap = Array(width * height); 1197 | 1198 | for(var i = 0; i < colorMap.length; i++) 1199 | colorMap[i] = 255; 1200 | 1201 | initPalette(); 1202 | initBuffer(); 1203 | 1204 | this.update(); 1205 | }; 1206 | 1207 | // init palette from warm to white hot colors 1208 | var initPalette = function() { 1209 | palette = Array(256); 1210 | 1211 | for(var i = 0; i < 64; i++) { 1212 | palette[i] = [(i << 2), 0, 0]; 1213 | palette[i + 64] = [255, (i << 2), 0]; 1214 | palette[i + 128] = [255, 255, (i << 2)]; 1215 | palette[i + 192] = [255, 255, 255]; 1216 | } 1217 | }; 1218 | 1219 | // offscreen buffer for rendering and scaling 1220 | var initBuffer = function() { 1221 | buffer = document.createElement('canvas'); 1222 | buffer.width = width; 1223 | buffer.height = height; 1224 | buffer.style.visibility = 'hidden'; 1225 | 1226 | bufferContext = buffer.getContext('2d'); 1227 | imageData = bufferContext.createImageData(width, height); 1228 | }; 1229 | 1230 | // main render loop 1231 | this.update = function() { 1232 | if (!G.isMobile()) { 1233 | smooth(); 1234 | draw(); 1235 | fan = utils.getRandomInt(0, 6); 1236 | } else { 1237 | var grd = ctx.createLinearGradient(0, CC.h - P.fireOffset , 0, G.can.height); 1238 | grd.addColorStop(0, 'rgba(255, 0, 0, ' + utils.getRandomInt(8, 10)/10 + ')'); 1239 | grd.addColorStop(0.7, 'rgba(255, 165, 0, ' + utils.getRandomInt(8, 10)/10 + ')'); 1240 | grd.addColorStop(0.9, 'rgba(255, 255, 0, ' + utils.getRandomInt(8, 10)/10 + ')'); 1241 | sv(); 1242 | fs(grd); 1243 | fr(0, CC.h - P.fireOffset, G.can.width, P.fireOffset) 1244 | rs(); 1245 | } 1246 | }; 1247 | 1248 | var smooth = function() { 1249 | for(var x = width - 1; x >= 1; x--) { 1250 | for(var y = height; y--;) { 1251 | var p = (( 1252 | colorMap[toIndex(x - 1, y - 1)] + 1253 | colorMap[toIndex(x, y - 1)] + 1254 | colorMap[toIndex(x + 1, y - 1)] + 1255 | colorMap[toIndex(x - 1, y)] + 1256 | colorMap[toIndex(x + 1, y)] + 1257 | colorMap[toIndex(x - 1, y + 1)] + 1258 | colorMap[toIndex(x, y + 1)] + 1259 | colorMap[toIndex(x + 1, y + 1)]) >> 3); 1260 | 1261 | p = Math.max(0, p - randomValue(fan)); 1262 | 1263 | colorMap[toIndex(x, y - 1)] = p; 1264 | 1265 | if (y < height - slack) { // don't draw random noise in bottom rows 1266 | if (y < height - 2) { 1267 | // set two lines of random palette noise at bottom of 1268 | // colorMap 1269 | colorMap[toIndex(x, height)] = 1270 | randomValue(palette.length); 1271 | colorMap[toIndex(x, height - 1)] = 1272 | randomValue(palette.length); 1273 | } 1274 | 1275 | drawPixel(x, y, palette[colorMap[toIndex(x, y)]]); 1276 | } 1277 | } 1278 | } 1279 | }; 1280 | 1281 | // draw colormap->palette values to screen 1282 | var draw = function() { 1283 | // render the image data to the offscreen buffer... 1284 | bufferContext.putImageData(imageData, 0, 0); 1285 | // ...then draw it to scale to the onscreen canvas 1286 | context.drawImage(buffer, -20, CC.h - (height * scale), width * scale, height * scale + 10); 1287 | }; 1288 | 1289 | // set pixels in imageData 1290 | var drawPixel = function(x, y, color) { 1291 | var offset = (x + y * imageData.width) * 4; 1292 | imageData.data[offset] = color[0]; 1293 | imageData.data[offset + 1] = color[1]; 1294 | imageData.data[offset + 2] = color[2]; 1295 | imageData.data[offset + 3] = 255; 1296 | }; 1297 | 1298 | var randomValue = function(max) { 1299 | // protip: a double bitwise not (~~) is much faster than 1300 | // Math.floor() for truncating floating point values into "ints" 1301 | return ~~(Math.random() * max); 1302 | }; 1303 | 1304 | // because "two-dimensional" arrays in JavaScript suck 1305 | var toIndex = function(x, y) { 1306 | return (y * width + x); 1307 | }; 1308 | 1309 | // draw a bunch of random embers onscreen 1310 | this.drawEmbers = function() { 1311 | for(var x = 1; x < width - 1; x++) { 1312 | for(var y = 1; y < height; y++) { 1313 | if(Math.random() < 0.11) 1314 | colorMap[toIndex(x, y)] = randomValue(palette.length); 1315 | } 1316 | } 1317 | }; 1318 | }; 1319 | 1320 | var Pl; 1321 | function Player() { 1322 | Pl = this; 1323 | Pl.liesOn = 0; 1324 | Pl.maxH = 66; 1325 | Pl.bounceHeight = 5; 1326 | Pl.w = 20; 1327 | Pl.h = Pl.maxH; 1328 | Pl.x = G.trees[0].x; 1329 | Pl.y = G.trees[0].y - Pl.h; 1330 | Pl.vel = 0; 1331 | Pl.isJet = false; 1332 | Pl.isRest = true; 1333 | Pl.t = new Date(); 1334 | Pl.update(); 1335 | return Pl; 1336 | } 1337 | 1338 | Player.prototype = { 1339 | tears: function (x, y) { 1340 | if (Pl.died) return; 1341 | bp(); 1342 | ctx.fillStyle = '#36b1f7'; 1343 | mt(Pl.x + Pl.w + x - 3, Pl.y + y); 1344 | lt(Pl.x + Pl.w + x, Pl.y + y - 4); 1345 | lt(Pl.x + Pl.w + x + 3, Pl.y + y); 1346 | ar(Pl.x + Pl.w + x, Pl.y + y, 3, 0, Math.PI); 1347 | cp(); 1348 | fl(); 1349 | }, 1350 | body: function () { 1351 | sv(); 1352 | ctx.shadowColor = '#000'; 1353 | ctx.shadowOffsetX = -2; 1354 | ctx.shadowOffsetY = 2; 1355 | ctx.shadowBlur = 10; 1356 | if (Pl.died) { 1357 | fr(Pl.x - 2 * (Pl.h / 3), Pl.y, (2 * Pl.h) / 3, Pl.w); 1358 | rs(); 1359 | } else { 1360 | fr(Pl.x, Pl.y, Pl.w, (2 * Pl.h) / 3); 1361 | rs(); 1362 | // shade 1363 | fs('#777') 1364 | fr(Pl.x + 3, Pl.y + 15, 3, 2 * (Pl.h / 3) - 30); 1365 | } 1366 | rs(); 1367 | }, 1368 | eyes: function () { 1369 | if (!Pl.w && !Pl.h) return; 1370 | 1371 | var ct = new Date(), clr; 1372 | // 0 means NO, 1 means starts blinking, 2 means blinked 1373 | Pl.isBlink = ct - Pl.t > 2500 ? ++Pl.isBlink : 0; 1374 | clr = Pl.isBlink > 5 ? '#000' : '#fff'; 1375 | 1376 | if (Pl.isBlink >= 8) { 1377 | Pl.t = ct; Pl.isBlink = 0; 1378 | } 1379 | //fs(clr); 1380 | bp(clr); 1381 | 1382 | if (Pl.died) { 1383 | ar(Pl.x - 2 * (Pl.h / 3) + 6, Pl.y + 4, 2, 0, M.PI * 2, true); 1384 | ar(Pl.x - 2 * (Pl.h / 3) + 6, Pl.y + 10, 2, 0, M.PI * 2, true); 1385 | } else { 1386 | ar(Pl.x + 2 * (Pl.w / 3) - 2, Pl.y + 5, 2, 0, M.PI * 2, true); 1387 | ar(Pl.x + (Pl.w - 3), Pl.y + 5, 2, 0, M.PI * 2, true); 1388 | } 1389 | ctx.fillStyle = clr;//'#8effb6'; 1390 | fl(); 1391 | }, 1392 | legs: function () { 1393 | var lw = 3; // leg width 1394 | fs('#000'); 1395 | if (Pl.died) { 1396 | // left leg 1397 | fr(Pl.x, Pl.y + (Pl.w / 3), Pl.h / 3, lw); 1398 | // right leg 1399 | fr(Pl.x, Pl.y + 2 * (Pl.w / 3), Pl.h / 3, lw); 1400 | } else { 1401 | // left leg 1402 | fr(Pl.x + (Pl.w / 3) - lw / 2, Pl.y + 2 * (Pl.h / 3), lw, Pl.h / 3); 1403 | // right leg 1404 | fr(Pl.x + 2 * (Pl.w / 3) - lw / 2, Pl.y + 2 * (Pl.h / 3), lw, Pl.h / 3); 1405 | } 1406 | }, 1407 | fe: function () { // Fire Extinguisher 1408 | fs('#eaeaea'); 1409 | bp(); 1410 | el(ctx, Pl.x - 30, Pl.y + Pl.h, 80, 10, '#000') 1411 | cp(); 1412 | fs('#EAEAEA'); 1413 | fl(); 1414 | ctx.lineWidth = 1; 1415 | sts('#dedede'); 1416 | st(); 1417 | fs('#8ED6FF'); 1418 | for (var i = -30; i < Pl.w + 30; i+=6) { 1419 | bp(); 1420 | mt(Pl.x + i, Pl.y + Pl.h); 1421 | lt(Pl.x + i - 1, Pl.y + Pl.h + utils.getRandomInt(10, G.can.height/2)); 1422 | lt(Pl.x + i + 3, Pl.y + Pl.h + utils.getRandomInt(10, G.can.height/2)); 1423 | lt(Pl.x + i + 1, Pl.y + Pl.h); 1424 | cp(); 1425 | fl(); 1426 | } 1427 | 1428 | }, 1429 | burst: function () { 1430 | // Particle effects 1431 | if (Pl.w && Pl.h) { 1432 | this.dieParticles = new Particles(Pl.x, Pl.y); 1433 | } else if (PS.finished) { 1434 | G.stopCycle(); 1435 | PS.finished = false; 1436 | } 1437 | 1438 | if (!Pl.w && !Pl.h) { 1439 | this.dieParticles.draw(); 1440 | } 1441 | 1442 | Pl.w = 0; 1443 | Pl.h = 0; 1444 | }, 1445 | update: function () { 1446 | Pl.x -= G.speed; 1447 | if (Pl.isInAir && Pl.bounceFactor > 0) { 1448 | Pl.y -= Pl.bounceHeight; 1449 | Pl.x += 1.5 * G.speed; 1450 | Pl.bounceFactor -= 2; 1451 | } else if (Pl.isInAir && !Pl.bounceFactor) { // smooth curve parabola jump 1452 | Pl.y -= Pl.bounceHeight / 2; 1453 | Pl.x += 1.5 * G.speed; 1454 | Pl.bounceFactor -= 2; 1455 | } else if (Pl.isInAir && Pl.bounceFactor === -2) { // smooth curve parabola jump 1456 | Pl.y -= 0; 1457 | Pl.x += 1.5 * G.speed; 1458 | Pl.bounceFactor -= 2; 1459 | } else if (Pl.isInAir && Pl.bounceFactor === -4) { // smooth curve parabola jump 1460 | Pl.y -= Pl.bounceHeight / 2; 1461 | Pl.x += 1.5 * G.speed; 1462 | Pl.bounceFactor -= 2; 1463 | } else if (Pl.isInAir && Pl.bounceFactor === -6) { // revert back force rewind 1464 | Pl.y += Pl.bounceHeight; 1465 | Pl.x += 1.5 * G.speed; 1466 | Pl.bounceFactor = -6; 1467 | } 1468 | 1469 | if (Pl.died && !Pl.busted) { 1470 | Pl.y += 10; 1471 | if (Pl.isCornerStrike) { 1472 | if (Pl.y > CC.h - 3*P.fireOffset) { 1473 | if (!Pl.busted) { 1474 | Pl.busted = true; 1475 | } 1476 | } 1477 | } else if (!Pl.busted) { 1478 | Pl.busted = true; 1479 | } 1480 | } 1481 | 1482 | fs('#000'); 1483 | Pl.body(); 1484 | Pl.legs(); 1485 | Pl.eyes(); 1486 | Pl.tears(2, 6); 1487 | Pl.tears(5, 15); 1488 | if (Pl.isInAir && !Pl.busted) Pl.fe(); 1489 | if (Pl.busted) { 1490 | Pl.burst(); 1491 | } 1492 | Pl.checkCollision(); 1493 | }, 1494 | keyDown: function (key) { 1495 | if (Pl.busted) { return; } 1496 | 1497 | if (key === 32) { // 32 is space,38 is UP, 40 is DOWN 1498 | Pl.irj = true; // isReadyToJump 1499 | if (Pl.h < 50) { return; } 1500 | Pl.h -= 2; 1501 | Pl.y += 2; 1502 | } else if (key === 39) { 1503 | Pl.x += G.speed; 1504 | } 1505 | 1506 | if (Pl.irj) { 1507 | Pl.irj = false; 1508 | Pl.isInAir = true; 1509 | Pl.isKarmaLocked = false; 1510 | Pl.bounceFactor = Pl.maxH - Pl.h; 1511 | Pl.bounceFactor *= 4; 1512 | Pl.h = Pl.maxH; 1513 | } 1514 | }, 1515 | keyUp: function () { 1516 | /*if (Pl.irj) { 1517 | Pl.irj = false; 1518 | Pl.isInAir = true; 1519 | SU.play('moveAhead'); 1520 | Pl.bounceFactor = Pl.maxH - Pl.h; 1521 | Pl.bounceFactor *= 4; 1522 | Pl.h = Pl.maxH; 1523 | }*/ 1524 | }, 1525 | checkCollision: function () { 1526 | if (Pl.x <= 0) { // leftmost collision 1527 | Pl.died = true; 1528 | Pl.isCornerStrike = true; 1529 | return; 1530 | } else if (Pl.y > CC.h - P.fireOffset) { // bottom fire collision 1531 | Pl.died = true; 1532 | Pl.isCornerStrike = true; 1533 | return; 1534 | } else if (Pl.x + Pl.w > CC.w) { // rightmost collision 1535 | Pl.died = true; 1536 | Pl.isCornerStrike = true; 1537 | return; 1538 | } else if (Pl.y < 0 ) { // topmost collision 1539 | Pl.died = true; 1540 | Pl.isCornerStrike = true; 1541 | return; 1542 | } 1543 | 1544 | var i, tree; 1545 | for (i = 0; i < G.trees.length; i++) { // M.min(Pl.liesOn + 10, ) 1546 | tree = G.trees[i]; 1547 | 1548 | var playerEnd = Pl.x + Pl.w - 4; // 4 bcz legs are placed (x coord)technically before body 1549 | if ((playerEnd >= tree.x && playerEnd < (tree.x + tree.width + 4) && Pl.y + Pl.w + Pl.h >= tree.x) 1550 | ) { 1551 | for (var j = 0; j < Pl.bounceHeight; j++) { 1552 | if (Pl.y + Pl.h + j >= tree.y) { 1553 | G.trees[i].flame = null; 1554 | Pl.isInAir = false; 1555 | Pl.liesOn = i; 1556 | if (!Pl.isKarmaLocked && Pl.liesOn !== Pl.lastLiesOn) { 1557 | G.karma && SU.play('moveAhead'); 1558 | G.karma += 1; 1559 | } 1560 | Pl.isKarmaLocked = true;; 1561 | Pl.lastLiesOn = Pl.liesOn; 1562 | // Pl.y += tree.y - (Pl.y + Pl.h) - 2; 1563 | break; 1564 | } 1565 | } 1566 | if (Pl.y >= tree.y && Pl.y < tree.y + tree.height) { 1567 | Pl.died = true; 1568 | break; 1569 | } 1570 | 1571 | } 1572 | } 1573 | } 1574 | }; 1575 | var T, CC; 1576 | var blw = 200, bw = 0; 1577 | 1578 | function Tree(config) { 1579 | T = this; 1580 | config = config || {}; 1581 | // T.lw = T.w = 0; 1582 | T.minW = 10; 1583 | T.maxW = 80; 1584 | T.minH = P.fireOffset; 1585 | T.maxH = G.isMobile() ? 300 : 400; 1586 | T.minDist = 50; 1587 | T.maxDist = G.isMobile() ? 100 : 200; 1588 | // T.branchThickness = 3; 1589 | 1590 | CC.w = utils.pI(G.can.width); 1591 | CC.h = utils.pI(G.can.height); 1592 | 1593 | T.color = '#a77b44'; 1594 | this.add(); 1595 | if (!config.isNoFlame) { 1596 | if (G.isMobile()) { 1597 | this.flame = true; 1598 | } else { 1599 | this.flame = smoky; 1600 | this.flame.addEntity(Flame); 1601 | } 1602 | } 1603 | return T; 1604 | } 1605 | 1606 | Tree.prototype = { 1607 | /*drawFractalTree: function (x, y, width, height) { 1608 | T.drawTree(x, y, width, height, -90, T.branchThickness); 1609 | }, 1610 | drawTree: function (x1, y1, width, height, angle, depth){ 1611 | T.brLength = T.brLength || T.random(T.minW, T.maxW); 1612 | T.angle = T.angle || T.random(15, 20); 1613 | T.bb = (T.cos(angle) * depth * T.brLength); 1614 | T.vv = (T.sin(angle) * depth * T.brLength); 1615 | if (depth != 0){ 1616 | var x2 = x1 + T.bb; 1617 | var y2 = y1 - T.vv; 1618 | 1619 | T.drawLine(x1, y1, x2, y2, depth); 1620 | 1621 | T.drawTree(x2, y2, width, height, angle - T.angle, depth - 1); 1622 | T.drawTree(x2, y2, width, height, angle + T.angle, depth - 1); 1623 | // T.drawLine(x1, y1, x2, y2, depth); 1624 | } 1625 | }, 1626 | random: function (min, max){ 1627 | return min + Math.floor(Math.random()*(max+1-min)); 1628 | }, 1629 | drawLine: function (x1, y1, x2, y2, thickness){ 1630 | ctx.fillStyle = '#000'; 1631 | if(thickness > 2) 1632 | ctx.strokeStyle = 'rgb(139,126, 102)'; //Brown 1633 | else 1634 | ctx.strokeStyle = 'rgb(34,139,34)'; //Green 1635 | ctx.lineWidth = thickness * 1.5; 1636 | bp(); 1637 | mt(x1, y1); 1638 | lt(x2, y2); 1639 | cp(); 1640 | st(); 1641 | 1642 | }, 1643 | cos: function (angle) { 1644 | return M.cos(T.deg_to_rad(angle)); 1645 | }, 1646 | sin: function (angle) { 1647 | return M.sin(T.deg_to_rad(angle)); 1648 | }, 1649 | deg_to_rad: function (angle){ 1650 | return angle*(M.PI/180.0); 1651 | },*/ 1652 | getWidth: function (val) { 1653 | if (val !== undefined) { 1654 | return val; 1655 | } 1656 | return utils.getRandomInt(T.minW, T.maxW); 1657 | }, 1658 | getHeight: function (val) { 1659 | if (val !== undefined) { 1660 | return val; 1661 | } 1662 | return utils.getRandomInt(T.minH, T.maxH); 1663 | }, 1664 | add: function (val) { 1665 | T.preCompute(); 1666 | T.x = blw + bw; 1667 | T.y = CC.h - T.h - (P.fireOffset * 0.6), 1668 | T.width = bw, 1669 | T.height = T.h; 1670 | // T.drawFractalTree(T.x, T.y, T.width, T.height) 1671 | 1672 | //T.update(T); 1673 | return T; 1674 | }, 1675 | update: function (treeInstance) { 1676 | var x = treeInstance.x, 1677 | y = treeInstance.y, 1678 | width = treeInstance.width, 1679 | height = treeInstance.height; 1680 | 1681 | sv(); 1682 | fs(T.color); 1683 | 1684 | bp(); 1685 | mt(x, y); 1686 | // left side 1687 | bct(x , y + height, x - 25, y + height, x - 25, y + height); 1688 | 1689 | // left bottom curve 1690 | bct(x, y + height, x + (width / 2), y + height / 1.2, x + (width / 2), y + (height / 1.2)) 1691 | // right bottom curve 1692 | bct(x + (width / 2), y + (height / 1.2), x + (width / 2), y + height / 1.2, x + width + 25, y + height); 1693 | 1694 | // right side 1695 | bct(x + width, y + height, x + width, y, x + width, y); 1696 | 1697 | ctx.shadowColor = '#6b4e2a'; 1698 | ctx.shadowOffsetX = -3; 1699 | ctx.shadowOffsetY = 3; 1700 | ctx.shadowBlur = 10; 1701 | ctx.strokeStyle = '#6b4e2a'; 1702 | ctx.lineWidth = 1; 1703 | st(); 1704 | cp(); 1705 | fl(); 1706 | rs(); 1707 | 1708 | fs('#444') 1709 | el(ctx, x, y - 4, width, 10, '#6b4e2a'); 1710 | 1711 | if (treeInstance.flame) { 1712 | if (G.isMobile()) { 1713 | T.addCircle(x, y, width); 1714 | } else { 1715 | treeInstance.flame.update(x, y, width); 1716 | } 1717 | } 1718 | }, 1719 | addCircle: function (x, y, width) { 1720 | bp(); 1721 | ar(x + (width/2), y, width/2, 0, Math.PI*2, false); 1722 | fs('rgba(255, 0, 0, 0.4)'); 1723 | fl(); 1724 | 1725 | bp(); 1726 | ar(x + (width/2), y, width/3, 0, Math.PI*2, false); 1727 | fs('rgba(255, 165, 0, 0.4)'); 1728 | fl(); 1729 | 1730 | bp(); 1731 | ar(x + (width/2), y, width/6, 0, Math.PI*2, false); 1732 | fs('rgba(255, 255, 0, ' + utils.getRandomInt(0.3, 0.5)/10 + ')'); 1733 | fl(); 1734 | }, 1735 | preCompute: function () { 1736 | T.lw = blw + bw + (bw === 0 ? 0 : utils.getRandomInt(T.minDist, T.maxDist)); 1737 | blw = T.lw; 1738 | T.w = utils.getRandomInt(T.minW, T.maxW); 1739 | bw = T.w; 1740 | T.h = utils.getRandomInt(T.minH, T.maxH); 1741 | // console.log(blw, bw) 1742 | // T.rw = CC.w - T.lw - T.w; 1743 | }, 1744 | removeFlame: function (that) { 1745 | that.flame = undefined; 1746 | } 1747 | }; 1748 | 1749 | 1750 | var G, ctx, CC, background, player, weather, smoky; 1751 | function Game() { 1752 | G = this; 1753 | G.isInProgress = true; 1754 | G.canSpeedBeIncreased = G.canExplode = true; 1755 | G.backgroundColor = '#fff'; 1756 | 1757 | G.karma = 0; 1758 | 1759 | G.highscore = utils.getLocalStorageData() || 0; 1760 | G.isSound = utils.getLocalStorageData(true); 1761 | if (G.isSound !== 0) { 1762 | G.isSound = 1; 1763 | } 1764 | 1765 | G.resolution = 1; 1766 | G.curPos = []; 1767 | 1768 | G.can = document.querySelector('canvas'); 1769 | G.can.width = P.w; 1770 | G.can.height = P.h; 1771 | 1772 | ctx = G.ctx = window.c = G.can.getContext('2d'); 1773 | 1774 | G.trees = []; 1775 | 1776 | // Resizing 1777 | G.resize(); 1778 | addEventListener('resize', G.resize, false); 1779 | 1780 | CC = document.getElementById('canvascontainer').style; 1781 | 1782 | document.body.addEventListener('touchstart', G.touchStart.bind(G), false); 1783 | document.body.addEventListener('touchmove', G.touchMove.bind(G), false); 1784 | document.body.addEventListener('touchend', G.touchEnd.bind(G), false); 1785 | document.body.addEventListener('mousedown', G.mouseDown.bind(G), false); 1786 | document.body.addEventListener('mousemove', G.mouseMove.bind(G), false); 1787 | document.body.addEventListener('mouseup', G.mouseUp.bind(G), false); 1788 | 1789 | document.body.addEventListener('keydown', G.keyDown.bind(G), false); 1790 | document.body.addEventListener('keyup', G.keyUp.bind(G), false); 1791 | 1792 | // Loop 1793 | G.frameCount = 0; 1794 | G.lastFrame = G.frameCountStart = Date.now(); 1795 | 1796 | var displayablePixels = _.innerWidth * _.innerHeight * _.devicePixelRatio, 1797 | gamePixels = P.w * P.h, 1798 | ratio = displayablePixels / gamePixels; 1799 | 1800 | if (ratio < 0.5){ 1801 | G.setResolution(ratio * 2); 1802 | } 1803 | 1804 | G.speed = 1; 1805 | 1806 | // background animation 1807 | // background = new Background(); 1808 | flameBack.canvas = G.can; 1809 | //flameBack.init(); 1810 | 1811 | G.menu = true; 1812 | } 1813 | 1814 | var tree, time; 1815 | Game.prototype = { 1816 | restart: function () { 1817 | G.isGameOver = false; 1818 | G.isInProgress = true; 1819 | G.karma = 0; 1820 | G.speed = 1; 1821 | G.gameStartTime = new Date().getTime(); 1822 | 1823 | smoky = new SmokyFlame(); 1824 | 1825 | blw = 200, bw =0; 1826 | G.addInitialtrees(); 1827 | 1828 | player = new Player(); 1829 | Pl.x = G.trees[0].x; 1830 | 1831 | flameBack.init(); 1832 | weather = new Weather(); 1833 | G.raf = raf(function(){ 1834 | if (G.raf) { 1835 | G.cycle(); 1836 | raf(arguments.callee); 1837 | } 1838 | }); 1839 | }, 1840 | stopCycle: function () { 1841 | G.isGameOver = true; 1842 | G.isInProgress = false; 1843 | 1844 | flameBack.update(); 1845 | canvasToImage(); // get image before spash screen 1846 | 1847 | // console.log('Boom! DIE!'); 1848 | // update high score 1849 | if (G.karma > G.highscore) { 1850 | SU.play('highestScore'); 1851 | G.highscore = G.karma; 1852 | utils.setLocalStorageData(G.karma); 1853 | } 1854 | 1855 | SU.play('gameOver'); 1856 | 1857 | G.menu = new Menu(); 1858 | }, 1859 | cycle: function () { 1860 | var now = new Date().getTime(); 1861 | dt = now - time; 1862 | 1863 | if (dt < (1000 / fps)) 1864 | return; // skip a frame 1865 | 1866 | //SU.play('game'); 1867 | time = now; 1868 | if (G.menu) { 1869 | G.menu.update && G.menu.update(); 1870 | return; 1871 | } 1872 | 1873 | if (G.canExplode && M.ceil((now - G.gameStartTime) / 1000) % 6 === 0) { 1874 | G.mildExplosion ? SU.play('explosion2') : SU.play('explosion1'); 1875 | G.mildExplosion = !G.mildExplosion; 1876 | G.canExplode = false; 1877 | } else if (M.ceil((now - G.gameStartTime) / 1000) % 7 === 0) { 1878 | G.canExplode = true; 1879 | } 1880 | 1881 | if (G.canSpeedBeIncreased && M.ceil((now - G.gameStartTime) / 1000) % 10 === 0) { 1882 | G.speed += G.isMobile() ? 0.1 : 0.2; 1883 | WD.speed = utils.getRandomInt(1, 30); 1884 | G.canSpeedBeIncreased = false; 1885 | } else if (M.ceil((now - G.gameStartTime) / 1000) % 11 === 0) { 1886 | // G.speed += 0.1; 1887 | G.canSpeedBeIncreased = true; 1888 | } 1889 | 1890 | fs(G.backgroundColor); 1891 | fr(0, 0, CC.w, CC.h); 1892 | 1893 | var speedIncFactor = G.isMobile() ? 1.1 : 1.6; 1894 | if (G.speed >= speedIncFactor && 1895 | utils.getRandomInt(0, 10) === 10 1896 | ) { 1897 | G.showNoisyScreen(); 1898 | utils.getRandomInt(0, 10) === 4 && SU.play('glitch'); 1899 | } 1900 | 1901 | //background.burnBurnBurn(); 1902 | weather.update(); 1903 | 1904 | ctx.font = '15px Comic Sans'; 1905 | ctx.fillStyle = thisWeather.hexToRgb(thisWeather.getColor(true), 1.0); 1906 | ctx.fillText('KARMA: ' + G.karma, 25, 25); 1907 | ctx.fillText('SPEED: ' + G.speed.toFixed(1) + ' mph', G.can.width - 130, 25); 1908 | ctx.fillText('WIND: ' + WD.speed.toFixed(1) + ' mph W', G.can.width - 130, 45); 1909 | ctx.lineWidth = 3; 1910 | 1911 | if (G.trees.length) { 1912 | for (var i = 0; i < G.trees.length; i++) { 1913 | G.trees[i].x -= G.speed; 1914 | G.trees[i].update(G.trees[i]); 1915 | 1916 | if (G.trees[i].x < 0 - G.trees[i].width) { 1917 | G.trees[i] = new Tree(); 1918 | } 1919 | } 1920 | player.update(); 1921 | } 1922 | flameBack.update(); 1923 | }, 1924 | showNoisyScreen: function () { 1925 | var w = G.can.width, 1926 | h = G.can.height, 1927 | idata = ctx.createImageData(w, h), 1928 | buffer32 = new Uint32Array(idata.data.buffer), 1929 | len = buffer32.length, 1930 | i = 0; 1931 | 1932 | for (; i < len;) { 1933 | buffer32[i++] = ((255 * Math.random())|0) << 24; 1934 | } 1935 | 1936 | ctx.putImageData(idata, 0, 0); 1937 | }, 1938 | addInitialtrees: function () { 1939 | G.trees = []; 1940 | G.trees.push(new Tree({isNoFlame: true})) 1941 | for (var i = 0; i < 5; i++) { 1942 | G.trees.push(new Tree()) 1943 | } 1944 | }, 1945 | resize: function() { 1946 | setTimeout(function(){ 1947 | var maxWidth = innerWidth, 1948 | maxHeight = innerHeight, 1949 | 1950 | availableRatio = maxWidth / maxHeight, 1951 | baseRatio = P.w / P.h, 1952 | ratioDifference = abs(availableRatio - baseRatio), 1953 | width, 1954 | height, 1955 | s = document.getElementById('canvascontainer').style; 1956 | 1957 | if (availableRatio <= baseRatio){ 1958 | width = maxWidth; 1959 | height = maxHeight;//width / baseRatio; 1960 | } else{ 1961 | height = maxHeight; 1962 | width = height * baseRatio; 1963 | } 1964 | 1965 | s.width = width + 'px'; 1966 | s.height = height + 'px'; 1967 | 1968 | ctx.globalCompositeOperation="lighter"; 1969 | 1970 | G.can.width = width; 1971 | G.can.height = height; 1972 | 1973 | 1974 | if (G.menu) { 1975 | G.menu = new Menu(); 1976 | G.raf = raf(function(){ 1977 | if (G.raf) { 1978 | G.cycle(); 1979 | raf(arguments.callee); 1980 | } 1981 | }); 1982 | return; 1983 | } 1984 | 1985 | G.restart(); 1986 | 1987 | },100); 1988 | }, 1989 | isMobile: function () { 1990 | if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i 1991 | .test(navigator.userAgent)) { 1992 | return true; 1993 | } 1994 | return false; 1995 | }, 1996 | pos : function(e){ 1997 | var rect = G.can.getBoundingClientRect(), 1998 | pos = []; 1999 | 2000 | e = e.touches || [e]; 2001 | 2002 | for(var i = 0 ; i < e.length ; i++){ 2003 | pos.push({ 2004 | x : (e[i].clientX),// - rect.left) / (rect.width / P.w), 2005 | y : (e[i].clientY)// - rect.top) / (rect.height / P.h) 2006 | }) 2007 | } 2008 | 2009 | return pos; 2010 | }, 2011 | touchStart : function(e,m) { 2012 | e.preventDefault(); 2013 | G.touch = G.touch || !m; 2014 | var p = G.pos(e); 2015 | G.curPos = p; 2016 | 2017 | scrollTo(0, 1); 2018 | 2019 | if (G.menu) { 2020 | var x = G.curPos[0].x - G.can.offsetLeft, 2021 | y = G.curPos[0].y - G.can.offsetTop; 2022 | 2023 | G.menu.mouseDown && G.menu.mouseDown(e, x, y); 2024 | } else { 2025 | // G.isTouching = true; 2026 | G.keyDown({keyCode: 32}); 2027 | } 2028 | 2029 | if(!G.isInProgress) return; 2030 | 2031 | //G.world.touchStart(); 2032 | }, 2033 | touchMove : function(e) { 2034 | e.preventDefault(); 2035 | if (G.curPos){ 2036 | G.curPos = G.pos(e); 2037 | 2038 | if(!G.isInProgress) return; 2039 | //G.world.touchMove(); 2040 | } 2041 | }, 2042 | touchEnd : function(e) { 2043 | e.preventDefault(); 2044 | 2045 | var p = G.curPos[0]; 2046 | G.curPos = G.pos(e); 2047 | 2048 | if (!G.isInProgress) { 2049 | //!G.isInProgress.click(p.x, p.y); 2050 | } else { 2051 | //G.world.touchEnd(); 2052 | } 2053 | }, 2054 | keyDown: function(e) { 2055 | // 13 is enter 2056 | if ((e.keyCode === 13 || e.keyCode === 32) && G.menu) { 2057 | G.menu = null; 2058 | G.restart(); 2059 | SU.play('playGame'); 2060 | return; 2061 | } 2062 | if (!G.isInProgress) { 2063 | return; 2064 | } 2065 | 2066 | // 39 is right, 40 is down, 38 is up 2067 | if (e.keyCode === 39 || e.keyCode === 38 || e.keyCode === 32) { 2068 | player && player.keyDown(e.keyCode); 2069 | } 2070 | }, 2071 | keyUp: function(e) { 2072 | if(!G.isInProgress) return; 2073 | player && player.keyUp(e.keyCode); 2074 | }, 2075 | mouseDown: function(e) { 2076 | /*if(!G.touch){ 2077 | G.touchStart(e, true); 2078 | }*/ 2079 | if (G.menu) { 2080 | var x = e.pageX - G.can.offsetLeft, 2081 | y = e.pageY - G.can.offsetTop; 2082 | 2083 | G.menu.mouseDown && G.menu.mouseDown(e, x, y); 2084 | } 2085 | }, 2086 | mouseMove: function(e) { 2087 | /*if(!G.touch){ 2088 | G.touchMove(e); 2089 | }*/ 2090 | }, 2091 | mouseUp: function(e) { 2092 | /*if(!G.touch){ 2093 | G.touchEnd(e); 2094 | }*/ 2095 | }, 2096 | setResolution: function(r) { 2097 | G.can.width = P.w * r; 2098 | G.can.height = P.h * r; 2099 | 2100 | G.resolution = r; 2101 | } 2102 | } 2103 | var _ = window, 2104 | raf = (function() { 2105 | return _.requestAnimationFrame || 2106 | _.webkitRequestAnimationFrame || 2107 | _.mozRequestAnimationFrame || 2108 | 2109 | function(c){ 2110 | setTimeout(c, 1000 / 60); 2111 | }; 2112 | })(), 2113 | M = Math, 2114 | abs = M.abs, 2115 | min = M.min, 2116 | max = M.max, 2117 | to = setTimeout, 2118 | fps = 60; 2119 | 2120 | // Shortcuts 2121 | var p = CanvasRenderingContext2D.prototype; 2122 | p.fr = p.fillRect; 2123 | p.sv = p.save; 2124 | p.rs = p.restore; 2125 | p.lt = p.lineTo; 2126 | p.mt = p.moveTo; 2127 | p.sc = p.scale; 2128 | p.bp = p.beginPath; 2129 | p.cp = p.closePath; 2130 | p.rt = p.rotate; 2131 | p.ft = p.fillText; 2132 | p.bct = p.bezierCurveTo; 2133 | p.qct = p.quadraticCurveTo; 2134 | p.st = p.stroke; 2135 | p.ar = p.arc; 2136 | p.fl = p.fill; 2137 | 2138 | // ctx.ellipsis wont work in firefox 2139 | p.el = function drawEllipseWithQuatraticCurve(ctx, x, y, w, h, style) { 2140 | var kappa = .5522848, 2141 | ox = (w / 2) * kappa, // control point offset horizontal 2142 | oy = (h / 2) * kappa, // control point offset vertical 2143 | xe = x + w, // x-end 2144 | ye = y + h, // y-end 2145 | xm = x + w / 2, // x-middle 2146 | ym = y + h / 2; // y-middle 2147 | 2148 | sv(); 2149 | bp(); 2150 | mt(x, ym); 2151 | qct(x,y,xm,y); 2152 | qct(xe,y,xe,ym); 2153 | qct(xe,ye,xm,ye); 2154 | qct(x,ye,x,ym); 2155 | ctx.strokeStyle = style ? style : '#000'; 2156 | ctx.lineWidth = 2; 2157 | st(); 2158 | rs(); 2159 | fl(); 2160 | } 2161 | 2162 | p.fs = function(p){ 2163 | this.fillStyle = P.inverted ? invert(p) : p; 2164 | }; 2165 | p.sts = function(p){ 2166 | this.strokeStyle = P.inverted ? invert(p) : p; 2167 | }; 2168 | 2169 | // Adding all these functions to the global scope 2170 | for(var i in p){ 2171 | _[i] = (function(f){ 2172 | return function(){ 2173 | c[f].apply(c, arguments); 2174 | } 2175 | })(i); 2176 | } 2177 | 2178 | var P = { 2179 | w: 640, 2180 | h: 760, 2181 | g: 800, 2182 | fireOffset: 70, 2183 | spikesOffset: 50, 2184 | tbOffset: 20 2185 | }; 2186 | 2187 | function canvasToImage() { 2188 | G.dataURL = document.getElementById('game-canvas').toDataURL('image/png'); 2189 | } 2190 | 2191 | function downloadCanvas() { 2192 | var windowRef = _.open(); 2193 | if (windowRef) { 2194 | windowRef.document.write(''); 2195 | } else { 2196 | alert('Your browser prevented the window from opening. Please allow to view game screenshot.') 2197 | } 2198 | } 2199 | 2200 | addEventListener('DOMContentLoaded',function(){ 2201 | _._can = document.querySelector('canvas'); 2202 | new Game(); 2203 | }); 2204 | 2205 | --------------------------------------------------------------------------------