diff --git a/.gitignore b/.gitignore index 485dee6..de03f38 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .idea +.env diff --git a/data/scripts/events.js b/data/scripts/events.js deleted file mode 100644 index 652bc81..0000000 --- a/data/scripts/events.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -function keyPressed(){ - -} - -function keyReleased(){ - -} - -function mouseMoved(){ - -} - -function mouseDragged(){ - -} - -function mousePressed(){ - if (game) - if (!game.winId) - game.onMouseDown(); -} - -function mouseReleased(){ - -} - -window.onresize = () => { - let w = $('#canvas-holder').width(); - let h = $('#canvas-holder').height(); - resizeCanvas(w, h); -} \ No newline at end of file diff --git a/data/scripts/init.js b/data/scripts/init.js deleted file mode 100644 index 87bebc0..0000000 --- a/data/scripts/init.js +++ /dev/null @@ -1,104 +0,0 @@ -'use strict'; - -let projectName = "chainreact"; - -let debug = false, - productionMode = false, - font, - localSettings, - loader; - -//Only for online games -let socket; - -let game; -let gemContentImage; -let gemBorderImage; -let gemContentGraphics = {}; - -let antiCacheQuery = '?_=' + new Date().getTime(); - -function preload(){ - localSettings = loadJSON('data/settings/settings.json' + antiCacheQuery, json => { - console.log('Local settings loaded: ', json); - }, error => { - console.log('Local settings failed: ', error); - }); - - font = loadFont('data/styles/Tajawal/Tajawal-Regular.ttf', json => { - console.log('Local font loaded: ', json); - }, error => { - console.log('Local font failed: ', error); - }); - - gemContentImage = loadImage('data/images/gem_content.png', img => { - console.log('Image loaded: ', img); - }, error => { - console.log('Image failed: ' , error); - }); - - gemBorderImage = loadImage('data/images/gem_border.png', img => { - console.log('Image loaded: ', img); - }, error => { - console.log('Image failed: ' , error); - }); - - loadJSON('data/settings/libraries.json' + antiCacheQuery, json => { - loadScripts(json) - console.log('BenjoCraeft library scripts loaded: ', json) - }); -} - -function setup(){ - canvasSetup(); - interfaceSetup(); -} - -function draw(){ - background(0, 0, 10); - - if (game){ - game.display(); - game.update(); - } - - if (loader){ - loader.update(); - loader.display(); - } - - if (debug) debugInformation(); -} - -function canvasSetup(){ - setFrameRate(60); - let w = $('#canvas-holder').width(), - h = $('#canvas-holder').height(); - let canvas = createCanvas(w, h); - canvas.parent('canvas-holder'); - textFont(font); - textAlign(CENTER, CENTER); - imageMode(CENTER); - colorMode(HSB); -} - -function interfaceSetup(){ - window.onresize(); - setInterval(() => window.onresize(), 500); - $('#version').html(localSettings.project.version); - $('#start_feedback, #give_feedback').attr('disabled', 'disabled'); - nameTyped($('#main > input')); - - $('#main').fadeIn(menuesFadeTime); -} - -function loadScripts(libs){ - for (let script in libs){ - if (libs[script]){ - let url = '/lib/benjocraeft/' + script + '.js' - $.getScript(url, () => { - console.log('Successfully loaded script: ', url) - }); - } - } -} \ No newline at end of file diff --git a/changelog.txt b/public/changelog.txt similarity index 100% rename from changelog.txt rename to public/changelog.txt diff --git a/data/images/favicon.ico b/public/data/images/favicon.ico similarity index 100% rename from data/images/favicon.ico rename to public/data/images/favicon.ico diff --git a/data/images/gem_border.png b/public/data/images/gem_border.png similarity index 100% rename from data/images/gem_border.png rename to public/data/images/gem_border.png diff --git a/data/images/gem_content.png b/public/data/images/gem_content.png similarity index 100% rename from data/images/gem_content.png rename to public/data/images/gem_content.png diff --git a/data/images/gem_full.png b/public/data/images/gem_full.png similarity index 100% rename from data/images/gem_full.png rename to public/data/images/gem_full.png diff --git a/public/data/lib/socketiop2p.min.js b/public/data/lib/socketiop2p.min.js new file mode 100644 index 0000000..8c23f87 --- /dev/null +++ b/public/data/lib/socketiop2p.min.js @@ -0,0 +1,4 @@ +(function(e){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=e()}else if(typeof define==="function"&&define.amd){define([],e)}else{var t;if(typeof window!=="undefined"){t=window}else if(typeof global!=="undefined"){t=global}else if(typeof self!=="undefined"){t=self}else{t=this}t.P2P=e()}})(function(){var e,t,r;return function n(e,t,r){function i(a,s){if(!t[a]){if(!e[a]){var f=typeof require=="function"&&require;if(!s&&f)return f(a,!0);if(o)return o(a,!0);var c=new Error("Cannot find module '"+a+"'");throw c.code="MODULE_NOT_FOUND",c}var u=t[a]={exports:{}};e[a][0].call(u.exports,function(t){var r=e[a][1][t];return i(r?r:t)},u,u.exports,n,e,t,r)}return t[a].exports}var o=typeof require=="function"&&require;for(var a=0;aa)throw new RangeError("Attempt to allocate Buffer larger than maximum "+"size: 0x"+a.toString(16)+" bytes");if(i<0)i=0;else i>>>=0;var c=this;if(f.TYPED_ARRAY_SUPPORT){c=f._augment(new Uint8Array(i))}else{c.length=i;c._isBuffer=true}var u;if(f.TYPED_ARRAY_SUPPORT&&typeof e.byteLength==="number"){c._set(e)}else if(P(e)){if(f.isBuffer(e)){for(u=0;u0&&i<=f.poolSize)c.parent=s;return c}function c(e,t,r){if(!(this instanceof c))return new c(e,t,r);var n=new f(e,t,r);delete n.parent;return n}f.isBuffer=function(e){return!!(e!=null&&e._isBuffer)};f.compare=function(e,t){if(!f.isBuffer(e)||!f.isBuffer(t))throw new TypeError("Arguments must be Buffers");if(e===t)return 0;var r=e.length;var n=t.length;for(var i=0,o=Math.min(r,n);i>>1;break;case"utf8":case"utf-8":r=j(e).length;break;case"base64":r=O(e).length;break;default:r=e.length}return r};f.prototype.length=undefined;f.prototype.parent=undefined;f.prototype.toString=function(e,t,r){var n=false;t=t>>>0;r=r===undefined||r===Infinity?this.length:r>>>0;if(!e)e="utf8";if(t<0)t=0;if(r>this.length)r=this.length;if(r<=t)return"";while(true){switch(e){case"hex":return w(this,t,r);case"utf8":case"utf-8":return b(this,t,r);case"ascii":return y(this,t,r);case"binary":return m(this,t,r);case"base64":return v(this,t,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return _(this,t,r);default:if(n)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase();n=true}}};f.prototype.equals=function(e){if(!f.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(this===e)return true;return f.compare(this,e)===0};f.prototype.inspect=function(){var e="";var t=r.INSPECT_MAX_BYTES;if(this.length>0){e=this.toString("hex",0,t).match(/.{2}/g).join(" ");if(this.length>t)e+=" ... "}return""};f.prototype.compare=function(e){if(!f.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(this===e)return 0;return f.compare(this,e)};f.prototype.get=function(e){console.log(".get() is deprecated. Access using array indexes instead.");return this.readUInt8(e)};f.prototype.set=function(e,t){console.log(".set() is deprecated. Access using array indexes instead.");return this.writeUInt8(e,t)};function u(e,t,r,n){r=Number(r)||0;var i=e.length-r;if(!n){n=i}else{n=Number(n);if(n>i){n=i}}var o=t.length;if(o%2!==0)throw new Error("Invalid hex string");if(n>o/2){n=o/2}for(var a=0;athis.length)throw new RangeError("attempt to write outside buffer bounds");var o=this.length-t;if(!r){r=o}else{r=Number(r);if(r>o){r=o}}n=String(n||"utf8").toLowerCase();var a;switch(n){case"hex":a=u(this,e,t,r);break;case"utf8":case"utf-8":a=l(this,e,t,r);break;case"ascii":a=h(this,e,t,r);break;case"binary":a=d(this,e,t,r);break;case"base64":a=p(this,e,t,r);break;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":a=g(this,e,t,r);break;default:throw new TypeError("Unknown encoding: "+n)}return a};f.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};function v(e,t,r){if(t===0&&r===e.length){return n.fromByteArray(e)}else{return n.fromByteArray(e.slice(t,r))}}function b(e,t,r){var n="";var i="";r=Math.min(e.length,r);for(var o=t;on)r=n;var i="";for(var o=t;or){e=r}if(t<0){t+=r;if(t<0)t=0}else if(t>r){t=r}if(tr)throw new RangeError("Trying to access beyond buffer length")}f.prototype.readUIntLE=function(e,t,r){e=e>>>0;t=t>>>0;if(!r)E(e,t,this.length);var n=this[e];var i=1;var o=0;while(++o>>0;t=t>>>0;if(!r)E(e,t,this.length);var n=this[e+--t];var i=1;while(t>0&&(i*=256))n+=this[e+--t]*i;return n};f.prototype.readUInt8=function(e,t){if(!t)E(e,1,this.length);return this[e]};f.prototype.readUInt16LE=function(e,t){if(!t)E(e,2,this.length);return this[e]|this[e+1]<<8};f.prototype.readUInt16BE=function(e,t){if(!t)E(e,2,this.length);return this[e]<<8|this[e+1]};f.prototype.readUInt32LE=function(e,t){if(!t)E(e,4,this.length);return(this[e]|this[e+1]<<8|this[e+2]<<16)+this[e+3]*16777216};f.prototype.readUInt32BE=function(e,t){if(!t)E(e,4,this.length);return this[e]*16777216+(this[e+1]<<16|this[e+2]<<8|this[e+3])};f.prototype.readIntLE=function(e,t,r){e=e>>>0;t=t>>>0;if(!r)E(e,t,this.length);var n=this[e];var i=1;var o=0;while(++o=i)n-=Math.pow(2,8*t);return n};f.prototype.readIntBE=function(e,t,r){e=e>>>0;t=t>>>0;if(!r)E(e,t,this.length);var n=t;var i=1;var o=this[e+--n];while(n>0&&(i*=256))o+=this[e+--n]*i;i*=128;if(o>=i)o-=Math.pow(2,8*t);return o};f.prototype.readInt8=function(e,t){if(!t)E(e,1,this.length);if(!(this[e]&128))return this[e];return(255-this[e]+1)*-1};f.prototype.readInt16LE=function(e,t){if(!t)E(e,2,this.length);var r=this[e]|this[e+1]<<8;return r&32768?r|4294901760:r};f.prototype.readInt16BE=function(e,t){if(!t)E(e,2,this.length);var r=this[e+1]|this[e]<<8;return r&32768?r|4294901760:r};f.prototype.readInt32LE=function(e,t){if(!t)E(e,4,this.length);return this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24};f.prototype.readInt32BE=function(e,t){if(!t)E(e,4,this.length);return this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]};f.prototype.readFloatLE=function(e,t){if(!t)E(e,4,this.length);return i.read(this,e,true,23,4)};f.prototype.readFloatBE=function(e,t){if(!t)E(e,4,this.length);return i.read(this,e,false,23,4)};f.prototype.readDoubleLE=function(e,t){if(!t)E(e,8,this.length);return i.read(this,e,true,52,8)};f.prototype.readDoubleBE=function(e,t){if(!t)E(e,8,this.length);return i.read(this,e,false,52,8)};function A(e,t,r,n,i,o){if(!f.isBuffer(e))throw new TypeError("buffer must be a Buffer instance");if(t>i||te.length)throw new RangeError("index out of range")}f.prototype.writeUIntLE=function(e,t,r,n){e=+e;t=t>>>0;r=r>>>0;if(!n)A(this,e,t,r,Math.pow(2,8*r),0);var i=1;var o=0;this[t]=e&255;while(++o>>0&255;return t+r};f.prototype.writeUIntBE=function(e,t,r,n){e=+e;t=t>>>0;r=r>>>0;if(!n)A(this,e,t,r,Math.pow(2,8*r),0);var i=r-1;var o=1;this[t+i]=e&255;while(--i>=0&&(o*=256))this[t+i]=e/o>>>0&255;return t+r};f.prototype.writeUInt8=function(e,t,r){e=+e;t=t>>>0;if(!r)A(this,e,t,1,255,0);if(!f.TYPED_ARRAY_SUPPORT)e=Math.floor(e);this[t]=e;return t+1};function C(e,t,r,n){if(t<0)t=65535+t+1;for(var i=0,o=Math.min(e.length-r,2);i>>(n?i:1-i)*8}}f.prototype.writeUInt16LE=function(e,t,r){e=+e;t=t>>>0;if(!r)A(this,e,t,2,65535,0);if(f.TYPED_ARRAY_SUPPORT){this[t]=e;this[t+1]=e>>>8}else C(this,e,t,true);return t+2};f.prototype.writeUInt16BE=function(e,t,r){e=+e;t=t>>>0;if(!r)A(this,e,t,2,65535,0);if(f.TYPED_ARRAY_SUPPORT){this[t]=e>>>8;this[t+1]=e}else C(this,e,t,false);return t+2};function k(e,t,r,n){if(t<0)t=4294967295+t+1;for(var i=0,o=Math.min(e.length-r,4);i>>(n?i:3-i)*8&255}}f.prototype.writeUInt32LE=function(e,t,r){e=+e;t=t>>>0;if(!r)A(this,e,t,4,4294967295,0);if(f.TYPED_ARRAY_SUPPORT){this[t+3]=e>>>24;this[t+2]=e>>>16;this[t+1]=e>>>8;this[t]=e}else k(this,e,t,true);return t+4};f.prototype.writeUInt32BE=function(e,t,r){e=+e;t=t>>>0;if(!r)A(this,e,t,4,4294967295,0);if(f.TYPED_ARRAY_SUPPORT){this[t]=e>>>24;this[t+1]=e>>>16;this[t+2]=e>>>8;this[t+3]=e}else k(this,e,t,false);return t+4};f.prototype.writeIntLE=function(e,t,r,n){e=+e;t=t>>>0;if(!n){A(this,e,t,r,Math.pow(2,8*r-1)-1,-Math.pow(2,8*r-1))}var i=0;var o=1;var a=e<0?1:0;this[t]=e&255;while(++i>0)-a&255;return t+r};f.prototype.writeIntBE=function(e,t,r,n){e=+e;t=t>>>0;if(!n){A(this,e,t,r,Math.pow(2,8*r-1)-1,-Math.pow(2,8*r-1))}var i=r-1;var o=1;var a=e<0?1:0;this[t+i]=e&255;while(--i>=0&&(o*=256))this[t+i]=(e/o>>0)-a&255;return t+r};f.prototype.writeInt8=function(e,t,r){e=+e;t=t>>>0;if(!r)A(this,e,t,1,127,-128);if(!f.TYPED_ARRAY_SUPPORT)e=Math.floor(e);if(e<0)e=255+e+1;this[t]=e;return t+1};f.prototype.writeInt16LE=function(e,t,r){e=+e;t=t>>>0;if(!r)A(this,e,t,2,32767,-32768);if(f.TYPED_ARRAY_SUPPORT){this[t]=e;this[t+1]=e>>>8}else C(this,e,t,true);return t+2};f.prototype.writeInt16BE=function(e,t,r){e=+e;t=t>>>0;if(!r)A(this,e,t,2,32767,-32768);if(f.TYPED_ARRAY_SUPPORT){this[t]=e>>>8;this[t+1]=e}else C(this,e,t,false);return t+2};f.prototype.writeInt32LE=function(e,t,r){e=+e;t=t>>>0;if(!r)A(this,e,t,4,2147483647,-2147483648);if(f.TYPED_ARRAY_SUPPORT){this[t]=e;this[t+1]=e>>>8;this[t+2]=e>>>16;this[t+3]=e>>>24}else k(this,e,t,true);return t+4};f.prototype.writeInt32BE=function(e,t,r){e=+e;t=t>>>0;if(!r)A(this,e,t,4,2147483647,-2147483648);if(e<0)e=4294967295+e+1;if(f.TYPED_ARRAY_SUPPORT){this[t]=e>>>24;this[t+1]=e>>>16;this[t+2]=e>>>8;this[t+3]=e}else k(this,e,t,false);return t+4};function S(e,t,r,n,i,o){if(t>i||te.length)throw new RangeError("index out of range");if(r<0)throw new RangeError("index out of range")}function R(e,t,r,n,o){if(!o)S(e,t,r,4,3.4028234663852886e38,-3.4028234663852886e38);i.write(e,t,r,n,23,4);return r+4}f.prototype.writeFloatLE=function(e,t,r){return R(this,e,t,true,r)};f.prototype.writeFloatBE=function(e,t,r){return R(this,e,t,false,r)};function T(e,t,r,n,o){if(!o)S(e,t,r,8,1.7976931348623157e308,-1.7976931348623157e308);i.write(e,t,r,n,52,8);return r+8}f.prototype.writeDoubleLE=function(e,t,r){return T(this,e,t,true,r)};f.prototype.writeDoubleBE=function(e,t,r){return T(this,e,t,false,r)};f.prototype.copy=function(e,t,r,n){var i=this;if(!r)r=0;if(!n&&n!==0)n=this.length;if(t>=e.length)t=e.length;if(!t)t=0;if(n>0&&n=i.length)throw new RangeError("sourceStart out of bounds");if(n<0)throw new RangeError("sourceEnd out of bounds");if(n>this.length)n=this.length;if(e.length-t=this.length)throw new RangeError("start out of bounds");if(r<0||r>this.length)throw new RangeError("end out of bounds");var n;if(typeof e==="number"){for(n=t;n55295&&r<57344){if(i){if(r<56320){if((t-=3)>-1)o.push(239,191,189);i=r;continue}else{r=i-55296<<10|r-56320|65536;i=null}}else{if(r>56319){if((t-=3)>-1)o.push(239,191,189);continue}else if(a+1===n){if((t-=3)>-1)o.push(239,191,189);continue}else{i=r;continue}}}else if(i){if((t-=3)>-1)o.push(239,191,189);i=null}if(r<128){if((t-=1)<0)break;o.push(r)}else if(r<2048){if((t-=2)<0)break;o.push(r>>6|192,r&63|128)}else if(r<65536){if((t-=3)<0)break;o.push(r>>12|224,r>>6&63|128,r&63|128)}else if(r<2097152){if((t-=4)<0)break;o.push(r>>18|240,r>>12&63|128,r>>6&63|128,r&63|128)}else{throw new Error("Invalid code point")}}return o}function U(e){var t=[];for(var r=0;r>8;i=r%256;o.push(i);o.push(n)}return o}function O(e){return n.toByteArray(B(e))}function D(e,t,r,n){for(var i=0;i=t.length||i>=e.length)break;t[i+r]=e[i]}return i}function Y(e){try{return decodeURIComponent(e)}catch(t){return String.fromCharCode(65533)}}},{"base64-js":3,ieee754:4,"is-array":5}],3:[function(e,t,r){var n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";(function(e){"use strict";var t=typeof Uint8Array!=="undefined"?Uint8Array:Array;var r="+".charCodeAt(0);var i="/".charCodeAt(0);var o="0".charCodeAt(0);var a="a".charCodeAt(0);var s="A".charCodeAt(0);var f="-".charCodeAt(0);var c="_".charCodeAt(0);function u(e){var t=e.charCodeAt(0);if(t===r||t===f)return 62;if(t===i||t===c)return 63;if(t0){throw new Error("Invalid string. Length must be a multiple of 4")}var f=e.length;a="="===e.charAt(f-2)?2:"="===e.charAt(f-1)?1:0;s=new t(e.length*3/4-a);i=a>0?e.length-4:e.length;var c=0;function l(e){s[c++]=e}for(r=0,n=0;r>16);l((o&65280)>>8);l(o&255)}if(a===2){o=u(e.charAt(r))<<2|u(e.charAt(r+1))>>4;l(o&255)}else if(a===1){o=u(e.charAt(r))<<10|u(e.charAt(r+1))<<4|u(e.charAt(r+2))>>2;l(o>>8&255);l(o&255)}return s}function h(e){var t,r=e.length%3,i="",o,a;function s(e){return n.charAt(e)}function f(e){return s(e>>18&63)+s(e>>12&63)+s(e>>6&63)+s(e&63)}for(t=0,a=e.length-r;t>2);i+=s(o<<4&63);i+="==";break;case 2:o=(e[e.length-2]<<8)+e[e.length-1];i+=s(o>>10);i+=s(o>>4&63);i+=s(o<<2&63);i+="=";break}return i}e.toByteArray=l;e.fromByteArray=h})(typeof r==="undefined"?this.base64js={}:r)},{}],4:[function(e,t,r){r.read=function(e,t,r,n,i){var o,a,s=i*8-n-1,f=(1<>1,u=-7,l=r?i-1:0,h=r?-1:1,d=e[t+l];l+=h;o=d&(1<<-u)-1;d>>=-u;u+=s;for(;u>0;o=o*256+e[t+l],l+=h,u-=8);a=o&(1<<-u)-1;o>>=-u;u+=n;for(;u>0;a=a*256+e[t+l],l+=h,u-=8);if(o===0){o=1-c}else if(o===f){return a?NaN:(d?-1:1)*Infinity}else{a=a+Math.pow(2,n);o=o-c}return(d?-1:1)*a*Math.pow(2,o-n)};r.write=function(e,t,r,n,i,o){var a,s,f,c=o*8-i-1,u=(1<>1,h=i===23?Math.pow(2,-24)-Math.pow(2,-77):0,d=n?0:o-1,p=n?1:-1,g=t<0||t===0&&1/t<0?1:0;t=Math.abs(t);if(isNaN(t)||t===Infinity){s=isNaN(t)?1:0;a=u}else{a=Math.floor(Math.log(t)/Math.LN2);if(t*(f=Math.pow(2,-a))<1){a--;f*=2}if(a+l>=1){t+=h/f}else{t+=h*Math.pow(2,1-l)}if(t*f>=2){a++;f/=2}if(a+l>=u){s=0;a=u}else if(a+l>=1){s=(t*f-1)*Math.pow(2,i);a=a+l}else{s=t*Math.pow(2,l-1)*Math.pow(2,i);a=0}}for(;i>=8;e[r+d]=s&255,d+=p,s/=256,i-=8);a=a<0;e[r+d]=a&255,d+=p,a/=256,c-=8);e[r+d-p]|=g*128}},{}],5:[function(e,t,r){var n=Array.isArray;var i=Object.prototype.toString;t.exports=n||function(e){return!!e&&"[object Array]"==i.call(e)}},{}],6:[function(e,t,r){function n(){this._events=this._events||{};this._maxListeners=this._maxListeners||undefined}t.exports=n;n.EventEmitter=n;n.prototype._events=undefined;n.prototype._maxListeners=undefined;n.defaultMaxListeners=10;n.prototype.setMaxListeners=function(e){if(!o(e)||e<0||isNaN(e))throw TypeError("n must be a positive number");this._maxListeners=e;return this};n.prototype.emit=function(e){var t,r,n,o,f,c;if(!this._events)this._events={};if(e==="error"){if(!this._events.error||a(this._events.error)&&!this._events.error.length){t=arguments[1];if(t instanceof Error){throw t}throw TypeError('Uncaught, unspecified "error" event.')}}r=this._events[e];if(s(r))return false;if(i(r)){switch(arguments.length){case 1:r.call(this);break;case 2:r.call(this,arguments[1]);break;case 3:r.call(this,arguments[1],arguments[2]);break;default:n=arguments.length;o=new Array(n-1);for(f=1;f0&&this._events[e].length>r){this._events[e].warned=true;console.error("(node) warning: possible EventEmitter memory "+"leak detected. %d listeners added. "+"Use emitter.setMaxListeners() to increase limit.",this._events[e].length);if(typeof console.trace==="function"){console.trace()}}}return this};n.prototype.on=n.prototype.addListener;n.prototype.once=function(e,t){if(!i(t))throw TypeError("listener must be a function");var r=false;function n(){this.removeListener(e,n);if(!r){r=true;t.apply(this,arguments)}}n.listener=t;this.on(e,n);return this};n.prototype.removeListener=function(e,t){var r,n,o,s;if(!i(t))throw TypeError("listener must be a function");if(!this._events||!this._events[e])return this;r=this._events[e];o=r.length;n=-1;if(r===t||i(r.listener)&&r.listener===t){delete this._events[e];if(this._events.removeListener)this.emit("removeListener",e,t)}else if(a(r)){for(s=o;s-->0;){if(r[s]===t||r[s].listener&&r[s].listener===t){n=s;break}}if(n<0)return this;if(r.length===1){r.length=0;delete this._events[e]}else{r.splice(n,1)}if(this._events.removeListener)this.emit("removeListener",e,t)}return this};n.prototype.removeAllListeners=function(e){var t,r;if(!this._events)return this;if(!this._events.removeListener){if(arguments.length===0)this._events={};else if(this._events[e])delete this._events[e];return this}if(arguments.length===0){for(t in this._events){if(t==="removeListener")continue;this.removeAllListeners(t)}this.removeAllListeners("removeListener");this._events={};return this}r=this._events[e];if(i(r)){this.removeListener(e,r)}else{while(r.length)this.removeListener(e,r[r.length-1])}delete this._events[e];return this};n.prototype.listeners=function(e){var t;if(!this._events||!this._events[e])t=[];else if(i(this._events[e]))t=[this._events[e]];else t=this._events[e].slice();return t};n.listenerCount=function(e,t){var r;if(!e._events||!e._events[t])r=0;else if(i(e._events[t]))r=1;else r=e._events[t].length;return r};function i(e){return typeof e==="function"}function o(e){return typeof e==="number"}function a(e){return typeof e==="object"&&e!==null}function s(e){return e===void 0}},{}],7:[function(e,t,r){if(typeof Object.create==="function"){t.exports=function n(e,t){e.super_=t;e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:false,writable:true,configurable:true}})}}else{t.exports=function i(e,t){e.super_=t;var r=function(){};r.prototype=t.prototype;e.prototype=new r;e.prototype.constructor=e}}},{}],8:[function(e,t,r){t.exports=Array.isArray||function(e){return Object.prototype.toString.call(e)=="[object Array]"}},{}],9:[function(e,t,r){var n=t.exports={};var i=[];var o=false;function a(){if(o){return}o=true;var e;var t=i.length;while(t){e=i;i=[];var r=-1;while(++r0){if(t.ended&&!i){var a=new Error("stream.push() after EOF");e.emit("error",a)}else if(t.endEmitted&&i){var a=new Error("stream.unshift() after end event");e.emit("error",a)}else{if(t.decoder&&!i&&!n)r=t.decoder.write(r);if(!i)t.reading=false;if(t.flowing&&t.length===0&&!t.sync){e.emit("data",r);e.read(0)}else{t.length+=t.objectMode?1:r.length;if(i)t.buffer.unshift(r);else t.buffer.push(r);if(t.needReadable)m(e)}_(e,t)}}else if(!i){t.reading=false}return d(t)}function d(e){return!e.ended&&(e.needReadable||e.length=p){e=p}else{e--;for(var t=1;t<32;t<<=1)e|=e>>t;e++}return e}function v(e,t){if(t.length===0&&t.ended)return 0;if(t.objectMode)return e===0?0:1;if(isNaN(e)||s.isNull(e)){if(t.flowing&&t.buffer.length)return t.buffer[0].length;else return t.length}if(e<=0)return 0;if(e>t.highWaterMark)t.highWaterMark=g(e);if(e>t.length){if(!t.ended){t.needReadable=true;return 0}else return t.length}return e}l.prototype.read=function(e){c("read",e);var t=this._readableState;var r=e;if(!s.isNumber(e)||e>0)t.emittedReadable=false;if(e===0&&t.needReadable&&(t.length>=t.highWaterMark||t.ended)){c("read: emitReadable",t.length,t.ended);if(t.length===0&&t.ended)T(this);else m(this);return null}e=v(e,t);if(e===0&&t.ended){if(t.length===0)T(this);return null}var n=t.needReadable;c("need readable",n);if(t.length===0||t.length-e0)i=R(e,t);else i=null;if(s.isNull(i)){t.needReadable=true;e=0}t.length-=e;if(t.length===0&&!t.ended)t.needReadable=true;if(r!==e&&t.ended&&t.length===0)T(this);if(!s.isNull(i))this.emit("data",i);return i};function b(e,t){var r=null;if(!s.isBuffer(t)&&!s.isString(t)&&!s.isNullOrUndefined(t)&&!e.objectMode){r=new TypeError("Invalid non-string/buffer chunk")}return r}function y(e,t){if(t.decoder&&!t.ended){var r=t.decoder.end();if(r&&r.length){t.buffer.push(r);t.length+=t.objectMode?1:r.length}}t.ended=true;m(e)}function m(e){var t=e._readableState;t.needReadable=false;if(!t.emittedReadable){c("emitReadable",t.flowing);t.emittedReadable=true;if(t.sync)r.nextTick(function(){w(e)});else w(e)}}function w(e){c("emit readable");e.emit("readable");S(e)}function _(e,t){if(!t.readingMore){t.readingMore=true;r.nextTick(function(){E(e,t)})}}function E(e,t){var r=t.length;while(!t.reading&&!t.flowing&&!t.ended&&t.length=n){if(o)s=r.join("");else s=i.concat(r,n);r.length=0}else{if(e0)throw new Error("endReadable called on non-empty stream");if(!t.endEmitted){t.ended=true;r.nextTick(function(){if(!t.endEmitted&&t.length===0){t.endEmitted=true;e.readable=false;e.emit("end")}})}}function I(e,t){for(var r=0,n=e.length;r1){var r=[];for(var n=0;n=this.charLength-this.charReceived?this.charLength-this.charReceived:e.length;e.copy(this.charBuffer,this.charReceived,0,r);this.charReceived+=r;if(this.charReceived=55296&&n<=56319){this.charLength+=this.surrogateSize;t="";continue}this.charReceived=this.charLength=0;if(e.length===0){return t}break}this.detectIncompleteChar(e);var i=e.length;if(this.charLength){e.copy(this.charBuffer,0,e.length-this.charReceived,i);i-=this.charReceived}t+=e.toString(this.encoding,0,i);var i=t.length-1;var n=t.charCodeAt(i);if(n>=55296&&n<=56319){var o=this.surrogateSize;this.charLength+=o;this.charReceived+=o;this.charBuffer.copy(this.charBuffer,o,0,o);e.copy(this.charBuffer,0,0,o);return t.substring(0,i)}return t};a.prototype.detectIncompleteChar=function(e){var t=e.length>=3?3:e.length;for(;t>0;t--){var r=e[e.length-t];if(t==1&&r>>5==6){this.charLength=2;break}if(t<=2&&r>>4==14){this.charLength=3;break}if(t<=3&&r>>3==30){this.charLength=4;break}}this.charReceived=t};a.prototype.end=function(e){var t="";if(e&&e.length)t=this.write(e);if(this.charReceived){var r=this.charReceived;var n=this.charBuffer;var i=this.encoding;t+=n.slice(0,r).toString(i)}return t};function s(e){return e.toString(this.encoding)}function f(e){this.charReceived=e.length%2;this.charLength=this.charReceived?2:0}function c(e){this.charReceived=e.length%3;this.charLength=this.charReceived?3:0}},{buffer:2}],23:[function(e,t,r){var n=[].slice;t.exports=function(e,t){if("string"==typeof t)t=e[t];if("function"!=typeof t)throw new Error("bind() requires a function");var r=n.call(arguments,2);return function(){return t.apply(e,r.concat(n.call(arguments)))}}},{}],24:[function(e,t,r){t.exports=n;function n(e){if(e)return i(e)}function i(e){for(var t in n.prototype){e[t]=n.prototype[t]}return e}n.prototype.on=n.prototype.addEventListener=function(e,t){this._callbacks=this._callbacks||{};(this._callbacks["$"+e]=this._callbacks["$"+e]||[]).push(t);return this};n.prototype.once=function(e,t){function r(){this.off(e,r);t.apply(this,arguments)}r.fn=t;this.on(e,r);return this};n.prototype.off=n.prototype.removeListener=n.prototype.removeAllListeners=n.prototype.removeEventListener=function(e,t){this._callbacks=this._callbacks||{};if(0==arguments.length){this._callbacks={};return this}var r=this._callbacks["$"+e];if(!r)return this;if(1==arguments.length){delete this._callbacks["$"+e];return this}var n;for(var i=0;i=31}r.formatters.j=function(e){return JSON.stringify(e)};function i(){var e=arguments;var t=this.useColors;e[0]=(t?"%c":"")+this.namespace+(t?" %c":" ")+e[0]+(t?"%c ":" ")+"+"+r.humanize(this.diff);if(!t)return e;var n="color: "+this.color;e=[e[0],n,"color: inherit"].concat(Array.prototype.slice.call(e,1));var i=0;var o=0;e[0].replace(/%[a-z%]/g,function(e){if("%%"===e)return;i++;if("%c"===e){o=i}});e.splice(o,0,n);return e}function o(){return"object"===typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function a(e){try{if(null==e){r.storage.removeItem("debug")}else{r.storage.debug=e}}catch(t){}}function s(){var e;try{e=r.storage.debug}catch(t){}return e}r.enable(s());function f(){try{return window.localStorage}catch(e){}}},{"./debug":26}],26:[function(e,t,r){r=t.exports=a;r.coerce=u;r.disable=f;r.enable=s;r.enabled=c;r.humanize=e("ms");r.names=[];r.skips=[];r.formatters={};var n=0;var i;function o(){return r.colors[n++%r.colors.length]}function a(e){function t(){}t.enabled=false;function n(){var e=n;var t=+new Date;var a=t-(i||t);e.diff=a;e.prev=i;e.curr=t;i=t;if(null==e.useColors)e.useColors=r.useColors();if(null==e.color&&e.useColors)e.color=o();var s=Array.prototype.slice.call(arguments);s[0]=r.coerce(s[0]);if("string"!==typeof s[0]){s=["%o"].concat(s)}var f=0;s[0]=s[0].replace(/%([a-z%])/g,function(t,n){if(t==="%%")return t;f++;var i=r.formatters[n];if("function"===typeof i){var o=s[f];t=i.call(e,o);s.splice(f,1);f--}return t});if("function"===typeof r.formatArgs){s=r.formatArgs.apply(e,s)}var c=n.log||r.log||console.log.bind(console);c.apply(e,s)}n.enabled=true;var a=r.enabled(e)?n:t;a.namespace=e;return a}function s(e){r.save(e);var t=(e||"").split(/[\s,]+/);var n=t.length;for(var i=0;i1e4)return;var t=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(e);if(!t)return;var r=parseFloat(t[1]);var f=(t[2]||"ms").toLowerCase();switch(f){case"years":case"year":case"yrs":case"yr":case"y":return r*s;case"days":case"day":case"d":return r*a;case"hours":case"hour":case"hrs":case"hr":case"h":return r*o;case"minutes":case"minute":case"mins":case"min":case"m":return r*i;case"seconds":case"second":case"secs":case"sec":case"s":return r*n;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return r}}function c(e){if(e>=a)return Math.round(e/a)+"d";if(e>=o)return Math.round(e/o)+"h";if(e>=i)return Math.round(e/i)+"m";if(e>=n)return Math.round(e/n)+"s";return e+"ms"}function u(e){return l(e,a,"day")||l(e,o,"hour")||l(e,i,"minute")||l(e,n,"second")||e+" ms"}function l(e,t,r){if(e=Math.pow(2,e)){return n(e,t)}else return a};n.rack=function(e,t,r){var i=function(i){var a=0;do{if(a++>10){if(r)e+=r;else throw new Error("too many ID collisions, use more bits")}var s=n(e,t)}while(Object.hasOwnProperty.call(o,s));o[s]=i;return s};var o=i.hats={};i.get=function(e){return i.hats[e]};i.set=function(e,t){i.hats[e]=t;return i};i.bits=e||128;i.base=t||16;return i}},{}],32:[function(e,t,r){arguments[4][7][0].apply(r,arguments)},{dup:7}],33:[function(e,t,r){(function(r){t.exports=l;var n=e("debug")("simple-peer");var i=e("get-browser-rtc");var o=e("hat");var a=e("inherits");var s=e("is-typedarray");var f=e("once");var c=e("stream");var u=e("typedarray-to-buffer");a(l,c.Duplex);function l(e){var t=this;if(!(t instanceof l))return new l(e);t._debug("new peer %o",e);if(!e)e={};e.allowHalfOpen=false;if(e.highWaterMark==null)e.highWaterMark=1024*1024;c.Duplex.call(t,e);t.initiator=e.initiator||false;t.channelConfig=e.channelConfig||l.channelConfig;t.channelName=e.channelName||o(160);if(!e.initiator)t.channelName=null;t.config=e.config||l.config;t.constraints=e.constraints||l.constraints;t.reconnectTimer=e.reconnectTimer||0;t.sdpTransform=e.sdpTransform||function(e){return e};t.stream=e.stream||false;t.trickle=e.trickle!==undefined?e.trickle:true;t.destroyed=false;t.connected=false;t.remoteAddress=undefined;t.remoteFamily=undefined;t.remotePort=undefined;t.localAddress=undefined;t.localPort=undefined;t._wrtc=e.wrtc||i();if(!t._wrtc){if(typeof window==="undefined"){throw new Error("No WebRTC support: Specify `opts.wrtc` option in this environment")}else{throw new Error("No WebRTC support: Not a supported browser")}}t._maxBufferedAmount=e.highWaterMark;t._pcReady=false;t._channelReady=false;t._iceComplete=false;t._channel=null;t._pendingCandidates=[];t._chunk=null;t._cb=null;t._interval=null;t._reconnectTimeout=null;t._pc=new t._wrtc.RTCPeerConnection(t.config,t.constraints);t._pc.oniceconnectionstatechange=t._onIceConnectionStateChange.bind(t);t._pc.onsignalingstatechange=t._onSignalingStateChange.bind(t);t._pc.onicecandidate=t._onIceCandidate.bind(t);if(t.stream)t._pc.addStream(t.stream);t._pc.onaddstream=t._onAddStream.bind(t);if(t.initiator){t._setupData({channel:t._pc.createDataChannel(t.channelName,t.channelConfig)});t._pc.onnegotiationneeded=f(t._createOffer.bind(t));if(typeof window==="undefined"||!window.webkitRTCPeerConnection){t._pc.onnegotiationneeded()}}else{t._pc.ondatachannel=t._setupData.bind(t)}t.on("finish",function(){if(t.connected){setTimeout(function(){t._destroy()},100)}else{t.once("connect",function(){setTimeout(function(){t._destroy()},100)})}})}l.WEBRTC_SUPPORT=!!i();l.config={iceServers:[{url:"stun:23.21.150.121",urls:"stun:23.21.150.121"}]};l.constraints={};l.channelConfig={};Object.defineProperty(l.prototype,"bufferSize",{get:function(){var e=this;return e._channel&&e._channel.bufferedAmount||0}});l.prototype.address=function(){var e=this;return{port:e.localPort,family:"IPv4",address:e.localAddress}};l.prototype.signal=function(e){var t=this;if(t.destroyed)throw new Error("cannot signal after peer is destroyed");if(typeof e==="string"){try{e=JSON.parse(e)}catch(r){e={}}}t._debug("signal()");function n(e){try{t._pc.addIceCandidate(new t._wrtc.RTCIceCandidate(e),h,t._onError.bind(t))}catch(r){t._destroy(new Error("error adding candidate: "+r.message))}}if(e.sdp){t._pc.setRemoteDescription(new t._wrtc.RTCSessionDescription(e),function(){if(t.destroyed)return;if(t._pc.remoteDescription.type==="offer")t._createAnswer();t._pendingCandidates.forEach(n);t._pendingCandidates=[]},t._onError.bind(t))}if(e.candidate){if(t._pc.remoteDescription)n(e.candidate);else t._pendingCandidates.push(e.candidate)}if(!e.sdp&&!e.candidate){t._destroy(new Error("signal() called with invalid signal data"))}};l.prototype.send=function(e){var t=this;if(!s.strict(e)&&!(e instanceof ArrayBuffer)&&!r.isBuffer(e)&&typeof e!=="string"&&(typeof Blob==="undefined"||!(e instanceof Blob))){e=JSON.stringify(e)}if(r.isBuffer(e)&&!s.strict(e)){e=new Uint8Array(e)}var n=e.length||e.byteLength||e.size;t._channel.send(e);t._debug("write: %d bytes",n)};l.prototype.destroy=function(e){var t=this;t._destroy(null,e)};l.prototype._destroy=function(e,t){var r=this;if(r.destroyed)return;if(t)r.once("close",t);r._debug("destroy (error: %s)",e&&e.message);r.readable=r.writable=false;if(!r._readableState.ended)r.push(null);if(!r._writableState.finished)r.end();r.destroyed=true;r.connected=false;r._pcReady=false;r._channelReady=false;r._chunk=null;r._cb=null;clearInterval(r._interval);clearTimeout(r._reconnectTimeout);if(r._pc){try{r._pc.close()}catch(e){}r._pc.oniceconnectionstatechange=null;r._pc.onsignalingstatechange=null;r._pc.onicecandidate=null}if(r._channel){try{r._channel.close()}catch(e){}r._channel.onmessage=null;r._channel.onopen=null;r._channel.onclose=null}r._pc=null;r._channel=null;if(e)r.emit("error",e);r.emit("close")};l.prototype._setupData=function(e){var t=this;t._channel=e.channel;t.channelName=t._channel.label;t._channel.binaryType="arraybuffer";t._channel.onmessage=t._onChannelMessage.bind(t);t._channel.onopen=t._onChannelOpen.bind(t);t._channel.onclose=t._onChannelClose.bind(t)};l.prototype._read=function(){};l.prototype._write=function(e,t,r){var n=this;if(n.destroyed)return r(new Error("cannot write after peer is destroyed"));if(n.connected){try{n.send(e)}catch(i){return n._onError(i)}if(n._channel.bufferedAmount>n._maxBufferedAmount){n._debug("start backpressure: bufferedAmount %d",n._channel.bufferedAmount);n._cb=r}else{r(null)}}else{n._debug("write before connect");n._chunk=e;n._cb=r}};l.prototype._createOffer=function(){var e=this;if(e.destroyed)return;e._pc.createOffer(function(t){if(e.destroyed)return;t.sdp=e.sdpTransform(t.sdp);e._pc.setLocalDescription(t,h,e._onError.bind(e));var r=function(){var r=e._pc.localDescription||t;e._debug("signal");e.emit("signal",{type:r.type,sdp:r.sdp})};if(e.trickle||e._iceComplete)r();else e.once("_iceComplete",r)},e._onError.bind(e),e.offerConstraints)};l.prototype._createAnswer=function(){var e=this;if(e.destroyed)return;e._pc.createAnswer(function(t){if(e.destroyed)return;t.sdp=e.sdpTransform(t.sdp);e._pc.setLocalDescription(t,h,e._onError.bind(e));var r=function(){var r=e._pc.localDescription||t;e._debug("signal");e.emit("signal",{type:r.type,sdp:r.sdp})};if(e.trickle||e._iceComplete)r();else e.once("_iceComplete",r)},e._onError.bind(e),e.answerConstraints)};l.prototype._onIceConnectionStateChange=function(){var e=this;if(e.destroyed)return;var t=e._pc.iceGatheringState;var r=e._pc.iceConnectionState;e._debug("iceConnectionStateChange %s %s",t,r);e.emit("iceConnectionStateChange",t,r);if(r==="connected"||r==="completed"){clearTimeout(e._reconnectTimeout);e._pcReady=true;e._maybeReady()}if(r==="disconnected"){if(e.reconnectTimer){clearTimeout(e._reconnectTimeout);e._reconnectTimeout=setTimeout(function(){e._destroy()},e.reconnectTimer)}else{e._destroy()}}if(r==="closed"){e._destroy()}};l.prototype._maybeReady=function(){var e=this;e._debug("maybeReady pc %s channel %s",e._pcReady,e._channelReady);if(e.connected||e._connecting||!e._pcReady||!e._channelReady)return;e._connecting=true;if(typeof window!=="undefined"&&!!window.mozRTCPeerConnection){e._pc.getStats(null,function(e){var r=[];e.forEach(function(e){r.push(e)});t(r)},e._onError.bind(e))}else{e._pc.getStats(function(e){var r=[];e.result().forEach(function(e){var t={};e.names().forEach(function(r){t[r]=e.stat(r)});t.id=e.id;t.type=e.type;t.timestamp=e.timestamp;r.push(t)});t(r)})}function t(t){t.forEach(function(t){if(t.type==="remotecandidate"){e.remoteAddress=t.ipAddress;e.remoteFamily="IPv4";e.remotePort=Number(t.portNumber);e._debug("connect remote: %s:%s (%s)",e.remoteAddress,e.remotePort,e.remoteFamily)}else if(t.type==="localcandidate"&&t.candidateType==="host"){e.localAddress=t.ipAddress;e.localPort=Number(t.portNumber);e._debug("connect local: %s:%s",e.localAddress,e.localPort)}});e._connecting=false;e.connected=true;if(e._chunk){try{e.send(e._chunk)}catch(r){return e._onError(r)}e._chunk=null;e._debug('sent chunk from "write before connect"');var n=e._cb;e._cb=null;n(null)}e._interval=setInterval(function(){if(!e._cb||!e._channel||e._channel.bufferedAmount>e._maxBufferedAmount)return;e._debug("ending backpressure: bufferedAmount %d",e._channel.bufferedAmount);var t=e._cb;e._cb=null;t(null)},150);if(e._interval.unref)e._interval.unref();e._debug("connect");e.emit("connect")}};l.prototype._onSignalingStateChange=function(){var e=this;if(e.destroyed)return;e._debug("signalingStateChange %s",e._pc.signalingState);e.emit("signalingStateChange",e._pc.signalingState)};l.prototype._onIceCandidate=function(e){var t=this;if(t.destroyed)return;if(e.candidate&&t.trickle){t.emit("signal",{candidate:{candidate:e.candidate.candidate,sdpMLineIndex:e.candidate.sdpMLineIndex,sdpMid:e.candidate.sdpMid}})}else if(!e.candidate){t._iceComplete=true;t.emit("_iceComplete")}};l.prototype._onChannelMessage=function(e){var t=this;if(t.destroyed)return;var r=e.data;t._debug("read: %d bytes",r.byteLength||r.length);if(r instanceof ArrayBuffer){r=u(new Uint8Array(r));t.push(r)}else{try{r=JSON.parse(r)}catch(n){}t.emit("data",r)}};l.prototype._onChannelOpen=function(){var e=this;if(e.connected||e.destroyed)return;e._debug("on channel open");e._channelReady=true;e._maybeReady(); + +};l.prototype._onChannelClose=function(){var e=this;if(e.destroyed)return;e._debug("on channel close");e._destroy()};l.prototype._onAddStream=function(e){var t=this;if(t.destroyed)return;t._debug("on add stream");t.emit("stream",e.stream)};l.prototype._onError=function(e){var t=this;if(t.destroyed)return;t._debug("error %s",e.message||e);t._destroy(e)};l.prototype._debug=function(){var e=this;var t=[].slice.call(arguments);var r=e.channelName&&e.channelName.substring(0,7);t[0]="["+r+"] "+t[0];n.apply(null,t)};function h(){}}).call(this,e("buffer").Buffer)},{buffer:2,debug:25,"get-browser-rtc":34,hat:31,inherits:32,"is-typedarray":35,once:37,stream:21,"typedarray-to-buffer":38}],34:[function(e,t,r){t.exports=function n(){if(typeof window==="undefined")return null;var e={RTCPeerConnection:window.mozRTCPeerConnection||window.RTCPeerConnection||window.webkitRTCPeerConnection,RTCSessionDescription:window.mozRTCSessionDescription||window.RTCSessionDescription||window.webkitRTCSessionDescription,RTCIceCandidate:window.mozRTCIceCandidate||window.RTCIceCandidate||window.webkitRTCIceCandidate};if(!e.RTCPeerConnection)return null;return e}},{}],35:[function(e,t,r){t.exports=o;o.strict=a;o.loose=s;var n=Object.prototype.toString;var i={"[object Int8Array]":true,"[object Int16Array]":true,"[object Int32Array]":true,"[object Uint8Array]":true,"[object Uint8ClampedArray]":true,"[object Uint16Array]":true,"[object Uint32Array]":true,"[object Float32Array]":true,"[object Float64Array]":true};function o(e){return a(e)||s(e)}function a(e){return e instanceof Int8Array||e instanceof Int16Array||e instanceof Int32Array||e instanceof Uint8Array||e instanceof Uint8ClampedArray||e instanceof Uint16Array||e instanceof Uint32Array||e instanceof Float32Array||e instanceof Float64Array}function s(e){return i[n.call(e)]}},{}],36:[function(e,t,r){t.exports=n;function n(e,t){if(e&&t)return n(e)(t);if(typeof e!=="function")throw new TypeError("need wrapper function");Object.keys(e).forEach(function(t){r[t]=e[t]});return r;function r(){var t=new Array(arguments.length);for(var r=0;r=n)return(e/n).toFixed(1)+"h";if(e>=r)return(e/r).toFixed(1)+"m";if(e>=t)return(e/t|0)+"s";return e+"ms"};n.enabled=function(e){for(var t=0,r=n.skips.length;t1)))/4)-w((e-1901+t)/100)+w((e-1601+t)/400)}}if(!(i={}.hasOwnProperty)){i=function(e){var t={},n;if((t.__proto__=null,t.__proto__={toString:1},t).toString!=r){i=function(e){var t=this.__proto__,r=e in(this.__proto__=null,this);this.__proto__=t;return r}}else{n=t.constructor;i=function(e){var t=(this.constructor||n).prototype;return e in this&&!(e in t&&this[e]===t[e])}}t=null;return i.call(this,e)}}var A={"boolean":1,number:1,string:1,undefined:1};var C=function(e,t){var r=typeof e[t];return r=="object"?!!e[t]:!A[r]};o=function(e,t){var n=0,a,s,f;(a=function(){this.valueOf=0}).prototype.valueOf=0;s=new a;for(f in s){if(i.call(s,f)){n++}}a=s=null;if(!n){s=["valueOf","toString","toLocaleString","propertyIsEnumerable","isPrototypeOf","hasOwnProperty","constructor"];o=function(e,t){var n=r.call(e)==d,o,a;var f=!n&&typeof e.constructor!="function"&&C(e,"hasOwnProperty")?e.hasOwnProperty:i;for(o in e){if(!(n&&o=="prototype")&&f.call(e,o)){t(o)}}for(a=s.length;o=s[--a];f.call(e,o)&&t(o));}}else if(n==2){o=function(e,t){var n={},o=r.call(e)==d,a;for(a in e){if(!(o&&a=="prototype")&&!i.call(n,a)&&(n[a]=1)&&i.call(e,a)){t(a)}}}}else{o=function(e,t){var n=r.call(e)==d,o,a;for(o in e){if(!(n&&o=="prototype")&&i.call(e,o)&&!(a=o==="constructor")){t(o)}}if(a||i.call(e,o="constructor")){t(o)}}}return o(e,t)};if(!h("json-stringify")){var k={92:"\\\\",34:'\\"',8:"\\b",12:"\\f",10:"\\n",13:"\\r",9:"\\t"};var S="000000";var R=function(e,t){return(S+(t||0)).slice(-e)};var T="\\u00";var I=function(e){var t='"',r=0,n=e.length,i=n>10&&m,o;if(i){o=e.split("")}for(;r-1/0&&l<1/0){if(E){_=w(l/864e5);for(d=w(_/365.2425)+1970-1;E(d+1,0)<=_;d++);for(m=w((_-E(d,0))/30.42);E(d,m+1)<=_;m++);_=1+_-E(d,m);A=(l%864e5+864e5)%864e5;C=w(A/36e5)%24;k=w(A/6e4)%60;S=w(A/1e3)%60;T=A%1e3}else{d=l.getUTCFullYear();m=l.getUTCMonth();_=l.getUTCDate();C=l.getUTCHours();k=l.getUTCMinutes();S=l.getUTCSeconds();T=l.getUTCMilliseconds()}l=(d<=0||d>=1e4?(d<0?"-":"+")+R(6,d<0?-d:d):R(4,d))+"-"+R(2,m+1)+"-"+R(2,_)+"T"+R(2,C)+":"+R(2,k)+":"+R(2,S)+"."+R(3,T)+"Z"}else{l=null}}else if(typeof l.toJSON=="function"&&(h!=g&&h!=v&&h!=b||i.call(l,"toJSON"))){l=l.toJSON(e)}}if(n){l=n.call(t,e,l)}if(l===null){return"null"}h=r.call(l);if(h==y){return""+l}else if(h==g){return l>-1/0&&l<1/0?""+l:"null"}else if(h==v){return I(""+l)}if(typeof l=="object"){for(x=u.length;x--;){if(u[x]===l){throw TypeError()}}u.push(l);B=[];j=c;c+=f;if(h==b){for(P=0,x=l.length;P0){for(i="",n>10&&(n=10);i.length=48&&a<=57||a>=97&&a<=102||a>=65&&a<=70)){j()}}r+=B("0x"+e.slice(n,P));break;default:j()}}else{if(a==34){break}a=e.charCodeAt(P);n=P;while(a>=32&&a!=92&&a!=34){a=e.charCodeAt(++P)}r+=e.slice(n,P)}}if(e.charCodeAt(P)==34){P++;return r}j();default:n=P;if(a==45){o=true;a=e.charCodeAt(++P)}if(a>=48&&a<=57){if(a==48&&(a=e.charCodeAt(P+1),a>=48&&a<=57)){j()}o=false;for(;P=48&&a<=57);P++);if(e.charCodeAt(P)==46){i=++P;for(;i=48&&a<=57);i++);if(i==P){j()}P=i}a=e.charCodeAt(P);if(a==101||a==69){a=e.charCodeAt(++P);if(a==43||a==45){P++}for(i=P;i=48&&a<=57);i++);if(i==P){j()}P=i}return+e.slice(n,P)}if(o){j()}if(e.slice(P,P+4)=="true"){P+=4;return true}else if(e.slice(P,P+5)=="false"){P+=5;return false}else if(e.slice(P,P+4)=="null"){P+=4;return null}j()}}return"$"};var N=function(e){var t,r;if(e=="$"){j()}if(typeof e=="string"){if((m?e.charAt(0):e[0])=="@"){return e.slice(1)}if(e=="["){t=[];for(;;r||(r=true)){e=U();if(e=="]"){break}if(r){if(e==","){e=U();if(e=="]"){j()}}else{j()}}if(e==","){j()}t.push(N(e))}return t}else if(e=="{"){t={};for(;;r||(r=true)){e=U();if(e=="}"){break}if(r){if(e==","){e=U();if(e=="}"){j()}}else{j()}}if(e==","||typeof e!="string"||(m?e.charAt(0):e[0])!="@"||U()!=":"){j()}t[e.slice(1)]=N(U())}return t}j()}return e};var O=function(e,t,r){var n=D(e,t,r);if(n===a){delete e[t]}else{e[t]=n}};var D=function(e,t,n){var i=e[t],a;if(typeof i=="object"&&i){if(r.call(i)==b){for(a=i.length;a--;){O(i,a,n)}}else{o(i,function(e){O(i,e,n)})}}return n.call(e,t,i)};c.parse=function(e,t){var n,i;P=0;x=""+e;n=N(U());if(U()!="$"){j()}P=x=null;return t&&r.call(t)==d?D((i={},i[""]=n,i),"",t):n}}}if(s){e(function(){return c})}})(this)},{}],46:[function(e,t,r){t.exports=n;function n(e,t){var r=[];t=t||0;for(var n=t||0;n=26||n==="moz"&&i>=33);var u=window.AudioContext||window.webkitAudioContext;var l=document.createElement("video");var h=l&&l.canPlayType&&l.canPlayType('video/webm; codecs="vp8", vorbis')==="probably";var d=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.msGetUserMedia||navigator.mozGetUserMedia;t.exports={prefix:n,browserVersion:i,support:!!o&&h&&!!d,supportRTCPeerConnection:!!o,supportVp8:h,supportGetUserMedia:!!d,supportDataChannel:!!(o&&o.prototype&&o.prototype.createDataChannel),supportWebAudio:!!(u&&u.prototype.createMediaStreamSource),supportMediaStream:!!(f&&f.prototype.removeTrack),supportScreenSharing:!!c,AudioContext:u,PeerConnection:o,SessionDescription:s,IceCandidate:a,MediaStream:f,getUserMedia:d}},{}],"/":[function(e,t,r){window.myDebug=e("debug");var n=e("simple-peer");var i=e("component-emitter");var o=e("socket.io-parser");var a=e("to-array");var s=e("has-binary");var f=e("component-bind");var c=e("debug")("socket");var u=e("hat");var l=e("extend.js");var h=e("webrtcsupport");var d=i.prototype.emit;function p(e,t,r){var i=this;i.useSockets=true;i.usePeerConnection=false;i.decoder=new o.Decoder(this);i.decoder.on("decoded",f(this,this.ondecoded));i.socket=e;i.cb=r;i._peers={};i.readyPeers=0;i.ready=false;i._peerEvents={upgrade:1,error:1,peer_signal:1,peer_ready:1,stream:1};var a={autoUpgrade:true,numClients:5};i.opts=l(a,t||{});i.peerOpts=i.opts.peerOpts||{};i.numConnectedClients;e.on("numClients",function(t){i.peerId=e.io.engine.id;i.numConnectedClients=t;if(h.supportDataChannel){r(function(t){var r={offers:t,fromPeerId:i.peerId};e.emit("offers",r)})}function r(e){var t=[];for(var r=0;r=i.numConnectedClients&&!i.ready){i.ready=true;if(i.opts.autoUpgrade)i.usePeerConnection=true;if(typeof i.cb==="function")i.cb();i.emit("upgrade")}})}i(p.prototype);p.prototype.setupPeerEvents=function(e){var t=this;e.on("connect",function(e){t.emit("peer_ready",e)});e.on("data",function(e){if(this.destroyed)return;t.decoder.add(e)});e.on("stream",function(e){t.emit("stream",e)})};p.prototype.on=function(e,t){var r=this;this.socket.addEventListener(e,function(t){d.call(r,e,t)});this.addEventListener(e,t)};p.prototype.emit=function(e,t){var r=this;var n=t||{};var i=new o.Encoder;if(this._peerEvents.hasOwnProperty(e)||n.fromSocket){d.apply(this,arguments)}else if(this.usePeerConnection||!this.useSockets){var f=a(arguments);var c=o.EVENT;if(s(f)){c=o.BINARY_EVENT}var u={type:c,data:f};i.encode(u,function(e){if(e[1]instanceof ArrayBuffer){r._sendArray(e)}else if(e){for(var t=0;t { + if (game) + if (!game.winId) + game.onMouseDown(); +} + +window.onresize = () => { + let w = $('#canvas-holder').width(); + let h = $('#canvas-holder').height(); + p.resizeCanvas(w, h); +} \ No newline at end of file diff --git a/data/scripts/game.js b/public/data/scripts/game.js similarity index 85% rename from data/scripts/game.js rename to public/data/scripts/game.js index 4f7039c..18c0324 100644 --- a/data/scripts/game.js +++ b/public/data/scripts/game.js @@ -17,7 +17,7 @@ function readGameSettings(){ for (let i = 0; i < maxIndex * 0.1; i++){ let index; do{ - index = floor(random(0, maxIndex)); + index = Math.floor(p.random(0, maxIndex)); } while (indices.find(i => i === index) != null); indices.push(index); } @@ -66,17 +66,17 @@ class Game{ } get winCount(){ - return round(this.fields.filter(f => !f.isEmpty).length * 0.7); + return Math.round(this.fields.filter(f => !f.isEmpty).length * 0.7); } //Width and height of game field get size(){ - return Math.min(width, height - this.leaderboard.height); + return Math.min(p.width, p.height - this.leaderboard.height); } //Upper left corner of game field get pos(){ - return {x: (width - this.size) / 2, y: (height + this.leaderboard.height - this.size) / 2}; + return {x: (p.width - this.size) / 2, y: (p.height + this.leaderboard.height - this.size) / 2}; } get hasTurn(){ @@ -123,8 +123,8 @@ class Game{ setPlayerHues(colors){ for (let key in colors){ this.playerHues[key] = colors[key]; - let graphics = createGraphics(100, 100); - graphics.colorMode(HSB); + let graphics = p.createGraphics(100, 100); + graphics.colorMode(p.HSB); graphics.tint(colors[key], 100, 100); graphics.image(gemContentImage, 0, 0, 100, 100); gemContentGraphics[colors[key]] = graphics; @@ -304,8 +304,8 @@ class Field{ } get isMouseOver(){ - return mouseX > this.pos.x && mouseX < this.pos.x + this.size - && mouseY > this.pos.y && mouseY < this.pos.y + this.size; + return p.mouseX > this.pos.x && p.mouseX < this.pos.x + this.size + && p.mouseY > this.pos.y && p.mouseY < this.pos.y + this.size; } get isNeutral(){ @@ -326,9 +326,9 @@ class Field{ setup(allFields){ if (!this.isEmpty){ - for (let angle = 0; angle < TWO_PI; angle += PI / 2){ - let x = round(sin(angle)); - let y = round(cos(angle)); + for (let angle = 0; angle < Math.PI * 2; angle += Math.PI / 2){ + let x = Math.round(Math.sin(angle)); + let y = Math.round(Math.cos(angle)); if (this.getPartner(x, y, allFields)) this.slots.push(new Slot(x, y)); } @@ -347,19 +347,19 @@ class Field{ display(){ if (!this.isEmpty){ - noStroke(); - fill(0, 0, 5); + p.noStroke(); + p.fill(0, 0, 5); let size = this.size / 3; - rect(this.pos.x + size, this.pos.y + size, size, size); + p.rect(this.pos.x + size, this.pos.y + size, size, size); } this.slots.forEach(s => s.display()); let s = this.isNeutral ? 0 : 50; - stroke(100); - strokeWeight(1); - fill(game.playerHues[this.ownerId], s, 60, 0.3); - rect(this.pos.x, this.pos.y, this.size, this.size); + p.stroke(100); + p.strokeWeight(1); + p.fill(game.playerHues[this.ownerId], s, 60, 0.3); + p.rect(this.pos.x, this.pos.y, this.size, this.size); } onMouseDown(){ @@ -389,15 +389,15 @@ class Slot{ } get isMouseOver(){ - return mouseX > this.pos.x && mouseX < this.pos.x + this.size - && mouseY > this.pos.y && mouseY < this.pos.y + this.size; + return p.mouseX > this.pos.x && p.mouseX < this.pos.x + this.size + && p.mouseY > this.pos.y && p.mouseY < this.pos.y + this.size; } get color(){ let h = game.playerHues[this.field.ownerId]; let s = this.isFilled ? 100 : 0; let b = this.isMouseOver && !this.isFilled ? 90 : 70; - return color(h, s, b) + return p.color(h, s, b); } get partner(){ @@ -414,24 +414,24 @@ class Slot{ display(){ - fill(this.color); - noStroke(); - rect(this.pos.x, this.pos.y, this.size, this.size); + p.fill(this.color); + p.noStroke(); + p.rect(this.pos.x, this.pos.y, this.size, this.size); if (this.isFilled){ - stroke(0, 0, 0); - strokeWeight(3); + p.stroke(0, 0, 0); + p.strokeWeight(3); let x = this.pos.x + this.size / 2; let y = this.pos.y + this.size / 2; - line(this.pos.x, y, this.pos.x + this.size, y); - line(x, this.pos.y, x, this.pos.y + this.size); + p.line(this.pos.x, y, this.pos.x + this.size, y); + p.line(x, this.pos.y, x, this.pos.y + this.size); } if (this.isHighlighted){ - stroke(0); - fill(0); + p.stroke(0); + p.fill(0); let hs = this.size / 2; - ellipse(this.pos.x + hs, this.pos.y + hs, hs, hs); + p.ellipse(this.pos.x + hs, this.pos.y + hs, hs, hs); } } @@ -461,13 +461,13 @@ class Spread{ get startPos(){ let x = this.slot.field.pos.x + this.slot.size; let y = this.slot.field.pos.y + this.slot.size; - return createVector(x, y); + return p.createVector(x, y); } get endPos(){ let x = this.slot.partner.field.pos.x + this.slot.size; let y = this.slot.partner.field.pos.y + this.slot.size; - return createVector(x, y); + return p.createVector(x, y); } get pos(){ @@ -481,26 +481,26 @@ class Spread{ display(){ - noStroke(); + p.noStroke(); let x = this.pos.x + this.size / 2; let y = this.pos.y + this.size / 2; - fill(0, 0, 0); - ellipse(x, y, this.size * 0.9, this.size * 0.9); + p.fill(0, 0, 0); + p.ellipse(x, y, this.size * 0.9, this.size * 0.9); - let c = frameCount % 10 >= 5 && !this.moving ? color(0, 0, 0) : this.color; - fill(c); - ellipse(x, y, this.size * 0.5, this.size * 0.5); + let c = p.frameCount % 10 >= 5 && !this.moving ? p.color(0, 0, 0) : this.color; + p.fill(c); + p.ellipse(x, y, this.size * 0.5, this.size * 0.5); } update(){ - this.waitTime += 1 / frameRate(); + this.waitTime += 1 / p.frameRate(); if (this.waitTime >= 0.2 && !this.moving){ this.moving = true; this.slot.isFilled = false; this.slot.field.ownerId = 'neutral'; } if (this.moving){ - this.moveProgress += 1.5 / frameRate(); + this.moveProgress += 1.5 / p.frameRate(); } if (this.moveProgress >= 1) this.fillEndSlot(); @@ -556,14 +556,14 @@ class Countdown{ display(){ if (!this.isFinished){ - let panelW = (width - game.size) / 2; - stroke(200); - fill(150); - textSize(panelW / 4); + let panelW = (p.width - game.size) / 2; + p.stroke(200); + p.fill(150); + p.textSize(panelW / 4); let x = panelW / 2; let y = game.pos.y + game.size / 2; - let rounded = floor(this.time); - text(rounded, x, y); + let rounded = Math.floor(this.time); + p.text(rounded, x, y); } } diff --git a/public/data/scripts/init.js b/public/data/scripts/init.js new file mode 100644 index 0000000..d9c9d99 --- /dev/null +++ b/public/data/scripts/init.js @@ -0,0 +1,100 @@ +'use strict'; + +let projectName = "chainreact"; + +let debug = false, + productionMode = false, + font, + localSettings, + loader; + +//Only for online games +let socket; + +let game; +let gemContentImage; +let gemBorderImage; +let gemContentGraphics = {}; + +let antiCacheQuery = '?_=' + new Date().getTime(); + +let p; + +p = new p5(s => { + s.preload = () => { + localSettings = p.loadJSON('data/settings/settings.json' + antiCacheQuery, json => { + console.log('Local settings loaded: ', json); + }, error => { + console.log('Local settings failed: ', error); + }); + + font = p.loadFont('data/styles/Tajawal/Tajawal-Regular.ttf', json => { + console.log('Local font loaded: ', json); + }, error => { + console.log('Local font failed: ', error); + }); + + gemContentImage = p.loadImage('data/images/gem_content.png', img => { + console.log('Image loaded: ', img); + }, error => { + console.log('Image failed: ', error); + }); + + gemBorderImage = p.loadImage('data/images/gem_border.png', img => { + console.log('Image loaded: ', img); + }, error => { + console.log('Image failed: ', error); + }); + + p.loadJSON('data/settings/libraries.json' + antiCacheQuery, json => { + loadScripts(json) + console.log('BenjoCraeft library scripts loaded: ', json) + }); + }; + + s.setup = () => { + canvasSetup(); + interfaceSetup(); + } + + s.draw = () => { + p.background(0, 0, 10); + if (game){ + game.display(); + game.update(); + } + } + + }, null, null); + +function canvasSetup(){ + p.setFrameRate(60); + let w = $('#canvas-holder').width(), + h = $('#canvas-holder').height(); + let canvas = p.createCanvas(w, h); + canvas.parent('canvas-holder'); + p.textFont(font); + p.textAlign(p.CENTER, p.CENTER); + p.imageMode(p.CENTER); + p.colorMode(p.HSB); +} + +function interfaceSetup(){ + window.onresize(); + setInterval(() => window.onresize(), 500); + $('#start_feedback, #give_feedback').attr('disabled', 'disabled'); + nameTyped($('#main > input')); + + $('#main').fadeIn(menuesFadeTime); +} + +function loadScripts(libs){ + for (let script in libs){ + if (libs[script]){ + let url = '/lib/benjocraeft/' + script + '.js' + $.getScript(url, () => { + console.log('Successfully loaded script: ', url) + }); + } + } +} \ No newline at end of file diff --git a/data/scripts/leaderboard.js b/public/data/scripts/leaderboard.js similarity index 84% rename from data/scripts/leaderboard.js rename to public/data/scripts/leaderboard.js index 5551d3a..ca36f99 100644 --- a/data/scripts/leaderboard.js +++ b/public/data/scripts/leaderboard.js @@ -5,7 +5,7 @@ class Leaderboard{ } get pos(){ - return createVector(game.pos.x, 0); + return p.createVector(game.pos.x, 0); } get width(){ @@ -41,14 +41,14 @@ class Bar{ return game.size / game.winCount; } get marginY(){ - return height * 0.005; + return p.height * 0.005; } get pos(){ let lb = game.leaderboard; let x = lb.pos.x + this.marginX; let y = lb.pos.y + this.marginY + lb.bars.findIndex(b => b.id === this.id) * (this.height + this.marginY); - return createVector(x, y); + return p.createVector(x, y); } get height(){ @@ -67,11 +67,11 @@ class Bar{ let filled = game.fields.filter(f => f.ownerId === this.id).length; for (let i = 0; i < count; i++){ let x = i * this.width / count + this.width / count / 2 + this.pos.x; - image(gemBorderImage, x, y, imageSize, imageSize); + p.image(gemBorderImage, x, y, imageSize, imageSize); } for (let i = 0; i < filled; i++){ let x = i * this.width / count + this.width / count / 2 + this.pos.x; - image(gemContentGraphics[game.playerHues[this.id]], x, y, imageSize, imageSize); + p.image(gemContentGraphics[game.playerHues[this.id]], x, y, imageSize, imageSize); } } diff --git a/data/scripts/online.js b/public/data/scripts/online.js similarity index 99% rename from data/scripts/online.js rename to public/data/scripts/online.js index 33e1602..e1c232c 100644 --- a/data/scripts/online.js +++ b/public/data/scripts/online.js @@ -24,7 +24,7 @@ function socketConnect(project, name = playerName){ connecting = true; let urlQueries = '?game=' + project.name + '&name=' + name; - $.get('/php/get_nodejs_port.php', port => { + $.get('data/settings/get_port.php', port => { let url = 'https://' + location.hostname + ':' + port + urlQueries; socket = io.connect(url); @@ -217,7 +217,7 @@ function serverStartGame(dom){ } function startGame(room, seed){ - randomSeed(seed); + p.randomSeed(seed); buildGame(room); console.log('Game started'); socket.emit('ready-for-turn'); diff --git a/public/data/settings/get_port.php b/public/data/settings/get_port.php new file mode 100644 index 0000000..8de971e --- /dev/null +++ b/public/data/settings/get_port.php @@ -0,0 +1,3 @@ + - - - - - - - - + + + + diff --git a/styles.css b/public/styles.css similarity index 100% rename from styles.css rename to public/styles.css diff --git a/public/thumbnail.png b/public/thumbnail.png new file mode 100644 index 0000000..182ed7b Binary files /dev/null and b/public/thumbnail.png differ diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..97b842c --- /dev/null +++ b/server/.gitignore @@ -0,0 +1,3 @@ +out +logs +node_modules \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json new file mode 100644 index 0000000..d090b32 --- /dev/null +++ b/server/package-lock.json @@ -0,0 +1,439 @@ +{ + "name": "chainreact-server", + "version": "2.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "chainreact-server", + "version": "2.0", + "dependencies": { + "dotenv": "^16.0.3", + "https": "^1.0.0", + "socket.io": "^4.4.1", + "typescript": "^5.0.2" + }, + "devDependencies": { + "@types/node": "^18.15.3" + } + }, + "base": { + "name": "game-server", + "version": "2.0", + "extraneous": true, + "dependencies": { + "dotenv": "^16.0.3", + "https": "^1.0.0", + "ini": "^2.0.0", + "socket.io": "^4.4.1" + }, + "devDependencies": { + "@types/node": "^17.0.18", + "typescript": "^4.8.4" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "18.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz", + "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/engine.io": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.1.tgz", + "integrity": "sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", + "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/https": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz", + "integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/socket.io": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", + "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.4.1", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dependencies": { + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", + "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/typescript": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", + "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + }, + "dependencies": { + "@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "18.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz", + "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==" + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" + }, + "engine.io": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.1.tgz", + "integrity": "sha512-JFYQurD/nbsA5BSPmbaOSLa3tSVj8L6o4srSwXXY3NqE+gGUNmmPTbhn8tjzcCtSqhFgIeqef81ngny8JM25hw==", + "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.11.0" + } + }, + "engine.io-parser": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", + "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==" + }, + "https": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz", + "integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "socket.io": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.1.tgz", + "integrity": "sha512-KMcaAi4l/8+xEjkRICl6ak8ySoxsYG+gG6/XfRCPJPQ/haCRIJBTL4wIl8YCsmtaBovcAXGLOShyVWQ/FG8GZA==", + "requires": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.4.1", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.1" + } + }, + "socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "requires": { + "ws": "~8.11.0" + } + }, + "socket.io-parser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", + "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + } + }, + "typescript": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", + "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "requires": {} + } + } +} diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..e4f5b4a --- /dev/null +++ b/server/package.json @@ -0,0 +1,18 @@ +{ + "name": "chainreact-server", + "version": "2.0", + "private": true, + "scripts": { + "start": "node out/index.js", + "tsc": "npx tsc" + }, + "dependencies": { + "dotenv": "^16.0.3", + "https": "^1.0.0", + "socket.io": "^4.4.1", + "typescript": "^5.0.2" + }, + "devDependencies": { + "@types/node": "^18.15.3" + } +} diff --git a/server/src/chainreact.ts b/server/src/chainreact.ts new file mode 100644 index 0000000..986e1ea --- /dev/null +++ b/server/src/chainreact.ts @@ -0,0 +1,104 @@ +import {ServerGame} from "./game_standard"; +import {Client} from "./client"; +import {Room} from "./room"; + +export class Chainreact extends ServerGame { + + readyForTurn: Client[]; + currentTurnIndex: number; + currentGameData: any; + colorHues: { [id: string]: number }; + + constructor(room: Room, settings: Settings.Global) { + super(room, settings); + this.readyForTurn = [] + } + + setEvents(client: Client) { + let socket = client.socket; + socket.on('ready-for-turn', isDead => { + if (isDead) { + client.isPlayer = false; + client.isSpectator = true; + this.room.toAll('client-list', this.room.clients) + } else { + this.readyForTurn.push(client) + } + + let allReady = true; + this.room.players.forEach(c => { + if (this.readyForTurn.find(r => r.id === c.id) == null) { + allReady = false + } + }); + if (allReady) { + this.nextTurn(); + this.readyForTurn = [] + } + }); + socket.on('set-slot', (fieldsIndex: number, slotsIndex: number) => { + this.room.toAll('set-slot', fieldsIndex, slotsIndex, socket.id) + }); + socket.on('game-data', data => this.currentGameData = data) + } + + addClient(client: Client): void { + super.addClient(client); + if (client.isSpectator) { + let room = this.room; + let data = this.currentGameData; + let hues = this.colorHues; + let turnId = ''; + if (this.room.players[this.currentTurnIndex]) + turnId = this.room.players[this.currentTurnIndex].id; + client.send('start-spectate', room, data, hues, turnId) + } + } + + removeClient(client: Client): void { + super.removeClient(client); + if (this.room.players.indexOf(client) === this.currentTurnIndex) + this.nextTurn(true); + + let s = client.socket; + s.removeAllListeners('set-slot'); + s.removeAllListeners('ready-for-turn'); + s.removeAllListeners('game-data') + } + + nextTurn(skip?: boolean) { + if (this.currentTurnIndex != null && !skip) { + this.currentTurnIndex++; + if (this.currentTurnIndex >= this.room.players.length) { + this.currentTurnIndex = 0 + } + } else if (!skip) { + this.setTurnAndColors() + } + let index = this.currentTurnIndex; + if (skip) { + index = this.currentTurnIndex + 1; + if (index >= this.room.players.length) { + index = 0; + this.currentTurnIndex = 0 + } + } + if (this.room.players.length) { + this.room.toAll('current-turn', this.room.players[index].id) + } + } + + setTurnAndColors() { + this.currentTurnIndex = Math.floor(Math.random() * this.room.players.length); + let colorHues = [0, 60, 120, 240]; + this.colorHues = {}; + for (let c of this.room.players) { + let index = Math.floor(Math.random() * colorHues.length); + let hue = colorHues[index]; + colorHues.splice(index, 1); + this.colorHues[c.id] = hue + } + this.room.toAll('player-colors', this.colorHues) + } + +} \ No newline at end of file diff --git a/server/src/client.ts b/server/src/client.ts new file mode 100644 index 0000000..873e6a2 --- /dev/null +++ b/server/src/client.ts @@ -0,0 +1,168 @@ +import {Room} from "./room.js" +import {ConnectionManager, serializeObject} from "./manager.js" +import {log} from "./logger.js"; +import * as SocketIO from "socket.io"; + +export class Client { + + socket: SocketIO.Socket; + name: string; + game: string; + id: string; + isReady: boolean; + isPlayer: boolean; + isSpectator: boolean; + + constructor(socket: SocketIO.Socket, manager: ConnectionManager) { + this.socket = socket; + // @ts-ignore + this.name = socket.handshake.query.name; + // @ts-ignore + this.game = socket.handshake.query.game; + this.id = socket.id; + this.setEvents(manager) + } + + get serialized(): Serialized.Client { + return { + id: this.id, + name: this.name, + game: this.game, + isReady: this.isReady, + isPlayer: this.isPlayer, + isSpectator: this.isSpectator + }; + } + + setEvents(mng: ConnectionManager): void { + let s = this.socket; + s.on('room-list', () => this.sendRoomList()); + s.on('client-list', () => this.sendClientList()); + s.on('set-ready', ready => this.setReady(ready)); + s.on('game-settings', settings => this.setGameSettings(settings)); + s.on('create-lobby', (settings, name) => this.createRoom(settings, name)); + s.on('join-lobby', roomId => this.joinRoom(roomId)); + s.on('leave-lobby', roomId => this.leaveRoom(roomId)); + s.on('join-spectators', () => this.joinSpectators()); + s.on('join-players', () => this.joinPlayers()); + s.on('start-game', lobbyId => mng.startGame(this, lobbyId)); + s.on('stop-game', lobbyId => mng.stopGame(this, lobbyId)); + s.on('feedback', content => mng.saveFeedbackToFile(this, content)); + s.on('disconnect', () => mng.disconnected(this)); + + this.send('connected') + } + + sendRoomList(): void { + let rooms = ConnectionManager.RoomListByGame(this.game); + this.send('room-list', rooms) + } + + sendClientList(): void { + let clients = ConnectionManager.ClientListByClientId(this.id); + this.send('client-list', clients) + } + + setReady(ready: boolean): void { + let room = Room.getByClientId(this.id, ConnectionManager.Instance.rooms); + + if (room) { + this.isReady = ready; + room.toAll('client-list', room.clients) + } + } + + setGameSettings(settings: any): void { + let room = Room.getByClientId(this.id, ConnectionManager.Instance.rooms); + + if (room) { + room.gameSettings = settings; + room.toAll('game-settings', settings) + } + } + + createRoom(settings: Settings.Global, name: string): void { + let room = ConnectionManager.Instance.createRoom(settings, name); + + room.add(this); + this.send('created-lobby', room); + + log('lobby-created', this, room) + } + + joinRoom(roomId: string): Room { + let room = Room.getByRoomId(roomId, ConnectionManager.Instance.rooms); + + if (!room) { + this.send('join-failed', 'Room does not exist!'); + log('join-non-existent', this, new Room('not-existent', roomId)) + } else if (room.hasStarted && !room.settings.spectators) { + this.send('join-failed', 'Game has started yet!'); + log('join-started', this, room) + } else { + room.add(this); + log('member-joined', this, room) + } + return room + } + + leaveRoom(_roomId: string): void { + let room = Room.getByClientId(this.id, ConnectionManager.Instance.rooms); + + if (!room) + return; + + this.leave(room.id); + if (room.runningGame) + room.runningGame.removeClient(this); + room.clients.splice(room.clients.indexOf(this), 1); + room.toAll('member-left', this.id, this.name); + room.toAll('client-list', room.clients); + + this.send('left-lobby'); + + log('member-left', this, room); + + if (room.isEmpty && !room.settings.always) { + ConnectionManager.Instance.deleteRoom(room) + } + } + + joinSpectators() { + let room = Room.getByClientId(this.id, ConnectionManager.Instance.rooms); + if (!room) + return; + + this.isSpectator = true; + this.isPlayer = false; + + room.toAll('client-list', room.clients) + } + + joinPlayers() { + let room = Room.getByClientId(this.id, ConnectionManager.Instance.rooms); + if (!room) + return; + + if (room.hasStarted) + return; + + this.isSpectator = false; + this.isPlayer = true; + + room.toAll('client-list', room.clients) + } + + send(event: string, ...args: any[]): void { + this.socket.emit(event, ...serializeObject(args)) + } + + join(roomId: string): void { + this.socket.join(roomId) + } + + leave(roomId: string): void { + this.socket.leave(roomId) + } + +} \ No newline at end of file diff --git a/server/src/definitions/serialized.d.ts b/server/src/definitions/serialized.d.ts new file mode 100644 index 0000000..b7b6a04 --- /dev/null +++ b/server/src/definitions/serialized.d.ts @@ -0,0 +1,20 @@ +declare namespace Serialized { + + interface Lobby { + id: string + name: string + game: string + clientCounts: number[] + clients: Client[] + hasStarted: boolean + } + + interface Client { + id: string + name: string + game: string + isReady: boolean + isPlayer: boolean + isSpectator: boolean + } +} \ No newline at end of file diff --git a/server/src/definitions/settings.d.ts b/server/src/definitions/settings.d.ts new file mode 100644 index 0000000..0d73fef --- /dev/null +++ b/server/src/definitions/settings.d.ts @@ -0,0 +1,74 @@ +declare module Settings { + interface Global { + project: Project + frameWork: FrameWork + game: any + always: boolean + spectators: boolean + } + + interface Project { + name: string + author: string + playerCounts: number[] + } + + interface FrameWork { + frameRate: number + updateRate: number + width: number + height: number + } + + interface Game { + ball: Ball + player: Player + cw: number + ch: number + } + + interface Ball { + radius: number + velocity: number + acceleration: number + runUp: Ball.RunUp + color: Color + cw: number + ch: number + } + + interface Player { + width: number + height: number + margin: number + points: number + normal: State + weakened: State + enhanced: State + cw: number + ch: number + } + + interface Color { + stroke: string + fill: string + } + + interface State { + vel: Vector + color: Color + moveMargin: number + } + + interface Vector { + x: number + y: number + } + + module Ball { + interface RunUp { + min: number + max: number + } + } +} \ No newline at end of file diff --git a/server/src/game_standard.ts b/server/src/game_standard.ts new file mode 100644 index 0000000..33b4ca8 --- /dev/null +++ b/server/src/game_standard.ts @@ -0,0 +1,37 @@ +import {Room} from "./room.js" +import {Client} from "./client.js" + +export class ServerGame { + + room: Room; + settings: Settings.Global; + game: any; + + constructor(room: Room, settings: Settings.Global) { + this.settings = settings; + this.room = room; + this.room.clients.forEach(c => this.addClient(c)) + } + + addClient(client: Client): void { + this.setEvents(client) + } + + removeClient(client: Client): void { + this.removeEvents(client) + } + + gameAction(action: string, ...args: any[]): void { + } + + setEvents(client: Client): void { + let socket = client.socket; + socket.on('game-action', (action, ...args) => this.gameAction(action, ...args)) + } + + removeEvents(client: Client): void { + let socket = client.socket; + socket.removeAllListeners('game-action') + } + +} \ No newline at end of file diff --git a/server/src/index.ts b/server/src/index.ts new file mode 100644 index 0000000..8288048 --- /dev/null +++ b/server/src/index.ts @@ -0,0 +1,7 @@ +import {Chainreact} from "./chainreact"; +import {StartServer} from "./start"; + +StartServer({ + useP2P: false, + gameClass: Chainreact +}); \ No newline at end of file diff --git a/server/src/logger.ts b/server/src/logger.ts new file mode 100644 index 0000000..2b4e936 --- /dev/null +++ b/server/src/logger.ts @@ -0,0 +1,105 @@ +import {Room} from "./room.js" +import {Client} from "./client.js" + +import * as fs from "fs"; +import * as util from "util"; + +let logFolder = "./logs"; + +if (!fs.existsSync(logFolder)) { + fs.mkdirSync(logFolder); +} + +let logFile = fs.createWriteStream(logFolder + '/' + new Date().getTime() + '.log', {flags: 'a'}); +let logStdout = process.stdout; + +console.log = function () { + logFile.write(util.format.apply(null, arguments) + '\n'); + logStdout.write(util.format.apply(null, arguments) + '\n'); +}; + +console.error = console.log; + +process.on('uncaughtException', err => { + console.error('Uncaught error: ', err); + process.exit(1); +}); + +process.stdin.pipe(logFile); + + +export function log(type: string, client: Client, lobby?: Room, msg?: string) { + let now = new Date(Date.now()).toString(), message, name, game; + let date = '[' + now.substring(0, now.indexOf('GMT') - 1) + ']'; + + if (client) { + game = '[' + client.game + ']'; + let short = client.id.substring(0, Math.round(client.id.length / 3)); + name = '"' + client.name + '(' + short + '...)"'; + } else { + if (type === 'lobby-deleted') { + game = '[' + lobby.gameName + ']'; + } else { + game = '[undefined]'; + } + name = 'UNKNOWN'; + } + if (lobby) { + game = '[' + lobby.gameName + ']'; + } + switch (type) { + case 'join-non-existent': + message = name + ' tried to join non-existent lobby "' + lobby.id + '"'; + break; + case 'join-started': + message = name + ' tried to join the started game "' + lobby.id + '"'; + break; + case 'lobby-created': + message = name + ' created new lobby: "' + lobby.id + '"'; + break; + case 'game-started': + message = name + ' started the game: "' + lobby.id + '"'; + break; + case 'game-stopped': + message = name + ' stopped the game: "' + lobby.id + '"'; + break; + case 'member-joined': + message = name + ' joined the lobby "' + lobby.id + '"'; + break; + case 'member-left': + message = name + ' left the lobby "' + lobby.id + '"'; + break; + case 'lobby-deleted': + message = 'Lobby "' + lobby.id + '" was deleted'; + break; + case 'save-success': + message = msg; + break; + case 'save-error': + message = 'Failed to save contents to file: ' + msg; + break; + case 'load-success': + message = 'Successfully loaded and parsed file contents'; + break; + case 'load-error': + message = 'Failed to load file: ' + msg; + break; + case 'parse-error': + message = 'Failed to parse contents: ' + msg; + break; + case 'feedback': + message = 'Saved feedback to file: ' + msg; + break; + case 'connection': + message = name + ' connected'; + break; + case 'disconnection': + message = name + ' disconnected'; + break; + case 'startup': + message = msg; + break; + } + + console.log(date + game + ' ---> {' + message + '}'); +} \ No newline at end of file diff --git a/server/src/manager.ts b/server/src/manager.ts new file mode 100644 index 0000000..a15fabf --- /dev/null +++ b/server/src/manager.ts @@ -0,0 +1,148 @@ +import {Room} from "./room.js" +import {Client} from "./client.js" +import {log} from "./logger.js" +import * as fs from "fs"; +import * as SocketIO from "socket.io" + +export class ConnectionManager { + + static Instance: ConnectionManager; + io: SocketIO.Server; + rooms: Room[]; + + constructor(io: SocketIO.Server) { + ConnectionManager.Instance = this; + + this.io = io; + this.rooms = []; + + /*let drawSettings = { + project: { + name: 'global-draw', + playerCounts: null + }, + always: true, + spectators: true + }; + let drawRoom = this.createRoom(drawSettings, ''); + drawRoom.id = 'global-draw-room'; + drawRoom.startGame(); + this.rooms.push(drawRoom);*/ + } + + static RoomListByGame(game: string): Room[] { + return this.Instance.rooms.filter(l => l.gameName === game) + } + + static ClientListByClientId(clientId: string): Client[] { + let room = Room.getByClientId(clientId, this.Instance.rooms); + + return room.clients + } + + newSocket(socket: SocketIO.Socket): void { + let client = new Client(socket, this); + log('connection', client) + } + + roomListUpdate(): void { + this.io.sockets.emit('room-list', serializeObject(this.rooms)) + } + + createRoom(settings: Settings.Global | any, name: string): Room { + let roomId = Room.generateCode(10); + let room = new Room(name, roomId, settings, this.io); + + this.rooms.push(room); + this.roomListUpdate(); + + return room + } + + deleteRoom(room: Room): void { + this.rooms.splice(this.rooms.indexOf(room), 1); + this.roomListUpdate(); + + log('lobby-deleted', null, room) + } + + //Starts the game of a room with given id + startGame(client: Client, _roomId: string): void { + let lobby = Room.getByClientId(client.id, this.rooms); + if (!lobby) return; + + if (!lobby.hasStarted) { + lobby.startGame(); + log('game-started', client, lobby) + } + + this.io.sockets.emit('room-list', serializeObject(this.rooms)) + } + + //Stops the game of a lobby with given id + stopGame(client: Client, lobbyId: string): void { + let lobby = Room.getByRoomId(lobbyId, this.rooms); + if (!lobby) return; + + lobby.stopGame(client); + log('game-stopped', client, lobby) + } + + + //Saves user feedback to a file + saveFeedbackToFile(client: Client, content: string): void { + let date = new Date(Date.now()).toString(); + let path = "feedback/" + client.game + '.txt'; + let saveToFile = (content: string) => { + fs.writeFile(path, content, (err: any) => { + if (err) + log('save-error', client, null, err.message); + else + log('feedback', client, null, path) + }); + }; + if (fs.existsSync(path)) { + fs.readFile(path, 'utf8', (err, data) => { + if (err) + log('load-error', client, null, err.message); + else { + log('load-success', client, null); + let newContent = data + '\n\n\n\n' + date + '\n\n' + content; + saveToFile(newContent) + } + }) + } else { + saveToFile(date + '\n' + content) + } + } + + //Removes a disconnected client from all references + disconnected(client: Client): void { + let room = Room.getByClientId(client.id, this.rooms); + + if (room) + client.leaveRoom(room.id); + + log('disconnection', client) + } + +} + +export function serializeObject(object: any): any { + function serialize(obj: any) { + if (!obj) + return obj; + if (obj.serialized) + return obj.serialized; + else if (obj instanceof Array) { + let content = []; + obj.forEach(o => { + content.push(serialize(o)) + }); + return content + } + return obj + } + + return serialize(object) +} \ No newline at end of file diff --git a/server/src/room.ts b/server/src/room.ts new file mode 100644 index 0000000..8510b03 --- /dev/null +++ b/server/src/room.ts @@ -0,0 +1,135 @@ +import {Client} from "./client.js" +import {ServerGame} from "./game_standard.js" +import {serializeObject} from "./manager.js"; +import {Server} from "socket.io"; + +export class Room { + + id: string; + gameName: string; + clientCounts: number[]; + io: Server; + clients: Client[]; + runningGame: ServerGame; + settings: Settings.Global; + gameSettings: any; + name: string; + + static GameClass: typeof ServerGame + + constructor(name: string, id: string, settings?: Settings.Global, io?: Server) { + this.id = id; + this.name = name; + if (!io || !settings) return; + this.settings = settings; + this.gameName = settings.project.name; + this.clientCounts = settings.project.playerCounts; + this.io = io; + this.clients = []; + this.gameSettings = {} + } + + get leader(): Client { + return this.players[0] + } + + get players(): Client[] { + return this.clients.filter(c => c.isPlayer) + } + + get spectators(): Client[] { + return this.clients.filter(c => c.isSpectator) + } + + get serialized(): Serialized.Lobby { + return { + id: this.id, + name: this.name, + game: this.gameName, + clientCounts: this.clientCounts, + clients: serializeObject(this.clients), + hasStarted: this.hasStarted + }; + } + + get isEmpty(): boolean { + return !(this.clients.length) + } + + get hasStarted(): boolean { + return this.runningGame != null + } + + static getByRoomId(id: string, lobbies: Room[]): Room { + for (let l of lobbies) { + if (l.id === id) + return l + } + return null; + } + + static getByClientId(id: string, lobbies: Room[]): Room { + for (let l of lobbies) { + for (let c of l.clients) { + if (c.id === id) + return l + } + } + return null; + } + + static generateCode(elements: number): string { + let code = ''; + let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + while (elements--) { + code += possible.charAt(Math.floor(Math.random() * possible.length)) + } + return code + } + + startGame(): void { + let seed = Math.random() * 10000; + this.toAll('start-game', seed); + this.runGame() + } + + stopGame(client: Client): void { + this.toAll('stop-game', client); + this.runningGame = null + } + + add(client: Client): void { + this.clients.push(client); + + let isPlayer = !this.hasStarted && this.hasValidPlayerCount(); + client.isPlayer = isPlayer; + client.isSpectator = !isPlayer; + client.isReady = false; + client.join(this.id); + + this.toAll('member-joined', client.id, client.name); + this.toAll('client-list', this.clients); + this.toAll('game-settings', this.gameSettings); + + if (this.hasStarted) + this.runningGame.addClient(client) + } + + hasValidPlayerCount(): boolean { + let valid = false; + this.clientCounts.forEach(c => { + if (c === this.clients.length) + valid = true + }); + return valid + } + + runGame(): void { + this.runningGame = new Room.GameClass(this, this.settings); + } + + toAll(event: string, ...args: any[]): void { + this.io.to(this.id).emit(event, serializeObject(this), ...serializeObject(args)) + } + +} \ No newline at end of file diff --git a/server/src/start.ts b/server/src/start.ts new file mode 100644 index 0000000..82293ed --- /dev/null +++ b/server/src/start.ts @@ -0,0 +1,39 @@ +import {ConnectionManager} from "./manager.js"; +import {log} from "./logger.js"; + +import {Server} from 'socket.io'; +import {Room} from "./room.js"; +import * as https from "https"; +import * as fs from "fs"; + +export function StartServer(settings: any){ + + require("dotenv").config(); + const httpsPort = parseInt(process.env.HTTPS_PORT); + + let cert = fs.readFileSync(`${process.env.SSL_PATH}/cert.pem`); + let key = fs.readFileSync(`${process.env.SSL_PATH}/key.pem`); + + let httpsServer = https.createServer({key: key, cert: cert}); + + let sIO = new Server(httpsServer, { + cors: { + origin: ["https://play.benjamin-kraft.local", "https://play.benjamin-kraft.eu"] + } + }); + if (settings.useP2P){ + const p2p = require('socket.io-p2p-server').Server; + sIO.use(p2p); + } + + httpsServer.listen(httpsPort); + + Room.GameClass = settings.gameClass; + + let connectionManager = new ConnectionManager(sIO); + + // On new connection + sIO.on('connection', socket => connectionManager.newSocket(socket)); + + log('startup', null, null, 'Server is listening on port ' + httpsPort); +} \ No newline at end of file diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 0000000..3be4fdb --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "CommonJS", + "outDir": "./out", + "sourceMap": true, + "alwaysStrict": true + }, + "include": [ + "./src" + ] +} \ No newline at end of file