diff --git a/.gitignore b/.gitignore index 2f41c87..10c4a25 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,7 @@ docs/_site/* docs/.sass-cache/* docs/.jekyll-cache/* -docs/node_modules \ No newline at end of file +docs/node_modules + +# idea +.idea \ No newline at end of file diff --git a/docs/assets/js/UI/ui.js b/docs/assets/js/UI/ui.js index a2dfd21..4f05905 100644 --- a/docs/assets/js/UI/ui.js +++ b/docs/assets/js/UI/ui.js @@ -1,57 +1,130 @@ // UI for title screen -function drawTitleScreenUI() {}; +function drawTitleScreenUI() { +} // UI for level transition -function drawLevelTransitionUI() {}; +function drawLevelTransitionUI() { +} // UI for playing -function drawPlayingUI() {}; +function drawPlayingUI() { + + // cartesianRect(0,ch/3*2, cw, ch/3, "#333333") + + //Heart Rate Monitor + heartBeatUI(cw/4*3-8,ch/8*7-8,cw/4,ch/8); + + //Respiration Monitor + respiratoryUI(cw/8*5,ch/8*7-8, cw/16, ch/8); +} //UI for pause screen -function drawPausedUI() {}; +function drawPausedUI() { +} //UI for game end -function drawEndUI() {}; +function drawEndUI() { +} -// Construct a rectangular UI -function rectUI() {}; + +/*** + * + * RESPIRATORY UI + * + */ + +function respiratoryUI(x, y, width, height){ + cartesianRect(x,y,width,height, "rgb("+noBreathTimer/180*255+","+0+","+0+")"); + cartesianRect(x,y+(height-breath/constants.lifeFuncs.breath.fullBreath*height), width, breath/constants.lifeFuncs.breath.fullBreath*height, "rgb("+255+","+(255-fullBreathTimer/180*255)+","+(255-fullBreathTimer/180*255)+")"); +} + +/*** + * + * HEART RATE MONITOR UI + * + */ //Heart rate monitor history -var heartBeatHistory = [].fill(0,0, constants.ui.heartRate.history_length); +let heartBeatHistory = []; + heartBeatHistory.length = constants.ui.heartRate.history_length; + heartBeatHistory.fill(0); //Shift accumulation -var shiftAccum = 0; +let shiftAccum = 0; //Beat progression -var beatTimeElapsed = 0; +let beatTimeElapsed = Infinity; // Draw heartbeat UI function heartBeatUI(x, y, width, height){ + //Shift monitor over once a full scrolling unit is accumulated shiftAccum += constants.ui.heartRate.scroll_speed; if(shiftAccum>=1){ shiftAccum%=1; + beatTimeElapsed += 0.04; + + //Remove oldest value heartBeatHistory.shift(); + + //Append new value pushNextBeatValue(); } - if(timeSinceLastBeat===0){ + //If heart is beaten, reset beat timer. + if(heartBeat){ beatTimeElapsed = 0; + heartBeat = false; } + //Backdrop + rect(x+width/2,y+height/2,width,height,"black"); + + //Pressure Meter + rect(x+width-8,y+height/2,16,height,"red"); + rect(x+width-8,y+height/2,16,height/2,"yellow"); + rect(x+width-8,y+height/2,16,height/6,"green"); + let pressureHeight = Math.max(Math.min(y+height-(pressure/constants.lifeFuncs.cardio.optimalPressure*height/2),y+height),y); + line(x+width-16, pressureHeight,x+width,pressureHeight, 2,"black") + + //Graph for (let index = 0; index < heartBeatHistory.length; index++) { const qrsValueAtPosition = heartBeatHistory[index]; - line(x+index, y+(2*height/3), x+index, y+(2*height/3)+qrsValueAtPosition); + const qrsValueAtNextPosition = heartBeatHistory[index+1]; + line(x+(index*(width-16)/heartBeatHistory.length), y+(2*height/3)+(qrsValueAtPosition*(width-16)/heartBeatHistory.length), x+((index+1)*(width-16)/heartBeatHistory.length), y+(2*height/3)+(qrsValueAtNextPosition*(width-16)/heartBeatHistory.length),Math.min(3,Math.max(3/beatTimeElapsed,1)), "red"); } } +//Determine next value to be added to the graph function pushNextBeatValue(){ - var nextBeatValue; + let nextBeatValue = 0; - beatTimeElapsed %= constants.ui.heartRate.complex_width; - if(beatTimeElapsed<=constants.ui.heartRate.pr_width){ - nextBeatValue = -0.25((x - 1.5)**2) + 0.5625; - } else if (beatTimeElapsed >= constants.ui.heartRate.pr_width + 1 && beatTimeElapsed <= constants.ui.heartRate.pr_width + 1 + constants.ui.heartRate.qrs_width/4) { + //Timespan of one "square" on the EKG + const squareSize = constants.ui.heartRate.square_size; + //Length of full complex + const complexTime = constants.ui.heartRate.complex_width*squareSize; + //Length of PR segment of complex + const prTime = constants.ui.heartRate.pr_width*squareSize; + //Length of QRS component of complex + const qrsTime = constants.ui.heartRate.qrs_width*squareSize; + //Length of QT component of complex + const qtTime = constants.ui.heartRate.qt_width*squareSize; + + if(beatTimeElapsed<=complexTime) { + //PR Segment + if (beatTimeElapsed <= prTime) { + nextBeatValue = 0.5*(Math.pow((beatTimeElapsed/squareSize - (prTime/2/squareSize)), 2)) - 2; + } else if (beatTimeElapsed > prTime + squareSize && beatTimeElapsed <= prTime + squareSize + (qrsTime / 4)) { //QRS Segment pt. 1 + nextBeatValue = -4 + beatTimeElapsed/squareSize; + } else if (beatTimeElapsed > prTime + squareSize + qrsTime / 4 && beatTimeElapsed <= prTime + squareSize + qrsTime / 2) { //QRS Segment pt. 2 + nextBeatValue = -14 * (beatTimeElapsed/squareSize - 4.5) - 0.5; + } else if (beatTimeElapsed > prTime + squareSize + qrsTime / 2 && beatTimeElapsed <= prTime + squareSize + (3*qrsTime / 4)) { //QRS Segment pt. 3 + nextBeatValue = 7 * (beatTimeElapsed/squareSize - 5) - 6.5; + } else if (beatTimeElapsed > prTime + squareSize + (3*qrsTime / 4) && beatTimeElapsed <= prTime + squareSize + qrsTime) { //QRS Segment pt. 4 + nextBeatValue = 2 * (beatTimeElapsed/squareSize - 6); + } else if (beatTimeElapsed > prTime + squareSize*2 + qrsTime && beatTimeElapsed <= prTime + squareSize*2 + qrsTime + qtTime) { //PT Segment + nextBeatValue = 0.5 * Math.pow((beatTimeElapsed/squareSize - (prTime + squareSize*2 + qrsTime + qtTime/2)/squareSize),2) - 3; + } } heartBeatHistory.push(nextBeatValue); diff --git a/docs/assets/js/constants.js b/docs/assets/js/constants.js index 4570ac8..d9c73f2 100644 --- a/docs/assets/js/constants.js +++ b/docs/assets/js/constants.js @@ -26,16 +26,32 @@ var constants = { history_length: 100, //300 squares/min - scroll_speed: 0.13333, - pr_width: 0.16, - qrs_width: 0.1, - qt_width: 0.39, - complex_width: 0.65 + scroll_speed: 0.8, + square_size: 0.08, + pr_width: 4, + qrs_width: 2, + qt_width: 5, + complex_width: 18 } }, - legs:{ - size:{ - maximumMovement: 30 + lifeFuncs:{ + breath:{ + fullBreath: 200 + }, + cardio:{ + optimalPressure: 50 + } + }, + player:{ + leg_speed: 0.1, + movement_divider: 50, + max_movement_speed: 3, + width: 30, + height: 50, + select_range: 10, + hip: { + offset_x: 15, + offset_y: 25 } } diff --git a/docs/assets/js/crypto/core.min.js b/docs/assets/js/crypto/core.min.js new file mode 100644 index 0000000..1fbb437 --- /dev/null +++ b/docs/assets/js/crypto/core.min.js @@ -0,0 +1 @@ +!function(t,n){"object"==typeof exports?module.exports=exports=n():"function"==typeof define&&define.amd?define([],n):t.CryptoJS=n()}(this,function(){var t=t||function(f){var t;if("undefined"!=typeof window&&window.crypto&&(t=window.crypto),!t&&"undefined"!=typeof window&&window.msCrypto&&(t=window.msCrypto),!t&&"undefined"!=typeof global&&global.crypto&&(t=global.crypto),!t&&"function"==typeof require)try{t=require("crypto")}catch(t){}function i(){if(t){if("function"==typeof t.getRandomValues)try{return t.getRandomValues(new Uint32Array(1))[0]}catch(t){}if("function"==typeof t.randomBytes)try{return t.randomBytes(4).readInt32LE()}catch(t){}}throw new Error("Native crypto module could not be used to get secure random number.")}var e=Object.create||function(t){var n;return r.prototype=t,n=new r,r.prototype=null,n};function r(){}var n={},o=n.lib={},s=o.Base={extend:function(t){var n=e(this);return t&&n.mixIn(t),n.hasOwnProperty("init")&&this.init!==n.init||(n.init=function(){n.$super.init.apply(this,arguments)}),(n.init.prototype=n).$super=this,n},create:function(){var t=this.extend();return t.init.apply(t,arguments),t},init:function(){},mixIn:function(t){for(var n in t)t.hasOwnProperty(n)&&(this[n]=t[n]);t.hasOwnProperty("toString")&&(this.toString=t.toString)},clone:function(){return this.init.prototype.extend(this)}},p=o.WordArray=s.extend({init:function(t,n){t=this.words=t||[],this.sigBytes=null!=n?n:4*t.length},toString:function(t){return(t||c).stringify(this)},concat:function(t){var n=this.words,e=t.words,i=this.sigBytes,r=t.sigBytes;if(this.clamp(),i%4)for(var o=0;o>>2]>>>24-o%4*8&255;n[i+o>>>2]|=s<<24-(i+o)%4*8}else for(o=0;o>>2]=e[o>>>2];return this.sigBytes+=r,this},clamp:function(){var t=this.words,n=this.sigBytes;t[n>>>2]&=4294967295<<32-n%4*8,t.length=f.ceil(n/4)},clone:function(){var t=s.clone.call(this);return t.words=this.words.slice(0),t},random:function(t){for(var n=[],e=0;e>>2]>>>24-r%4*8&255;i.push((o>>>4).toString(16)),i.push((15&o).toString(16))}return i.join("")},parse:function(t){for(var n=t.length,e=[],i=0;i>>3]|=parseInt(t.substr(i,2),16)<<24-i%8*4;return new p.init(e,n/2)}},u=a.Latin1={stringify:function(t){for(var n=t.words,e=t.sigBytes,i=[],r=0;r>>2]>>>24-r%4*8&255;i.push(String.fromCharCode(o))}return i.join("")},parse:function(t){for(var n=t.length,e=[],i=0;i>>2]|=(255&t.charCodeAt(i))<<24-i%4*8;return new p.init(e,n)}},d=a.Utf8={stringify:function(t){try{return decodeURIComponent(escape(u.stringify(t)))}catch(t){throw new Error("Malformed UTF-8 data")}},parse:function(t){return u.parse(unescape(encodeURIComponent(t)))}},h=o.BufferedBlockAlgorithm=s.extend({reset:function(){this._data=new p.init,this._nDataBytes=0},_append:function(t){"string"==typeof t&&(t=d.parse(t)),this._data.concat(t),this._nDataBytes+=t.sigBytes},_process:function(t){var n,e=this._data,i=e.words,r=e.sigBytes,o=this.blockSize,s=r/(4*o),a=(s=t?f.ceil(s):f.max((0|s)-this._minBufferSize,0))*o,c=f.min(4*a,r);if(a){for(var u=0;u>> 24)) & 0x00ff00ff) | + (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) + ); + } + + // Shortcuts + var H = this._hash.words; + + var M_offset_0 = M[offset + 0]; + var M_offset_1 = M[offset + 1]; + var M_offset_2 = M[offset + 2]; + var M_offset_3 = M[offset + 3]; + var M_offset_4 = M[offset + 4]; + var M_offset_5 = M[offset + 5]; + var M_offset_6 = M[offset + 6]; + var M_offset_7 = M[offset + 7]; + var M_offset_8 = M[offset + 8]; + var M_offset_9 = M[offset + 9]; + var M_offset_10 = M[offset + 10]; + var M_offset_11 = M[offset + 11]; + var M_offset_12 = M[offset + 12]; + var M_offset_13 = M[offset + 13]; + var M_offset_14 = M[offset + 14]; + var M_offset_15 = M[offset + 15]; + + // Working varialbes + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + + // Computation + a = FF(a, b, c, d, M_offset_0, 7, T[0]); + d = FF(d, a, b, c, M_offset_1, 12, T[1]); + c = FF(c, d, a, b, M_offset_2, 17, T[2]); + b = FF(b, c, d, a, M_offset_3, 22, T[3]); + a = FF(a, b, c, d, M_offset_4, 7, T[4]); + d = FF(d, a, b, c, M_offset_5, 12, T[5]); + c = FF(c, d, a, b, M_offset_6, 17, T[6]); + b = FF(b, c, d, a, M_offset_7, 22, T[7]); + a = FF(a, b, c, d, M_offset_8, 7, T[8]); + d = FF(d, a, b, c, M_offset_9, 12, T[9]); + c = FF(c, d, a, b, M_offset_10, 17, T[10]); + b = FF(b, c, d, a, M_offset_11, 22, T[11]); + a = FF(a, b, c, d, M_offset_12, 7, T[12]); + d = FF(d, a, b, c, M_offset_13, 12, T[13]); + c = FF(c, d, a, b, M_offset_14, 17, T[14]); + b = FF(b, c, d, a, M_offset_15, 22, T[15]); + + a = GG(a, b, c, d, M_offset_1, 5, T[16]); + d = GG(d, a, b, c, M_offset_6, 9, T[17]); + c = GG(c, d, a, b, M_offset_11, 14, T[18]); + b = GG(b, c, d, a, M_offset_0, 20, T[19]); + a = GG(a, b, c, d, M_offset_5, 5, T[20]); + d = GG(d, a, b, c, M_offset_10, 9, T[21]); + c = GG(c, d, a, b, M_offset_15, 14, T[22]); + b = GG(b, c, d, a, M_offset_4, 20, T[23]); + a = GG(a, b, c, d, M_offset_9, 5, T[24]); + d = GG(d, a, b, c, M_offset_14, 9, T[25]); + c = GG(c, d, a, b, M_offset_3, 14, T[26]); + b = GG(b, c, d, a, M_offset_8, 20, T[27]); + a = GG(a, b, c, d, M_offset_13, 5, T[28]); + d = GG(d, a, b, c, M_offset_2, 9, T[29]); + c = GG(c, d, a, b, M_offset_7, 14, T[30]); + b = GG(b, c, d, a, M_offset_12, 20, T[31]); + + a = HH(a, b, c, d, M_offset_5, 4, T[32]); + d = HH(d, a, b, c, M_offset_8, 11, T[33]); + c = HH(c, d, a, b, M_offset_11, 16, T[34]); + b = HH(b, c, d, a, M_offset_14, 23, T[35]); + a = HH(a, b, c, d, M_offset_1, 4, T[36]); + d = HH(d, a, b, c, M_offset_4, 11, T[37]); + c = HH(c, d, a, b, M_offset_7, 16, T[38]); + b = HH(b, c, d, a, M_offset_10, 23, T[39]); + a = HH(a, b, c, d, M_offset_13, 4, T[40]); + d = HH(d, a, b, c, M_offset_0, 11, T[41]); + c = HH(c, d, a, b, M_offset_3, 16, T[42]); + b = HH(b, c, d, a, M_offset_6, 23, T[43]); + a = HH(a, b, c, d, M_offset_9, 4, T[44]); + d = HH(d, a, b, c, M_offset_12, 11, T[45]); + c = HH(c, d, a, b, M_offset_15, 16, T[46]); + b = HH(b, c, d, a, M_offset_2, 23, T[47]); + + a = II(a, b, c, d, M_offset_0, 6, T[48]); + d = II(d, a, b, c, M_offset_7, 10, T[49]); + c = II(c, d, a, b, M_offset_14, 15, T[50]); + b = II(b, c, d, a, M_offset_5, 21, T[51]); + a = II(a, b, c, d, M_offset_12, 6, T[52]); + d = II(d, a, b, c, M_offset_3, 10, T[53]); + c = II(c, d, a, b, M_offset_10, 15, T[54]); + b = II(b, c, d, a, M_offset_1, 21, T[55]); + a = II(a, b, c, d, M_offset_8, 6, T[56]); + d = II(d, a, b, c, M_offset_15, 10, T[57]); + c = II(c, d, a, b, M_offset_6, 15, T[58]); + b = II(b, c, d, a, M_offset_13, 21, T[59]); + a = II(a, b, c, d, M_offset_4, 6, T[60]); + d = II(d, a, b, c, M_offset_11, 10, T[61]); + c = II(c, d, a, b, M_offset_2, 15, T[62]); + b = II(b, c, d, a, M_offset_9, 21, T[63]); + + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + + var nBitsTotalH = Math.floor(nBitsTotal / 0x100000000); + var nBitsTotalL = nBitsTotal; + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = ( + (((nBitsTotalH << 8) | (nBitsTotalH >>> 24)) & 0x00ff00ff) | + (((nBitsTotalH << 24) | (nBitsTotalH >>> 8)) & 0xff00ff00) + ); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( + (((nBitsTotalL << 8) | (nBitsTotalL >>> 24)) & 0x00ff00ff) | + (((nBitsTotalL << 24) | (nBitsTotalL >>> 8)) & 0xff00ff00) + ); + + data.sigBytes = (dataWords.length + 1) * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var hash = this._hash; + var H = hash.words; + + // Swap endian + for (var i = 0; i < 4; i++) { + // Shortcut + var H_i = H[i]; + + H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) | + (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00); + } + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + function FF(a, b, c, d, x, s, t) { + var n = a + ((b & c) | (~b & d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function GG(a, b, c, d, x, s, t) { + var n = a + ((b & d) | (c & ~d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function HH(a, b, c, d, x, s, t) { + var n = a + (b ^ c ^ d) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function II(a, b, c, d, x, s, t) { + var n = a + (c ^ (b | ~d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.MD5('message'); + * var hash = CryptoJS.MD5(wordArray); + */ + C.MD5 = Hasher._createHelper(MD5); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacMD5(message, key); + */ + C.HmacMD5 = Hasher._createHmacHelper(MD5); + }(Math)); + + + return CryptoJS.MD5; + +})); \ No newline at end of file diff --git a/docs/assets/js/game.js b/docs/assets/js/game.js index 868774e..262094c 100644 --- a/docs/assets/js/game.js +++ b/docs/assets/js/game.js @@ -341,6 +341,11 @@ function rect(x,y,w,h,color) { curCtx.fillRect(x-(w/2)+camera.x+difx,y-(h/2)+camera.y+dify,w,h); } +function cartesianRect(x,y,w,h,color) { + curCtx.fillStyle = color; + curCtx.fillRect(x+camera.x+difx,y+camera.y+dify,w,h); +} + function circle(x,y,r,color) { curCtx.beginPath(); curCtx.arc(x+camera.x+difx, y+camera.y+dify, r, 0, 2 * Math.PI, false); @@ -348,11 +353,12 @@ function circle(x,y,r,color) { curCtx.fill(); } -function line(x1, y1, x2, y2, color) { +function line(x1, y1, x2, y2, weight, color) { curCtx.beginPath(); - curCtx.style = color; + curCtx.strokeStyle = color; + curCtx.lineWidth = weight; curCtx.moveTo(x1 + camera.x + difx, y1 + camera.y + dify); - curCtx.lineTo(x2 + camera.x + difx, y2 + camera.y + dify); + curCtx.lineTo(x2 + camera.x + difx , y2 + camera.y + dify); curCtx.stroke(); } diff --git a/docs/assets/js/index.js b/docs/assets/js/index.js index b3a82eb..b288566 100644 --- a/docs/assets/js/index.js +++ b/docs/assets/js/index.js @@ -51,6 +51,12 @@ function update() { } function draw() { + + // If draw is being called, the user has interacted with the page at least once. + // This signal can be used to notify the audio permission handler + unlockAudioPermission(); + + // Handle game state switch (globalState) { // title screen case globalStates.titleScreen: @@ -62,6 +68,7 @@ function draw() { break; // playing case globalStates.playing: + camera.zoom = 2; drawWorldBlocks(); player.draw(); break; @@ -93,7 +100,6 @@ function absoluteDraw() { // playing case globalStates.playing: drawPlayingUI(); - break; // paused case globalStates.paused: @@ -113,6 +119,25 @@ function onAssetsLoaded() { setup(60); -// Hide the preloader -// This should actually run after all assets have been downloaded -page_preloader.hide(false); \ No newline at end of file +/* Preload actions */ + +// To make something happen before the preloader is hidden, add it to this list +// The function must take a callback that will be run when the function finishes +let actions = [preCacheSounds]; +let actionsCompleted = 0; + +// Loop through every action, and load it +actions.forEach((action) => { + + // Run the action & handle loading + action(() => { + + // Incr the number of successfully loaded actions + actionsCompleted += 1; + + // If this is the last aciton, hide the loader + if (actionsCompleted == actions.length) { + page_preloader.hide(false); + } + }) +}); diff --git a/docs/assets/js/player/lifeFunctions.js b/docs/assets/js/player/lifeFunctions.js index deb1cd0..105ab0e 100644 --- a/docs/assets/js/player/lifeFunctions.js +++ b/docs/assets/js/player/lifeFunctions.js @@ -1,44 +1,75 @@ -var breath = 180; -var fullBreathTimer = 0; -var heartRate = 60; +let breath = 180; +let fullBreathTimer = 0; +let noBreathTimer = 0; +let pressure = 50; + +let heartBeat = false; + +var breathMode = { + inhale: 0, + exhale: 1 +}; + +let currentBreathMode = breathMode.exhale; -var timeSinceLastBeat = 0; function updateLife() { - if(keyDown[k.Z]) { - breathe(); - } else { - breath--; + if(keyDown[k.w]) { + if(breath === 0) currentBreathMode = breathMode.inhale; } - if(keyPress[k.X]) { + if(keyDown[k.s]) { + if(breath === constants.lifeFuncs.breath.fullBreath) currentBreathMode = breathMode.exhale; + } + + breathe(); + + if(keyPress[k.x]) { heartbeat(); - } else { - timeSinceLastBeat++; } + pressure-=0.25; + if(pressure<=0){ + pressure = 0; + } }; function breathe() { - - breath += 5; - if(breath >= 200) { - breath = 200; - fullBreathTimer++; - if(fullBreathTimer >= 60) { - //cough and lose breath or something - } - } else { - fullBreathTimer = 0; + switch (currentBreathMode) { + case breathMode.inhale: + breath += 1; + if(breath >= constants.lifeFuncs.breath.fullBreath) { + breath = constants.lifeFuncs.breath.fullBreath; + fullBreathTimer++; + if(fullBreathTimer >= 600) { + //cough and lose breath or something + } + } else { + fullBreathTimer = 0; + } + break; + case breathMode.exhale: + breath -= 2; + if(breath <= 0) { + breath = 0; + noBreathTimer++; + if(noBreathTimer >= 300) { + //cough and lose breath or something + } + } else { + noBreathTimer = 0; + } + break; } - }; function heartbeat() { - - timeSinceLastBeat = 0; - + pressure+=10; + if(pressure>=100){ + pressure = 100; + } + heartBeat = true; }; \ No newline at end of file diff --git a/docs/assets/js/player/player.js b/docs/assets/js/player/player.js index 5e356c4..601cb43 100644 --- a/docs/assets/js/player/player.js +++ b/docs/assets/js/player/player.js @@ -1,49 +1,88 @@ class Player { - - constructor(x, y){ + constructor(x, y) { this.x = x; this.y = y; - this.w = 10; - this.h = 20; - this.hipLeft = {x:this.x-5,y:this.y+10}; - this.hipRight = {x:this.x+5,y:this.y+10}; - this.leftLeg = new Leg(this.hipLeft.x,this.hipLeft.y,50,-Math.PI/4); - this.rightLeg = new Leg(this.hipRight.x, this.hipRight.y, 50, Math.PI/2); + this.w = constants.player.width; + this.h = constants.player.height; + this.hipLeft = { x: this.x - constants.player.hip.offset_x, y: this.y + constants.player.hip.offset_y }; + this.hipRight = { x: this.x + constants.player.hip.offset_x, y: this.y + constants.player.hip.offset_y }; + this.leftLeg = new Leg(this.hipLeft.x, this.hipLeft.y, 50, -Math.PI / 4); + this.rightLeg = new Leg(this.hipRight.x, this.hipRight.y, 50, Math.PI / 2); this.legSelected = "R"; - this.shouldMoveLeg = true; + this.shouldMoveLeg = false; this.collided = false; this.lastBodyX = x; this.lastBodyY = y; + this.hover = { active: false, leg: "R" }; } - - - } Player.prototype.getActiveLeg = function(){ - if(this.legSelected === "L"){ + if (this.legSelected === "L") { return this.leftLeg; } return this.rightLeg; } Player.prototype.getLockedLeg = function(){ - if(this.legSelected === "R"){ + if (this.legSelected === "R") { return this.leftLeg; } return this.rightLeg; } -// leg has been selected, move leg towards mouse -Player.prototype.moveLeg = function(){ - // Stops if we shouldn't move leg - if(!this.shouldMoveLeg){ - return 0; +Player.prototype.update = function() { + var curLeg = this.getActiveLeg(); + + // select + if (this.shouldMoveLeg) { + this.moveLeg(); + if(mousePress[0]) {// if (collidingWithWorld({ x: curLeg.x2, y: curLeg.y2, w: 4, h: 4 })) { + if (this.legSelected === "R") { + this.leftLeg.angle += pi; + } else { + this.rightLeg.angle += pi; + } + this.shouldMoveLeg = false; + } + // deselect + } else { + + var targetPos = mousePosition(); + var curLeg = this.getActiveLeg(); + this.hover.active = false; + //left + if (distanceToLineSegment(this.leftLeg.x, this.leftLeg.y, this.leftLeg.x2, this.leftLeg.y2, targetPos.x, targetPos.y) < constants.player.select_range) { + this.hover.active = true; + this.hover.leg = "L"; + if(mousePress[0]) { + this.shouldMoveLeg = true; + this.legSelected = "L"; + this.hover.active = false; + } + // right + } else if (distanceToLineSegment(this.rightLeg.x, this.rightLeg.y, this.rightLeg.x2, this.rightLeg.y2, targetPos.x, targetPos.y) < constants.player.select_range) { + this.hover.active = true; + this.hover.leg = "R"; + if(mousePress[0]) { + this.shouldMoveLeg = true; + this.legSelected = "R"; + this.hover.active = false; + } + } } + centerCameraOn(this.x,this.y); +} + + +// leg has been selected, move leg towards mouse +Player.prototype.moveLeg = function(){ + var targetPos = mousePosition(); + // gets active leg & target var curLeg = this.getActiveLeg(); - var target = mousePos; + var target = targetPos; // Last leg position var lastX = curLeg.x2; @@ -51,8 +90,9 @@ Player.prototype.moveLeg = function(){ // move selected leg towards mouse + // console.log(curLeg.angle.toPrecision(5),pointTo(curLeg,target).toPrecision(5)); - curLeg.angle = turn( curLeg.angle,pointTo(curLeg,target),0.1); + curLeg.angle = turn(curLeg.angle, pointTo(curLeg, target), constants.player.leg_speed); // var angle = pointTo(curLeg,target); curLeg.x2 = curLeg.x + curLeg.len * Math.cos(curLeg.angle); curLeg.y2 = curLeg.y + curLeg.len * Math.sin(curLeg.angle); @@ -74,44 +114,43 @@ Player.prototype.moveLeg = function(){ } - if(dist(curLeg,target) > curLeg.len) { + if (dist(curLeg, target) > curLeg.len) { // move towards mouse - this.x += Math.cos(pointTo(curLeg,target)) * clamp(dist(curLeg,target)/50,1,3); + this.x += Math.cos(pointTo(curLeg, target)) * clamp(dist(curLeg, target) / constants.player.movement_divider, 1, constants.player.max_movement_speed); - this.y += Math.sin(pointTo(curLeg,target)) * clamp(dist(curLeg,target)/50,1,3); + this.y += Math.sin(pointTo(curLeg, target)) * clamp(dist(curLeg, target) / constants.player.movement_divider, 1, constants.player.max_movement_speed); this.updateHips(); } // if leg is right update it accordingly - if(this.legSelected === "R") { + if (this.legSelected === "R") { // set angle to the locked foot to the locked hip oppLeg = this.getLockedLeg(); - oppLeg.angle = pointTo({x:oppLeg.x2,y:oppLeg.y2},this.hipRight); + oppLeg.angle = pointTo({ x: oppLeg.x2, y: oppLeg.y2 }, this.hipRight); + // snap body to a position where the hip is attached to the leg - this.x = (oppLeg.x2 + oppLeg.len * Math.cos(oppLeg.angle)) - 5; - this.y = (oppLeg.y2 + oppLeg.len * Math.sin(oppLeg.angle)) - 10; + this.x = (oppLeg.x2 + oppLeg.len * Math.cos(oppLeg.angle)) - constants.player.hip.offset_x; + this.y = (oppLeg.y2 + oppLeg.len * Math.sin(oppLeg.angle)) - constants.player.hip.offset_y; this.updateHips(); - + // make sure each leg ends at the hips oppLeg.x = this.hipRight.x; oppLeg.y = this.hipRight.y; curLeg.x = this.hipLeft.x; curLeg.y = this.hipLeft.y; - //console.log(oppLeg.angle) - // if leg is left update it accordingly + // if leg is left update it accordingly } else { - //console.log(curLeg.angle) - // set angle to the locked foot to the locked hip + // set angle to the locked foot to the locked hip oppLeg = this.getLockedLeg(); - oppLeg.angle = pointTo({x:oppLeg.x2,y:oppLeg.y2},this.hipLeft); + oppLeg.angle = pointTo({ x: oppLeg.x2, y: oppLeg.y2 }, this.hipLeft); // snap body to a position where the hip is attached to the leg - this.x = (oppLeg.x2 + oppLeg.len * Math.cos(oppLeg.angle)) + 5; - this.y = (oppLeg.y2 + oppLeg.len * Math.sin(oppLeg.angle)) - 10; + this.x = (oppLeg.x2 + oppLeg.len * Math.cos(oppLeg.angle)) + constants.player.hip.offset_x; + this.y = (oppLeg.y2 + oppLeg.len * Math.sin(oppLeg.angle)) - constants.player.hip.offset_y; this.updateHips(); @@ -132,34 +171,70 @@ Player.prototype.moveLeg = function(){ } Player.prototype.updateHips = function() { - this.hipLeft = {x:this.x-5,y:this.y+10}; - this.hipRight = {x:this.x+5,y:this.y+10}; + this.hipLeft = { x: this.x - constants.player.hip.offset_x, y: this.y + constants.player.hip.offset_y }; + this.hipRight = { x: this.x + constants.player.hip.offset_x, y: this.y + constants.player.hip.offset_y }; } Player.prototype.draw = function() { - rect(this.x, this.y, this.w, this.h,"green"); - - this.leftLeg.draw(); - this.rightLeg.draw(); + rect(this.x, this.y, this.w, this.h, "green"); + if(this.hover.active) { + if(this.hover.leg === "R") { + curCtx.shadowBlur = 10; + curCtx.shadowColor = "yellow"; + curCtx.lineWidth = 3; + this.rightLeg.draw(); + curCtx.lineWidth = 1; + curCtx.shadowBlur = 0; + curCtx.shadowColor = "black"; + this.leftLeg.draw(); + } else { + curCtx.shadowBlur = 10; + curCtx.shadowColor = "yellow"; + curCtx.lineWidth = 3; + this.leftLeg.draw(); + curCtx.lineWidth = 1; + curCtx.shadowBlur = 0; + curCtx.shadowColor = "black"; + this.rightLeg.draw(); + } + } else { + this.leftLeg.draw(); + this.rightLeg.draw(); + } } -Player.prototype.update = function() { - this.moveLeg(); - var curLeg = this.getActiveLeg(); - if(mousePress[0] || this.collided){ - if(this.legSelected === "R"){ - this.legSelected = "L"; - this.leftLeg.angle += pi; - } else { - this.legSelected = "R"; - this.rightLeg.angle += pi; - } - - this.collided = false; - +// https://github.com/scottglz/distance-to-line-segment/blob/master/index.js +function distanceSquaredToLineSegment2(lx1, ly1, ldx, ldy, lineLengthSquared, px, py) { + var t; // t===0 at line pt 1 and t ===1 at line pt 2 + if (!lineLengthSquared) { + // 0-length line segment. Any t will return same result + t = 0; } - centerCameraOn(this.x,this.y); + else { + t = ((px - lx1) * ldx + (py - ly1) * ldy) / lineLengthSquared; + + if (t < 0) + t = 0; + else if (t > 1) + t = 1; + } + + var lx = lx1 + t * ldx, + ly = ly1 + t * ldy, + dx = px - lx, + dy = py - ly; + return dx * dx + dy * dy; +} +function distanceSquaredToLineSegment(lx1, ly1, lx2, ly2, px, py) { + var ldx = lx2 - lx1, + ldy = ly2 - ly1, + lineLengthSquared = ldx * ldx + ldy * ldy; + return distanceSquaredToLineSegment2(lx1, ly1, ldx, ldy, lineLengthSquared, px, py); +} + +function distanceToLineSegment(lx1, ly1, lx2, ly2, px, py) { + return Math.sqrt(distanceSquaredToLineSegment(lx1, ly1, lx2, ly2, px, py)); } diff --git a/docs/assets/js/playing/playing.js b/docs/assets/js/playing/playing.js index 995b15f..006c491 100644 --- a/docs/assets/js/playing/playing.js +++ b/docs/assets/js/playing/playing.js @@ -3,4 +3,6 @@ function handlePlaying() { if(keyPress[k.BACKSLASH]) { globalState = globalStates.building; } + + updateLife(); } \ No newline at end of file diff --git a/docs/assets/js/sounds/permissionhandler.js b/docs/assets/js/sounds/permissionhandler.js new file mode 100644 index 0000000..1a5f2fb --- /dev/null +++ b/docs/assets/js/sounds/permissionhandler.js @@ -0,0 +1,22 @@ +/** + * This file just exists to keep track of weather the user has interacted with the + * webpage. Most browsers will block autoplay if no interaction has been made. + */ + + +// Tracker for permission unlock +let _audioPermUnlock = false; + +/** + * Call this when the user interacts with the page + */ +function unlockAudioPermission() { + _audioPermUnlock = true; +} + +/** + * Check if autoplay is enabled + */ +function canPlayAudio() { + return _audioPermUnlock; +} \ No newline at end of file diff --git a/docs/assets/js/sounds/soundcontext.js b/docs/assets/js/sounds/soundcontext.js new file mode 100644 index 0000000..f9dd3da --- /dev/null +++ b/docs/assets/js/sounds/soundcontext.js @@ -0,0 +1,164 @@ +/** + * This file contains classes for playing sounds. + * The SoundContext works by providing multiple audio channels, + * just like an old ATARI, or even GameBoy sound system. Each + * channel can be controlled individually. + * + * ---- Usage ---- + * + * // Load all sounds + * preCacheSounds(()=>{ + * // Code can be run here as soon as all sounds are loaded + * // ... + * }); + * + * // Play a sound using channels + * globalSoundContext.playSound(globalSoundContext.channels., sounds.); + * + * // Just play a sound now + * globalSoundContext.playSoundNow(sounds.); + * + * // Stop a channel + * globalSoundContext.mute(globalSoundContext.channels.); + */ + +/** + * A sound channel can play 1 sound at a time, and supports sound queueing + */ +class _SoundChannel { + + /** + * Create a sound channel + * @param {number} max_queue_size Maximum number of sounds that can be queued before sounds are skipped + */ + constructor(max_queue_size) { + this.max_size = max_queue_size + this.sound_queue = [] + } + + /** + * Add a snippet to the queue + * @param {SoundSnippet} snippet + */ + enqueue(snippet) { + + console.log(this.sound_queue) + + // If the queue is full, cut out the next sound in the queue to make room + // if (this.sound_queue.length > this.max_size) { + // this.sound_queue.splice(1, 1); + // } + + // Append the sound to the queue + this.sound_queue.push(snippet); + + // If this is the first sound in the queue, spawn a watcher job, and play it + if (this.sound_queue.length == 1) { + this.sound_queue[0].play(); + this._spawnWatcher(this.sound_queue[0]); + } + } + + /** + * Start a job to run when the sound finishes to remove the sound from the queue + * @param {SoundSnippet} snippet + */ + _spawnWatcher(snippet) { + + // Read the snippet length + let length = snippet.getLengthSeconds(); + + // Spawn a clean action for that time in the future + setTimeout(() => { + this._cleanQueue(snippet); + }, length * 1000); + } + + /** + * This should be run when every sound finishes. This will remove that + * sound from from the queue, start the next sound, and spawn a + * new watcher for that sound. + * @param {SoundSnippet} snippet + */ + _cleanQueue(snippet) { + + // Get the snippet hash + let hash = snippet.getHash(); + + // Make sure there are actually sounds playing + if (this.sound_queue.length > 0) { + + // If the first snippet in the queue matches this hash, remove it. + // If not, something must have happened. Just ignore it, and move on + if (this.sound_queue[0].getHash() == hash) { + + // Popoff the snippet + this.sound_queue.shift(); + + } + + // Spawn a watcher for the next sound & play it + if (this.sound_queue.length > 0) { + this.sound_queue[0].play(); + this._spawnWatcher(this.sound_queue[0]); + } + } + } + + /** + * Clear the entire queue, and stop all sounds + */ + clear() { + + // Stop every sound + this.sound_queue.forEach((sound) => { + sound.stop(); + }) + + // Clear the queue + this.sound_queue = []; + + } +} + + +class _SoundContext { + + constructor() { + + // Define all sound channels + this.channels = { + bgm: new _SoundChannel(3) + } + } + + /** + * Play a sound in a channel + * @param {*} channel + * @param {SoundSnippet} snippet + */ + playSound(channel, snippet) { + console.log(`[SoundContext] Playing snippet: ${snippet.getName()}`); + channel.enqueue(snippet); + } + + /** + * Play a sound right now + * @param {SoundSnippet} snippet + */ + playSoundNow(snippet) { + snippet.play(); + } + + /** + * Stop all sounds in a channel + * @param {*} channel + */ + mute(channel) { + channel.clear(); + } + +} + +// The global context for sounds +let globalSoundContext = new _SoundContext(); \ No newline at end of file diff --git a/docs/assets/js/sounds/sounds.js b/docs/assets/js/sounds/sounds.js new file mode 100644 index 0000000..935dc6a --- /dev/null +++ b/docs/assets/js/sounds/sounds.js @@ -0,0 +1,63 @@ +/** + * This file handles all sound assets, and loading them + * To add a new sound asset: + * 1) Make a mapping from the asset name to it's filename in soundAssetMap + * 2) Define the SoundSnippet in soundAssets + * + * The preloader will handle asset loading for you. + * Make sure to check the console for any errors with loading your file + */ + + +// A mapping of asset names to their files +// This exists to give nicer names to files +let soundAssetMap = { + "debug-ding":"/assets/sounds/debug-ding.mp3" +} + +// All available sounds +let soundAssets = { + debug_ding: new SoundSnippet("debug-ding") +} + +/** + * Cache all sounds in browser, then notify a callback of success + * @param {function} callback Callback for completion + */ +function preCacheSounds(callback) { + + // Counter for number of sounds cached + let cachedCount = 0; + + Object.keys(soundAssets).forEach((key) => { + + // Get the SoundSnippet + let sound = soundAssets[key]; + + // Cache the sound + sound.cache(() => { + + // Incr the cache count + cachedCount += 1; + + // If this is the last sound, fire off the callback + if (cachedCount == Object.keys(soundAssets).length) { + callback(); + } + + }); + + }); + + + // Spawn a notifier for loading issues + setTimeout(() => { + + // If not all sounds have been cached by the time this is called, send a warning + if (cachedCount < Object.keys(soundAssets).length) { + console.warn(`[preCacheSounds] Only ${cachedCount} of ${Object.keys(soundAssets).length} sounds have been cached after 2 seconds. Is there a missing asset? or is the user on a slow connection?`); + } + + }, 2000); + +} \ No newline at end of file diff --git a/docs/assets/js/sounds/soundsnippet.js b/docs/assets/js/sounds/soundsnippet.js new file mode 100644 index 0000000..3aeb74e --- /dev/null +++ b/docs/assets/js/sounds/soundsnippet.js @@ -0,0 +1,81 @@ + +// Counter for hash generation +let _hashCounter = 0; + +class SoundSnippet { + + /** + * Load a sound asset to a snipper + * @param {string} asset_name Asset name as defined in soundassetmap.js + */ + constructor(asset_name) { + + // Store asset name + this.asset_name = asset_name; + + // Compute an asset hash + this.asset_hash = CryptoJS.MD5(`ASSET:${asset_name}::${_hashCounter}`); + _hashCounter += 1; + + // Read actual asset path + this.assetPath = soundAssetMap[asset_name]; + + // Set up the audio object + this.audio = new Audio(); + + } + + /** + * Cache this sound, then notify a callback of completion + * @param {function} callback callback to notify + */ + cache(callback) { + + // Set the audio SRC + this.audio.src = this.assetPath; + + // Create a callback for loading finished + this.audio.addEventListener("loadeddata", callback, true); + } + + + play() { + + // If autoplay is disabled, we notify the console + if (canPlayAudio()) { + // Play the snippet + this.audio.play(); + } else { + console.warn("[SoundSnippet] Tried to play audio with autoplay disabled. The user must press the play button before you can play audio"); + } + + } + + stop() { + + // Stop the snippet + this.audio.stop(); + + } + + /** + * Get the sound length in seconds + */ + getLengthSeconds() { + return this.audio.duration; + } + + /** + * Get the asset name + */ + getName() { + return this.asset_name; + } + + /** + * Get this asset's hash. This can be used for comparing objects efficiently. + */ + getHash() { + return this.asset_hash; + } +} \ No newline at end of file diff --git a/docs/assets/sounds/debug-ding.mp3 b/docs/assets/sounds/debug-ding.mp3 new file mode 100644 index 0000000..ff9ebbd Binary files /dev/null and b/docs/assets/sounds/debug-ding.mp3 differ diff --git a/docs/index.html b/docs/index.html index 4ca07ad..0f3ecfb 100644 --- a/docs/index.html +++ b/docs/index.html @@ -24,6 +24,10 @@ + + + + @@ -34,17 +38,23 @@ - + - + + + + + + +