diff --git a/build/public/assets/index.421011a7.css b/build/public/assets/index.421011a7.css new file mode 100644 index 0000000..ede76bf --- /dev/null +++ b/build/public/assets/index.421011a7.css @@ -0,0 +1 @@ +:root{--main-color:#c1b19f;--link-color:#808db0;--link-hover-color:#c5cfeb;--highlight-color:#dd7e7e;--input-bg-color:#262523;--bg-color:rgba(0,0,0,.7)}body,html{margin:0;background:#2b2b2b;color:var(--main-color);height:100%}*{font-family:monospace;font-size:15px}h1,h2,h3,h4{font-size:20px}a{color:var(--link-color);text-decoration:none}a:hover{color:var(--link-hover-color)}td,th{vertical-align:top}.btn{display:inline-block;background:var(--input-bg-color);color:var(--link-color);border:solid 1px #000;padding:5px 10px;box-shadow:1px 1px 2px rgba(0,0,0,.5),0 0 1px rgba(150,150,150,.4) inset;border-radius:4px;user-select:none}.btn:hover{background:#2f2e2c;color:var(--link-hover-color);border:solid 1px #111;box-shadow:0 0 1px rgba(150,150,150,.4) inset;cursor:pointer}.btn:disabled{background:#2f2e2c;color:#8c4747!important;border:solid 1px #111;box-shadow:0 0 1px rgba(150,150,150,.4) inset;cursor:not-allowed}input{background:#333230;border-radius:4px;color:var(--main-color);padding:6px 10px;border:solid 1px #000;box-shadow:0 0 3px rgba(0,0,0,.3) inset}input:focus{border:solid 1px #686767;background:var(--input-bg-color)}.scores{position:absolute;right:0;top:0;background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7)}.timer{position:absolute;left:0;top:0;background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7)}.menu{position:absolute;top:0;left:50%;transform:translateX(-50%);background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7);z-index:2}.closed{display:none}.overlay{position:absolute;top:0;left:0;right:0;bottom:0;z-index:10;background:var(--bg-color)}.overlay.transparent{background:0 0}.overlay-content{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7);z-index:1}.connection-lost .overlay-content{padding:20px;text-align:center}.preview{position:absolute;top:20px;left:20px;bottom:20px;right:20px}.preview .img{height:100%;width:100%;position:absolute;background-repeat:no-repeat;background-position:center;background-size:contain}.menu .opener{display:inline-block;margin-right:10px;color:var(--link-color)}.menu .opener:last-child{margin-right:0}.menu .opener:hover{color:var(--link-hover-color);cursor:pointer}canvas.loaded{cursor:none}kbd{background-color:#eee;border-radius:3px;border:1px solid #b4b4b4;box-shadow:0 1px 1px rgba(0,0,0,.2),0 2px 0 0 rgba(255,255,255,.7) inset;color:#333;display:inline-block;font-size:.85em;font-weight:700;line-height:1;padding:2px 4px;white-space:nowrap}.nav{list-style:none;padding:0}.nav li{display:inline-block;margin-right:1em}.image-list{overflow:scroll}.image-list-inner{white-space:nowrap}.imageteaser{width:150px;height:100px;display:inline-block;margin:5px;background-size:contain;background-position:center;background-repeat:no-repeat;background-color:#222;cursor:pointer}.game-teaser-wrap{display:inline-block;width:20%;padding:5px;box-sizing:border-box}.game-teaser{display:block;background-repeat:no-repeat;background-position:center;background-size:contain;position:relative;padding-top:56.25%;width:100%;background-color:#222}.game-info{position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%}.game-info-text{position:absolute;top:0;background:var(--bg-color);padding:5px}.game-replay{position:absolute;top:0;right:0}html.view-game{overflow:hidden}html.view-game body{overflow:hidden}html.view-replay{overflow:hidden}html.view-replay body{overflow:hidden}html.view-replay canvas{cursor:grab} \ No newline at end of file diff --git a/build/public/assets/index.4c0f0f73.js b/build/public/assets/index.4c0f0f73.js new file mode 100644 index 0000000..1cb9a9a --- /dev/null +++ b/build/public/assets/index.4c0f0f73.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as o,b as l,r as i,o as s,e as a,t as r,F as d,f as c,g as u,v as p,h,i as g,j as y,k as m,l as f,m as w,n as v}from"./vendor.00b608ff.js";var x=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const b={id:"app"},A={key:0,class:"nav"},z=a("Index"),T=a("New game");x.render=function(e,a,r,d,c,u){const p=i("router-link"),h=i("router-view");return s(),t("div",b,[e.showNav?(s(),t("ul",A,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[z])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[T])),_:1})])])):l("",!0),n(h)])};const S=864e5,C=e=>{const t=Math.floor(e/S);e%=S;const n=Math.floor(e/36e5);e%=36e5;const o=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${o}m ${Math.floor(e/1e3)}s`};var k=1e3,I=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},P=(e,t)=>C(t-e),_=C,D=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",o=e,l=t||I();return`${n} ${P(o,l)}`}}});const B={class:"game-info-text"},O=n("br",null,null,-1),E=n("br",null,null,-1),M=n("br",null,null,-1);D.render=function(e,d,c,u,p,h){const g=i("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(g,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",B,[a(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),O,a(" πŸ‘₯ "+r(e.game.players),1),E,a(" "+r(e.time(e.game.started,e.game.finished)),1),M])])),_:1},8,["to"]),l("",!0)],4)};var N=e({components:{GameTeaser:D},data:()=>({gamesRunning:[],gamesFinished:[]}),async created(){const e=await fetch("/api/index-data"),t=await e.json();this.gamesRunning=t.gamesRunning,this.gamesFinished=t.gamesFinished}});const U=n("h1",null,"Running games",-1),R=n("h1",null,"Finished games",-1);function $(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function G(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}N.render=function(e,o,l,a,r,u){const p=i("game-teaser");return s(),t("div",null,[U,(s(!0),t(d,null,c(e.gamesRunning,((e,o)=>(s(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128)),R,(s(!0),t(d,null,c(e.gamesFinished,((e,o)=>(s(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128))])};var V={pointSub:function(e,t){return{x:e.x-t.x,y:e.y-t.y}},pointAdd:function(e,t){return{x:e.x+t.x,y:e.y+t.y}},pointDistance:$,pointInBounds:function(e,t){return e.x>=t.x&&e.x<=t.x+t.w&&e.y>=t.y&&e.y<=t.y+t.h},rectCenter:G,rectMoved:function(e,t,n){return{x:e.x+t,y:e.y+n,w:e.w,h:e.h}},rectCenterDistance:function(e,t){return $(G(e),G(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};var j=1,W=4,F=5,H=2,L=3,Q=6,Y=2,q=4,Z=3,K=9,J=1,X=2,ee=3,te=4,ne=5,oe=6,le=7,ie=8,se=10,ae=1,re=2,de=3;class ce{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){return this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295,e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new ce(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const ue=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},pe=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=ue(o.getHours(),"00"),i=ue(o.getMinutes(),"00"),s=ue(o.getSeconds(),"00");console[t](`${l}:${i}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}},he=(e,t,n)=>e.random(t,n);var ge={hash:e=>{let t=0;for(let n=0;nDate.now().toString(36)+Math.random().toString(36).substring(2),randomInt:he,choice:(e,t)=>t[he(e,0,t.length-1)],shuffle:(e,t)=>{const n=t.slice();for(let o=0;o<=n.length-2;o++){const t=he(e,o,n.length-1),l=n[o];n[o]=n[t],n[t]=l}return n},encodeShape:function(e){return"number"==typeof e?e:e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return"number"!=typeof e?e:{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodeTile:function(e){return Array.isArray(e)?e:[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodeTile:function(e){return Array.isArray(e)?{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}:e},encodePlayer:function(e){return Array.isArray(e)?e:[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return Array.isArray(e)?{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}:e},encodeGame:function(e){return Array.isArray(e)?e:[e.id,e.rng.type,ce.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode]},decodeGame:function(e){return Array.isArray(e)?{id:e[0],rng:{type:e[1],obj:ce.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}:e},coordByTileIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}}};const ye={};function me(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}function fe(e,t){let n=0;for(let o of ye[e].players){if(ge.decodePlayer(o).id===t)return n;n++}return-1}function we(e,t){let n=fe(e,t);return ge.decodePlayer(ye[e].players[n])}function ve(e,t,n){let o=fe(e,t);-1===o?ye[e].players.push(ge.encodePlayer(n)):ye[e].players[o]=ge.encodePlayer(n)}function xe(e,t){return-1!==fe(e,t)}function be(e){return ye[e]?ye[e].players.map(ge.decodePlayer):[]}function Ae(e){return ye[e].puzzle.tiles.length}function ze(e){return ye[e].scoreMode||0}function Te(e){return Se(e)===Ae(e)}function Se(e){let t=0;for(let n of ye[e].puzzle.tiles)-1===ge.decodeTile(n).owner&&t++;return t}function Ce(e,t,n){const o=we(e,t);for(let l of Object.keys(n))o[l]=n[l];ve(e,t,o)}function ke(e,t){for(let n of Object.keys(t))ye[e].puzzle.data[n]=t[n]}function Ie(e,t,n){for(let o of Object.keys(n)){const l=ge.decodeTile(ye[e].puzzle.tiles[t]);l[o]=n[o],ye[e].puzzle.tiles[t]=ge.encodeTile(l)}}const Pe=(e,t)=>ge.decodeTile(ye[e].puzzle.tiles[t]),_e=(e,t)=>Pe(e,t).group,De=(e,t)=>{const n=ye[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=ye[e].puzzle.info,o=ge.coordByTileIdx(n,t),l=o.x*n.tileSize,i=o.y*n.tileSize;return{x:l,y:i}}(e,t);return V.pointAdd(o,l)},Be=(e,t)=>Pe(e,t).pos,Oe=e=>{const t=Ye(e),n=qe(e),o=Math.round(t/4),l=Math.round(n/4);return{x:0-o,y:0-l,w:t+2*o,h:n+2*l}},Ee=(e,t)=>{const n=Re(e),o=Pe(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Me=(e,t)=>Pe(e,t).z,Ne=(e,t)=>{for(let n of ye[e].puzzle.tiles){const e=ge.decodeTile(n);if(e.owner===t)return e.idx}return-1},Ue=e=>ye[e].puzzle.info.tileDrawSize,Re=e=>ye[e].puzzle.info.tileSize,$e=e=>ye[e].puzzle.data.maxGroup,Ge=e=>ye[e].puzzle.data.maxZ;function Ve(e,t){const n=ye[e].puzzle.info,o=ge.coordByTileIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const je=(e,t,n)=>{for(let o of t)Ie(e,o,{z:n})},We=(e,t,n)=>{const o=Be(e,t);Ie(e,t,{pos:V.pointAdd(o,n)})},Fe=(e,t,n)=>{const o=Ue(e),l=Oe(e),i=n;for(let s of t){const t=Pe(e,s);t.pos.x+n.xl.x+l.w&&(i.x=Math.min(l.x+l.w-t.pos.x+o,i.x)),t.pos.y+n.yl.y+l.h&&(i.y=Math.min(l.y+l.h-t.pos.y+o,i.y))}for(let s of t)We(e,s,i)},He=(e,t,n)=>{for(let o of t)Ie(e,o,{owner:n})};function Le(e,t){const n=ye[e].puzzle.tiles,o=ge.decodeTile(n[t]),l=[];if(o.group)for(let i of n){const e=ge.decodeTile(i);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const Qe=(e,t)=>{const n=we(e,t);return n?n.points:null},Ye=e=>ye[e].puzzle.info.table.width,qe=e=>ye[e].puzzle.info.table.height;var Ze={__createPlayerObject:me,setGame:function(e,t){ye[e]=t},exists:function(e){return!!ye[e]||!1},playerExists:xe,getActivePlayers:function(e,t){const n=t-30*k;return be(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*k;return be(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){xe(e,t)?Ce(e,t,{ts:n}):ve(e,t,me(t,n))},getFinishedTileCount:Se,getTileCount:Ae,getImageUrl:function(e){return ye[e].puzzle.info.imageUrl},setImageUrl:function(e,t){ye[e].puzzle.info.imageUrl=t},get:function(e){return ye[e]},getAllGames:function(){return Object.values(ye).sort(((e,t)=>Te(e.id)===Te(t.id)?t.puzzle.data.started-e.puzzle.data.started:Te(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=we(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=we(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=we(e,t);return n?n.name:null},getPlayerIndexById:fe,getPlayerIdByIndex:function(e,t){return ye[e].players.length>t?ge.decodePlayer(ye[e].players[t]).id:null},changePlayer:Ce,setPlayer:ve,setTile:function(e,t,n){ye[e].puzzle.tiles[t]=ge.encodeTile(n)},setPuzzleData:function(e,t){ye[e].puzzle.data=t},getTableWidth:Ye,getTableHeight:qe,getPuzzle:e=>ye[e].puzzle,getRng:e=>ye[e].rng.obj,getPuzzleWidth:e=>ye[e].puzzle.info.width,getPuzzleHeight:e=>ye[e].puzzle.info.height,getTilesSortedByZIndex:function(e){return ye[e].puzzle.tiles.map(ge.decodeTile).sort(((e,t)=>e.z-t.z))},getFirstOwnedTile:(e,t)=>{const n=Ne(e,t);return n<0?null:ye[e].puzzle.tiles[n]},getTileDrawOffset:e=>ye[e].puzzle.info.tileDrawOffset,getTileDrawSize:Ue,getFinalTilePos:De,getStartTs:e=>ye[e].puzzle.data.started,getFinishTs:e=>ye[e].puzzle.data.finished,handleInput:function(e,t,n,o){const l=ye[e].puzzle,i=function(e,t){return t in ye[e].evtInfos?ye[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],a=()=>{s.push([ae,l.data])},r=t=>{s.push([re,ge.encodeTile(Pe(e,t))])},d=e=>{for(const t of e)r(t)},c=()=>{s.push([de,ge.encodePlayer(we(e,t))])},u=n[0];if(u===oe){const l=n[1];Ce(e,t,{bgcolor:l,ts:o}),c()}else if(u===le){const l=n[1];Ce(e,t,{color:l,ts:o}),c()}else if(u===ie){const l=`${n[1]}`.substr(0,16);Ce(e,t,{name:l,ts:o}),c()}else if(u===J){const l={x:n[1],y:n[2]};Ce(e,t,{d:1,ts:o}),c(),i._last_mouse_down=l;const s=((e,t)=>{let n=ye[e].puzzle.info,o=ye[e].puzzle.tiles,l=-1,i=-1;for(let s=0;sl)&&(l=e.z,i=s)}return i})(e,l);if(s>=0){let n=Ge(e)+1;ke(e,{maxZ:n}),a();const o=Le(e,s);je(e,o,Ge(e)),He(e,o,t),d(o)}i._last_mouse=l}else if(u===ee){const l=n[1],s=n[2],a={x:l,y:s};if(null===i._last_mouse_down)Ce(e,t,{x:l,y:s,ts:o}),c();else{let n=Ne(e,t);if(n>=0){Ce(e,t,{x:l,y:s,ts:o}),c();const r=Le(e,n);let u=V.pointInBounds(a,Oe(e))&&V.pointInBounds(i._last_mouse_down,Oe(e));for(let t of r){const n=Ee(e,t);if(V.pointInBounds(a,n)){u=!0;break}}if(u){const t=l-i._last_mouse_down.x,n=s-i._last_mouse_down.y;Fe(e,r,{x:t,y:n}),d(r)}}else Ce(e,t,{ts:o}),c();i._last_mouse_down=a}i._last_mouse=a}else if(u===X){const s={x:n[1],y:n[2]},u=0;i._last_mouse_down=null;let p=Ne(e,t);if(p>=0){let n=Le(e,p);He(e,n,0),d(n);let i=Be(e,p),s=De(e,p);if(V.pointDistance(s,i){for(let n of t)Ie(e,n,{owner:-1,z:1})})(e,n),d(n);let r=Qe(e,t);0===ze(e)?r+=n.length:1===ze(e)&&(r+=1),Ce(e,t,{d:u,ts:o,points:r}),c(),Se(e)===Ae(e)&&(ke(e,{finished:o}),a())}else{const n=(e,t,n,o)=>{let l=ye[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=_e(e,t),l=_e(e,n);return o&&o===l})(e,t,n))return!1;const i=Be(e,t),s=V.pointAdd(Be(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(V.pointDistance(i,s){const o=ye[e].puzzle.tiles,l=_e(e,t),i=_e(e,n);let s;const d=[];l&&d.push(l),i&&d.push(i),l?s=l:i?s=i:(ke(e,{maxGroup:$e(e)+1}),a(),s=$e(e));if(Ie(e,t,{group:s}),r(t),Ie(e,n,{group:s}),r(n),d.length>0)for(const a of o){const t=ge.decodeTile(a);d.includes(t.group)&&(Ie(e,t.idx,{group:s}),r(t.idx))}})(e,t,n),l=Le(e,t);const c=((e,t)=>{let n=0;for(let o of t){let t=Me(e,o);t>n&&(n=t)}return n})(e,l);return je(e,l,c),d(l),!0}return!1};let l=!1;for(let t of Le(e,p)){let o=Ve(e,t);if(n(e,t,o[0],[0,1])||n(e,t,o[1],[-1,0])||n(e,t,o[2],[0,-1])||n(e,t,o[3],[1,0])){l=!0;break}}if(l&&1===ze(e)){const n=Qe(e,t)+1;Ce(e,t,{d:u,ts:o,points:n}),c()}else Ce(e,t,{d:u,ts:o}),c()}}else Ce(e,t,{d:u,ts:o}),c();i._last_mouse=s}else if(u===te){const l=n[1],s=n[2];Ce(e,t,{x:l,y:s,ts:o}),c(),i._last_mouse={x:l,y:s}}else if(u===ne){const l=n[1],s=n[2];Ce(e,t,{x:l,y:s,ts:o}),c(),i._last_mouse={x:l,y:s}}else Ce(e,t,{ts:o}),c();return function(e,t,n){ye[e].evtInfos[t]=n}(e,t,i),s},SCORE_MODE_FINAL:0,SCORE_MODE_ANY:1},Ke=e({name:"upload",props:{accept:String,label:String},methods:{async upload(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const o=new FormData;o.append("file",n,n.name);const l=await fetch("/upload",{method:"post",body:o}),i=await l.json();this.$emit("uploaded",i)}}});const Je={class:"btn"};Ke.render=function(e,o,l,i,a,d){return s(),t("label",null,[n("input",{type:"file",style:{display:"none"},onChange:o[1]||(o[1]=(...t)=>e.upload&&e.upload(...t)),accept:e.accept},null,40,["accept"]),n("span",Je,r(e.label||"Upload File"),1)])};var Xe=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},methods:{onClick(){this.$emit("click")}}});Xe.render=function(e,n,o,l,i,a){return s(),t("div",{class:"imageteaser",style:e.style,onClick:n[1]||(n[1]=(...t)=>e.onClick&&e.onClick(...t))},null,4)};var et=e({name:"new-game-dialog",components:{Upload:Ke,ImageTeaser:Xe},props:{images:Array},emits:{newGame:null},data:()=>({tiles:1e3,image:"",scoreMode:Ze.SCORE_MODE_ANY}),methods:{mediaImgUploaded(e){this.image=e.image},canStartNewGame(){return!!(this.tilesInt&&this.image&&[0,1].includes(this.scoreModeInt))},onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const tt=n("h1",null,"New game",-1),nt=n("td",null,[n("label",null,"Pieces: ")],-1),ot=n("td",null,[n("label",null,"Scoring: ")],-1),lt=a(" Any (Score when pieces are connected to each other or on final location)"),it=n("br",null,null,-1),st=a(" Final (Score when pieces are put to their final location)"),at=n("td",null,[n("label",null,"Image: ")],-1),rt={key:0},dt=a(" or "),ct={key:1},ut=a(" (or select from below) "),pt={colspan:"2"},ht=n("h1",null,"Image lib",-1);et.render=function(e,o,l,a,r,g){const y=i("upload"),m=i("image-teaser");return s(),t("div",null,[tt,n("table",null,[n("tr",null,[nt,n("td",null,[u(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[ot,n("td",null,[n("label",null,[u(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[h,e.scoreMode]]),lt]),it,n("label",null,[u(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[h,e.scoreMode]]),st])])]),n("tr",null,[at,n("td",null,[e.image?(s(),t("span",rt,[n("img",{src:e.image.url,style:{width:"150px"}},null,8,["src"]),dt,n(y,{onUploaded:o[4]||(o[4]=t=>e.mediaImgUploaded(t)),accept:"image/*",label:"Upload an image"})])):(s(),t("span",ct,[n(y,{onUploaded:o[5]||(o[5]=t=>e.mediaImgUploaded(t)),accept:"image/*",label:"Upload an image"}),ut]))])]),n("tr",null,[n("td",pt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[6]||(o[6]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))},"Start new game",8,["disabled"])])])]),ht,n("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(m,{image:n,onClick:t=>e.image=n,key:o},null,8,["image","onClick"])))),128))])])};var gt=e({components:{NewGameDialog:et},data:()=>({images:[]}),async created(){const e=await fetch("/api/newgame-data"),t=await e.json();this.images=t.images},methods:{async onNewGame(e){const t=await fetch("/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});gt.render=function(e,o,l,a,r,d){const c=i("new-game-dialog");return s(),t("div",null,[n(c,{images:e.images,onNewGame:e.onNewGame},null,8,["images","onNewGame"])])};var yt=e({name:"scores",props:{activePlayers:{type:Array,required:!0},idlePlayers:{type:Array,required:!0}},computed:{actives(){return this.activePlayers.sort(((e,t)=>t.points-e.points)),this.activePlayers},idles(){return this.idlePlayers.sort(((e,t)=>t.points-e.points)),this.idlePlayers}}});const mt={class:"scores"},ft=n("div",null,"Scores",-1),wt=n("td",null,"⚑",-1),vt=n("td",null,"πŸ’€",-1);yt.render=function(e,o,l,i,a,u){return s(),t("div",mt,[ft,n("table",null,[(s(!0),t(d,null,c(e.actives,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[wt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(s(!0),t(d,null,c(e.idles,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[vt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var xt=e({name:"puzzle-status",props:{finished:{type:Boolean,required:!0},duration:{type:Number,required:!0},piecesDone:{type:Number,required:!0},piecesTotal:{type:Number,required:!0}},computed:{icon(){return this.finished?"🏁":"⏳"},durationStr(){return _(this.duration)}}});const bt={class:"timer"};xt.render=function(e,o,l,i,a,d){return s(),t("div",bt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),g(e.$slots,"default")])};var At=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:Object},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const zt=n("td",null,[n("label",null,"Background: ")],-1),Tt=n("td",null,[n("label",null,"Color: ")],-1),St=n("td",null,[n("label",null,"Name: ")],-1);At.render=function(e,o,l,i,a,r){return s(),t("div",{class:"overlay transparent",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[4]||(o[4]=y((()=>{}),["stop"]))},[n("tr",null,[zt,n("td",null,[u(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[Tt,n("td",null,[u(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[St,n("td",null,[u(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])])])])};var Ct=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const kt={class:"preview"};Ct.render=function(e,o,l,i,a,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",kt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};const It=pe("Communication.js");let Pt,_t=e=>{},Dt=e=>{};let Bt=0;const Ot=e=>{Bt!==e&&(Bt=e,Dt(e))};function Et(e){if(2===Bt)try{Pt.send(JSON.stringify(e))}catch(t){It.info("unable to send message.. maybe because ws is invalid?")}}let Mt,Nt;var Ut={connect:function(e,t,n){return Mt=0,Nt={},Ot(3),new Promise((o=>{Pt=new WebSocket(e,n+"|"+t),Pt.onopen=e=>{Ot(2),Et([L])},Pt.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===W){const e=t[1];o(e)}else{if(l!==j)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&Nt[o])return void delete Nt[o];_t(t)}}},Pt.onerror=e=>{throw Ot(1),"[ 2021-05-15 onerror ]"},Pt.onclose=e=>{4e3===e.code||1001===e.code?Ot(4):Ot(1)}}))},connectReplay:function(e,t,n){return Mt=0,Nt={},Ot(3),new Promise((o=>{Pt=new WebSocket(e,n+"|"+t),Pt.onopen=e=>{Ot(2),Et([Q])},Pt.onmessage=e=>{const t=JSON.parse(e.data),n=t[0];if(n!==F)throw`[ 2021-05-09 invalid connectReplay msgType ${n} ]`;{const e=t[1],n=t[2];o({game:e,log:n})}},Pt.onerror=e=>{throw Ot(1),"[ 2021-05-15 onerror ]"},Pt.onclose=e=>{4e3===e.code||1001===e.code?Ot(4):Ot(1)}}))},disconnect:function(){Pt&&Pt.close(4e3),Mt=0,Nt={}},sendClientEvent:function(e){Mt++,Nt[Mt]=e,Et([H,Mt,Nt[Mt]])},onServerChange:function(e){_t=e},onConnectionStateChange:function(e){Dt=e},CODE_CUSTOM_DISCONNECT:4e3,CONN_STATE_NOT_CONNECTED:0,CONN_STATE_DISCONNECTED:1,CONN_STATE_CLOSED:4,CONN_STATE_CONNECTED:2,CONN_STATE_CONNECTING:3},Rt=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Ut.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Ut.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const $t={key:0,class:"overlay connection-lost"},Gt={key:0,class:"overlay-content"},Vt=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),jt={key:1,class:"overlay-content"},Wt=n("div",null,"Connecting...",-1);Rt.render=function(e,o,i,a,r,d){return e.show?(s(),t("div",$t,[e.lostConnection?(s(),t("div",Gt,[Vt,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",jt,[Wt])):l("",!0)])):l("",!0)};var Ft=e({name:"help-overlay",emits:{bgclick:null}});const Ht=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),a("/"),n("kbd",null,"↑"),a("/πŸ–±οΈ")])])],-1),Lt=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),a("/"),n("kbd",null,"↓"),a("/πŸ–±οΈ")])])],-1),Qt=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),a("/"),n("kbd",null,"←"),a("/πŸ–±οΈ")])])],-1),Yt=n("tr",null,[n("td",null,"➑️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),a("/"),n("kbd",null,"β†’"),a("/πŸ–±οΈ")])])],-1),qt=n("tr",null,[n("td"),n("td",null,[n("div",null,[a("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Zt=n("tr",null,[n("td",null,"πŸ”+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),a("/πŸ–±οΈ-Wheel")])])],-1),Kt=n("tr",null,[n("td",null,"πŸ”- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),a("/πŸ–±οΈ-Wheel")])])],-1),Jt=n("tr",null,[n("td",null,"πŸ–ΌοΈ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Xt=n("tr",null,[n("td",null,"πŸ§©βœ”οΈ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),en=n("tr",null,[n("td",null,"πŸ§©β“ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1);Ft.render=function(e,o,l,i,a,r){return s(),t("div",{class:"overlay transparent",onClick:o[2]||(o[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:o[1]||(o[1]=y((()=>{}),["stop"]))},[Ht,Lt,Qt,Yt,qt,Zt,Kt,Jt,Xt,en])])};var tn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),on=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),ln=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function sn(){let e=0,t=0,n=1;const o=(o,l)=>{e+=o/n,t+=l/n},l=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},i=o=>({x:o.x/n-e,y:o.y/n-t}),s=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),a=e=>({w:e.w*n,h:e.h*n});return{move:o,canZoom:e=>n!=l(e),zoom:(e,t)=>((e,t)=>{if(n==e)return!1;const l=1-n/e;return o(-t.x*l,-t.y*l),n=e,!0})(l(e),t),worldToViewport:e=>{const{x:t,y:n}=s(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:s,worldDimToViewport:e=>{const{w:t,h:n}=a(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:a,viewportToWorld:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:i}}function an(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var rn={createCanvas:an,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const o=an(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorize:async function(e,t,n){const o=an(e.width,e.height),l=o.getContext("2d");return l.save(),l.drawImage(t,0,0),l.fillStyle=n,l.globalCompositeOperation="source-in",l.fillRect(0,0,t.width,t.height),l.restore(),l.save(),l.globalCompositeOperation="destination-over",l.drawImage(e,0,0),l.restore(),await createImageBitmap(o)}};const dn=pe("Debug.js");let cn=0,un=0;var pn=e=>{cn=performance.now(),un=e},hn=e=>{const t=performance.now(),n=t-cn;n>un&&dn.log(e+": "+n),cn=t};const gn=pe("PuzzleGraphics.js");function yn(e,t){const n=ge.coordByTileIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var mn={loadPuzzleBitmaps:async function(e){const t=await rn.loadImageToBitmap(e.info.imageUrl),n=await rn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){gn.log("start createPuzzleTileBitmaps");var o=n.tileSize,l=n.tileMarginWidth,i=n.tileDrawSize,s=o/100,a=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0];const r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,i={x:l,y:l},r=V.pointAdd(i,{x:o,y:0}),c=V.pointAdd(r,{x:0,y:o}),u=V.pointSub(c,{x:o,y:0});if(n.moveTo(i.x,i.y),0!==e.top)for(let o=0;o=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;vn=t/2,xn=t-vn;const n=1/4*this.canvas.width/(t/2);fn=-n,wn=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new bn(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new bn(this.rng));const e=[];for(;this.explodedBombs.length>0;){const t=this.explodedBombs.shift();if(!t)break;t.update(),t.alive&&e.push(t)}this.explodedBombs=e;const t=[];for(;this.readyBombs.length>0;){const e=this.readyBombs.shift();if(!e)break;e.update(this.particles),e.hasExploded?this.explodedBombs.push(e):t.push(e)}this.readyBombs=t;const n=[];for(;this.particles.length>0;){const e=this.particles.shift();if(!e)break;e.update(),e.alive&&n.push(e)}this.particles=n}render(){this.ctx.beginPath(),this.ctx.fillStyle="rgba(0, 0, 0, 0.1)",this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height);for(let e=0;e{const t=e.color+" "+e.d;if(!g[t]){const n=e.d?s:a;if(e.color){const o=e.d?r:d;g[t]=await rn.colorize(n,o,e.color)}else g[t]=n}return g[t]},m=function(e,t){return t.width=window.innerWidth,t.height=window.innerHeight,e.appendChild(t),window.addEventListener("resize",(()=>{t.width=window.innerWidth,t.height=window.innerHeight,kn=!0})),t}(l,rn.createCanvas()),f={log:[],logIdx:0,speeds:[.5,1,2,5,10,20,50],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0};Ut.onConnectionStateChange((e=>{i.setConnectionState(e)}));let w=()=>0;const v=async()=>{if("play"===o){const o=await Ut.connect(n,e,t),l=ge.decodeGame(o);Ze.setGame(l.id,l),w=()=>I()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const o=await Ut.connectReplay(n,e,t),l=ge.decodeGame(o.game);Ze.setGame(l.id,l),f.log=o.log,f.lastRealTs=I(),f.gameStartTs=parseInt(f.log[0][f.log[0].length-2],10),f.lastGameTs=f.gameStartTs,w=()=>f.lastGameTs}}kn=!0};await v();const x=Ze.getTileDrawOffset(e),b=Ze.getTileDrawSize(e),A=Ze.getPuzzleWidth(e),z=Ze.getPuzzleHeight(e),T=Ze.getTableWidth(e),S=Ze.getTableHeight(e),C={x:(T-A)/2,y:(S-z)/2},k={w:A,h:z},P={w:b,h:b},_=await mn.loadPuzzleBitmaps(Ze.getPuzzle(e)),D=new zn(m,Ze.getRng(e));D.init();const B=m.getContext("2d");m.classList.add("loaded");const O=sn();O.move(-(T-m.width)/2,-(S-m.height)/2);const E=function(e,t,n){let o=[],l=!0,i=!1,s=!1,a=!1,r=!1,d=!1,c=!1,u=!1;const p=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>p(e.offsetX,e.offsetY),g=()=>p(e.width/2,e.height/2),y=(e,t)=>{l&&("Shift"===t.key?u=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?a=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?r=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?i=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?s=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};e.addEventListener("mousedown",(e=>{0===e.button&&m([J,...h(e)])})),e.addEventListener("mouseup",(e=>{0===e.button&&m([X,...h(e)])})),e.addEventListener("mousemove",(e=>{m([ee,...h(e)])})),e.addEventListener("wheel",(e=>{if(n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?te:ne;m([t,...h(e)])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{l&&(" "===e.key&&m([se]),"F"!==e.key&&"f"!==e.key||(Sn=!Sn,kn=!0),"G"!==e.key&&"g"!==e.key||(Cn=!Cn,kn=!0))}));const m=e=>{o.push(e)};return{addEvent:m,consumeAll:()=>{if(0===o.length)return[];const e=o.slice();return o=[],e},createKeyEvents:()=>{const e=u?20:10,t=(i?e:0)-(s?e:0),o=(a?e:0)-(r?e:0);0===t&&0===o||m([K,t,o]),d&&c||(d?n.canZoom("in")&&m([te,...g()]):c&&n.canZoom("out")&&m([ne,...g()]))},setHotkeys:e=>{l=e}}}(m,window,O),M=Ze.getImageUrl(e),N=()=>{const t=Ze.getStartTs(e),n=Ze.getFinishTs(e),o=w();i.setFinished(!!n),i.setDuration((n||o)-t)};N(),i.setPiecesDone(Ze.getFinishedTileCount(e)),i.setPiecesTotal(Ze.getTileCount(e));const U=w();i.setActivePlayers(Ze.getActivePlayers(e,U)),i.setIdlePlayers(Ze.getIdlePlayers(e,U));const R=!!Ze.getFinishTs(e);let $=R;const G=()=>$&&!R,V=()=>Ze.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",j=()=>{i.setReplaySpeed&&i.setReplaySpeed(f.speeds[f.speedIdx]),i.setReplayPaused&&i.setReplayPaused(f.paused)};if("play"===o?setInterval(N,1e3):"replay"===o&&j(),"play"===o)Ut.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,i]of o)switch(l){case de:{const n=ge.decodePlayer(i);n.id!==t&&(Ze.setPlayer(e,n.id,n),kn=!0)}break;case re:{const t=ge.decodeTile(i);Ze.setTile(e,t.idx,t),kn=!0}break;case ae:Ze.setPuzzleData(e,i),kn=!0}$=!!Ze.getFinishTs(e)}));else if("replay"===o){let t=setInterval((()=>{const n=I();if(f.paused)return void(f.lastRealTs=n);const o=(n-f.lastRealTs)*f.speeds[f.speedIdx],l=f.lastGameTs+o;for(;;){if(f.paused)break;const n=f.logIdx+1;if(n>=f.log.length){clearInterval(t);break}const o=f.log[n],i=f.gameStartTs+o[o.length-1];if(i>l)break;const s=o.slice();if(s[0]===Y){const t=s[1];Ze.addPlayer(e,t,i),kn=!0}else if(s[0]===q){const t=Ze.getPlayerIdByIndex(e,s[1]);Ze.addPlayer(e,t,i),kn=!0}else if(s[0]===Z){const t=Ze.getPlayerIdByIndex(e,s[1]),n=s[2];Ze.handleInput(e,t,n,i),kn=!0}f.logIdx=n}f.lastRealTs=n,f.lastGameTs=l,N()}),50)}let W=null;return(e=>{const t=e.fps||60,n=e.slow||1,o=e.update,l=e.render,i=window.requestAnimationFrame,s=1/t,a=n*s;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>a;)d-=a,o(s);l(d/n),c=r,i(u)};i(u)})({update:()=>{E.createKeyEvents();for(const n of E.consumeAll())if("play"===o){const o=n[0];if(o===K){const e=n[1],t=n[2];kn=!0,O.move(e,t)}else if(o===ee){if(W&&!Ze.getFirstOwnedTile(e,t)){const e={x:n[1],y:n[2]},t=O.worldToViewport(e),o=Math.round(t.x-W.x),l=Math.round(t.y-W.y);kn=!0,O.move(o,l),W=t}}else if(o===J){const e={x:n[1],y:n[2]};W=O.worldToViewport(e)}else if(o===X)W=null;else if(o===te){const e={x:n[1],y:n[2]};kn=!0,O.zoom("in",O.worldToViewport(e))}else if(o===ne){const e={x:n[1],y:n[2]};kn=!0,O.zoom("out",O.worldToViewport(e))}else o===se&&i.togglePreview();const l=w();Ze.handleInput(e,t,n,l).length>0&&(kn=!0),Ut.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===K){const e=n[1],t=n[2];kn=!0,O.move(e,t)}else if(e===ee){if(W){const e={x:n[1],y:n[2]},t=O.worldToViewport(e),o=Math.round(t.x-W.x),l=Math.round(t.y-W.y);kn=!0,O.move(o,l),W=t}}else if(e===J){const e={x:n[1],y:n[2]};W=O.worldToViewport(e)}else if(e===X)W=null;else if(e===te){const e={x:n[1],y:n[2]};kn=!0,O.zoom("in",O.worldToViewport(e))}else if(e===ne){const e={x:n[1],y:n[2]};kn=!0,O.zoom("out",O.worldToViewport(e))}else e===se&&i.togglePreview()}$=!!Ze.getFinishTs(e),G()&&(D.update(),kn=!0)},render:async()=>{if(!kn)return;const n=w();let l,s,a;window.DEBUG&&pn(0),B.fillStyle=V(),B.fillRect(0,0,m.width,m.height),window.DEBUG&&hn("clear done"),l=O.worldToViewportRaw(C),s=O.worldDimToViewportRaw(k),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&hn("board done");const r=Ze.getTilesSortedByZIndex(e);window.DEBUG&&hn("get tiles done"),s=O.worldDimToViewportRaw(P);for(const e of r)(-1===e.owner?Sn:Cn)&&(a=_[e.idx],l=O.worldToViewportRaw({x:x+e.pos.x,y:x+e.pos.y}),B.drawImage(a,0,0,a.width,a.height,l.x,l.y,s.w,s.h));window.DEBUG&&hn("tiles done");const d=[];for(const i of Ze.getActivePlayers(e,n))a=await y(i),l=O.worldToViewport(i),B.drawImage(a,l.x-u,l.y-h),c=i,("replay"===o||c.id!==t)&&d.push([`${i.name} (${i.points})`,l.x,l.y+p]);var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,o]of d)B.fillText(e,t,o);window.DEBUG&&hn("players done"),i.setActivePlayers(Ze.getActivePlayers(e,n)),i.setIdlePlayers(Ze.getIdlePlayers(e,n)),i.setPiecesDone(Ze.getFinishedTileCount(e)),window.DEBUG&&hn("HUD done"),G()&&D.render(),kn=!1}}),{setHotkeys:e=>{E.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),E.addEvent([oe,e])},onColorChange:e=>{localStorage.setItem("player_color",e),E.addEvent([le,e])},onNameChange:e=>{localStorage.setItem("player_name",e),E.addEvent([ie,e])},replayOnSpeedUp:()=>{f.speedIdx+1{f.speedIdx>=1&&(f.speedIdx--,j())},replayOnPauseToggle:()=>{f.paused=!f.paused,j()},previewImageUrl:M,player:{background:V(),color:Ze.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff",name:Ze.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon"},disconnect:Ut.disconnect,connect:v}}var Pn=e({name:"game",components:{PuzzleStatus:xt,Scores:yt,SettingsOverlay:At,PreviewOverlay:Ct,ConnectionOverlay:Rt,HelpOverlay:Ft},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},disconnect:()=>{},connect:()=>{}}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await In(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},setConnectionState:e=>{this.connectionState=e},togglePreview:()=>{this.toggle("preview",!1)}}))},unmounted(){this.g.disconnect()},methods:{reconnect(){this.g.connect()},toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}}});const _n={id:"game"},Dn={class:"menu"},Bn={class:"tabs"},On=a("🧩 Puzzles");Pn.render=function(e,l,a,r,d,c){const p=i("settings-overlay"),h=i("preview-overlay"),g=i("help-overlay"),y=i("connection-overlay"),f=i("puzzle-status"),w=i("router-link"),v=i("scores");return s(),t("div",_n,[u(n(p,{onBgclick:l[1]||(l[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":l[2]||(l[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[m,"settings"===e.overlay]]),u(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[m,"preview"===e.overlay]]),u(n(g,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[m,"help"===e.overlay]]),n(y,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(f,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",Dn,[n("div",Bn,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[On])),_:1}),n("div",{class:"opener",onClick:l[5]||(l[5]=t=>e.toggle("preview",!1))},"πŸ–ΌοΈ Preview"),n("div",{class:"opener",onClick:l[6]||(l[6]=t=>e.toggle("settings",!0))},"πŸ› οΈ Settings"),n("div",{class:"opener",onClick:l[7]||(l[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var En=e({name:"replay",components:{PuzzleStatus:xt,Scores:yt,SettingsOverlay:At,PreviewOverlay:Ct,HelpOverlay:Ft},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},disconnect:()=>{}},replay:{speed:1,paused:!1}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await In(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"replay",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},setReplaySpeed:e=>{this.replay.speed=e},setReplayPaused:e=>{this.replay.paused=e}}))},unmounted(){this.g.disconnect()},methods:{toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}},computed:{replayText(){return"Replay-Speed: "+this.replay.speed+"x"+(this.replay.paused?" Paused":"")}}});const Mn={id:"replay"},Nn={class:"menu"},Un={class:"tabs"},Rn=a("🧩 Puzzles");En.render=function(e,l,a,d,c,p){const h=i("settings-overlay"),g=i("preview-overlay"),y=i("help-overlay"),f=i("puzzle-status"),w=i("router-link"),v=i("scores");return s(),t("div",Mn,[u(n(h,{onBgclick:l[1]||(l[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":l[2]||(l[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[m,"settings"===e.overlay]]),u(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[m,"preview"===e.overlay]]),u(n(y,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[m,"help"===e.overlay]]),n(f,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:l[5]||(l[5]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[6]||(l[6]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",Nn,[n("div",Un,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Rn])),_:1}),n("div",{class:"opener",onClick:l[8]||(l[8]=t=>e.toggle("preview",!1))},"πŸ–ΌοΈ Preview"),n("div",{class:"opener",onClick:l[9]||(l[9]=t=>e.toggle("settings",!0))},"πŸ› οΈ Settings"),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=f({history:w(),routes:[{name:"index",path:"/",component:N},{name:"new-game",path:"/new-game",component:gt},{name:"game",path:"/g/:id",component:Pn},{name:"replay",path:"/replay/:id",component:En}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=v(x);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=ge.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); diff --git a/build/public/assets/vendor.00b608ff.js b/build/public/assets/vendor.00b608ff.js new file mode 100644 index 0000000..479e89d --- /dev/null +++ b/build/public/assets/vendor.00b608ff.js @@ -0,0 +1,6 @@ +function e(e,t){const n=Object.create(null),r=e.split(",");for(let o=0;o!!n[e.toLowerCase()]:e=>!!n[e]}const t=e("Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt"),n=e("itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly");function r(e){if(x(e)){const t={};for(let n=0;n{if(e){const n=e.split(s);n.length>1&&(t[n[0].trim()]=n[1].trim())}})),t}function i(e){let t="";if(C(e))t=e;else if(x(e))for(let n=0;nnull==e?"":A(e)?JSON.stringify(e,u,2):String(e),u=(e,t)=>E(t)?{[`Map(${t.size})`]:[...t.entries()].reduce(((e,[t,n])=>(e[`${t} =>`]=n,e)),{})}:k(t)?{[`Set(${t.size})`]:[...t.values()]}:!A(t)||x(t)||$(t)?t:String(t),f={},p=[],d=()=>{},h=()=>!1,m=/^on[^a-z]/,g=e=>m.test(e),v=e=>e.startsWith("onUpdate:"),y=Object.assign,b=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},_=Object.prototype.hasOwnProperty,w=(e,t)=>_.call(e,t),x=Array.isArray,E=e=>"[object Map]"===j(e),k=e=>"[object Set]"===j(e),S=e=>e instanceof Date,O=e=>"function"==typeof e,C=e=>"string"==typeof e,R=e=>"symbol"==typeof e,A=e=>null!==e&&"object"==typeof e,P=e=>A(e)&&O(e.then)&&O(e.catch),F=Object.prototype.toString,j=e=>F.call(e),$=e=>"[object Object]"===j(e),M=e=>C(e)&&"NaN"!==e&&"-"!==e[0]&&""+parseInt(e,10)===e,T=e(",key,ref,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),I=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},U=/-(\w)/g,V=I((e=>e.replace(U,((e,t)=>t?t.toUpperCase():"")))),N=/\B([A-Z])/g,L=I((e=>e.replace(N,"-$1").toLowerCase())),B=I((e=>e.charAt(0).toUpperCase()+e.slice(1))),D=I((e=>e?`on${B(e)}`:"")),q=(e,t)=>e!==t&&(e==e||t==t),z=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},K=e=>{const t=parseFloat(e);return isNaN(t)?e:t},G=new WeakMap,H=[];let X;const J=Symbol(""),Q=Symbol("");function Y(e,t=f){(function(e){return e&&!0===e._isEffect})(e)&&(e=e.raw);const n=function(e,t){const n=function(){if(!n.active)return t.scheduler?void 0:e();if(!H.includes(n)){te(n);try{return re.push(ne),ne=!0,H.push(n),X=n,e()}finally{H.pop(),se(),X=H[H.length-1]}}};return n.id=ee++,n.allowRecurse=!!t.allowRecurse,n._isEffect=!0,n.active=!0,n.raw=e,n.deps=[],n.options=t,n}(e,t);return t.lazy||n(),n}function Z(e){e.active&&(te(e),e.options.onStop&&e.options.onStop(),e.active=!1)}let ee=0;function te(e){const{deps:t}=e;if(t.length){for(let n=0;n{e&&e.forEach((e=>{(e!==X||e.allowRecurse)&&i.add(e)}))};if("clear"===t)l.forEach(c);else if("length"===n&&x(e))l.forEach(((e,t)=>{("length"===t||t>=r)&&c(e)}));else switch(void 0!==n&&c(l.get(n)),t){case"add":x(e)?M(n)&&c(l.get("length")):(c(l.get(J)),E(e)&&c(l.get(Q)));break;case"delete":x(e)||(c(l.get(J)),E(e)&&c(l.get(Q)));break;case"set":E(e)&&c(l.get(J))}i.forEach((e=>{e.options.scheduler?e.options.scheduler(e):e()}))}const ce=e("__proto__,__v_isRef,__isVue"),ae=new Set(Object.getOwnPropertyNames(Symbol).map((e=>Symbol[e])).filter(R)),ue=me(),fe=me(!1,!0),pe=me(!0),de=me(!0,!0),he={};function me(e=!1,t=!1){return function(n,r,o){if("__v_isReactive"===r)return!e;if("__v_isReadonly"===r)return e;if("__v_raw"===r&&o===(e?t?We:ze:t?qe:De).get(n))return n;const s=x(n);if(!e&&s&&w(he,r))return Reflect.get(he,r,o);const l=Reflect.get(n,r,o);if(R(r)?ae.has(r):ce(r))return l;if(e||le(n,0,r),t)return l;if(tt(l)){return!s||!M(r)?l.value:l}return A(l)?e?He(l):Ge(l):l}}["includes","indexOf","lastIndexOf"].forEach((e=>{const t=Array.prototype[e];he[e]=function(...e){const n=Ze(this);for(let t=0,o=this.length;t{const t=Array.prototype[e];he[e]=function(...e){oe();const n=t.apply(this,e);return se(),n}}));function ge(e=!1){return function(t,n,r,o){let s=t[n];if(!e&&(r=Ze(r),s=Ze(s),!x(t)&&tt(s)&&!tt(r)))return s.value=r,!0;const l=x(t)&&M(n)?Number(n)!0,deleteProperty:(e,t)=>!0},be=y({},ve,{get:fe,set:ge(!0)});y({},ye,{get:de});const _e=e=>A(e)?Ge(e):e,we=e=>A(e)?He(e):e,xe=e=>e,Ee=e=>Reflect.getPrototypeOf(e);function ke(e,t,n=!1,r=!1){const o=Ze(e=e.__v_raw),s=Ze(t);t!==s&&!n&&le(o,0,t),!n&&le(o,0,s);const{has:l}=Ee(o),i=r?xe:n?we:_e;return l.call(o,t)?i(e.get(t)):l.call(o,s)?i(e.get(s)):void 0}function Se(e,t=!1){const n=this.__v_raw,r=Ze(n),o=Ze(e);return e!==o&&!t&&le(r,0,e),!t&&le(r,0,o),e===o?n.has(e):n.has(e)||n.has(o)}function Oe(e,t=!1){return e=e.__v_raw,!t&&le(Ze(e),0,J),Reflect.get(e,"size",e)}function Ce(e){e=Ze(e);const t=Ze(this);return Ee(t).has.call(t,e)||(t.add(e),ie(t,"add",e,e)),this}function Re(e,t){t=Ze(t);const n=Ze(this),{has:r,get:o}=Ee(n);let s=r.call(n,e);s||(e=Ze(e),s=r.call(n,e));const l=o.call(n,e);return n.set(e,t),s?q(t,l)&&ie(n,"set",e,t):ie(n,"add",e,t),this}function Ae(e){const t=Ze(this),{has:n,get:r}=Ee(t);let o=n.call(t,e);o||(e=Ze(e),o=n.call(t,e)),r&&r.call(t,e);const s=t.delete(e);return o&&ie(t,"delete",e,void 0),s}function Pe(){const e=Ze(this),t=0!==e.size,n=e.clear();return t&&ie(e,"clear",void 0,void 0),n}function Fe(e,t){return function(n,r){const o=this,s=o.__v_raw,l=Ze(s),i=t?xe:e?we:_e;return!e&&le(l,0,J),s.forEach(((e,t)=>n.call(r,i(e),i(t),o)))}}function je(e,t,n){return function(...r){const o=this.__v_raw,s=Ze(o),l=E(s),i="entries"===e||e===Symbol.iterator&&l,c="keys"===e&&l,a=o[e](...r),u=n?xe:t?we:_e;return!t&&le(s,0,c?Q:J),{next(){const{value:e,done:t}=a.next();return t?{value:e,done:t}:{value:i?[u(e[0]),u(e[1])]:u(e),done:t}},[Symbol.iterator](){return this}}}}function $e(e){return function(...t){return"delete"!==e&&this}}const Me={get(e){return ke(this,e)},get size(){return Oe(this)},has:Se,add:Ce,set:Re,delete:Ae,clear:Pe,forEach:Fe(!1,!1)},Te={get(e){return ke(this,e,!1,!0)},get size(){return Oe(this)},has:Se,add:Ce,set:Re,delete:Ae,clear:Pe,forEach:Fe(!1,!0)},Ie={get(e){return ke(this,e,!0)},get size(){return Oe(this,!0)},has(e){return Se.call(this,e,!0)},add:$e("add"),set:$e("set"),delete:$e("delete"),clear:$e("clear"),forEach:Fe(!0,!1)},Ue={get(e){return ke(this,e,!0,!0)},get size(){return Oe(this,!0)},has(e){return Se.call(this,e,!0)},add:$e("add"),set:$e("set"),delete:$e("delete"),clear:$e("clear"),forEach:Fe(!0,!0)};function Ve(e,t){const n=t?e?Ue:Te:e?Ie:Me;return(t,r,o)=>"__v_isReactive"===r?!e:"__v_isReadonly"===r?e:"__v_raw"===r?t:Reflect.get(w(n,r)&&r in t?n:t,r,o)}["keys","values","entries",Symbol.iterator].forEach((e=>{Me[e]=je(e,!1,!1),Ie[e]=je(e,!0,!1),Te[e]=je(e,!1,!0),Ue[e]=je(e,!0,!0)}));const Ne={get:Ve(!1,!1)},Le={get:Ve(!1,!0)},Be={get:Ve(!0,!1)},De=new WeakMap,qe=new WeakMap,ze=new WeakMap,We=new WeakMap;function Ke(e){return e.__v_skip||!Object.isExtensible(e)?0:function(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}((e=>j(e).slice(8,-1))(e))}function Ge(e){return e&&e.__v_isReadonly?e:Xe(e,!1,ve,Ne,De)}function He(e){return Xe(e,!0,ye,Be,ze)}function Xe(e,t,n,r,o){if(!A(e))return e;if(e.__v_raw&&(!t||!e.__v_isReactive))return e;const s=o.get(e);if(s)return s;const l=Ke(e);if(0===l)return e;const i=new Proxy(e,2===l?r:n);return o.set(e,i),i}function Je(e){return Qe(e)?Je(e.__v_raw):!(!e||!e.__v_isReactive)}function Qe(e){return!(!e||!e.__v_isReadonly)}function Ye(e){return Je(e)||Qe(e)}function Ze(e){return e&&Ze(e.__v_raw)||e}const et=e=>A(e)?Ge(e):e;function tt(e){return Boolean(e&&!0===e.__v_isRef)}class nt{constructor(e,t=!1){this._rawValue=e,this._shallow=t,this.__v_isRef=!0,this._value=t?e:et(e)}get value(){return le(Ze(this),0,"value"),this._value}set value(e){q(Ze(e),this._rawValue)&&(this._rawValue=e,this._value=this._shallow?e:et(e),ie(Ze(this),"set","value",e))}}function rt(e,t=!1){return tt(e)?e:new nt(e,t)}function ot(e){return tt(e)?e.value:e}const st={get:(e,t,n)=>ot(Reflect.get(e,t,n)),set:(e,t,n,r)=>{const o=e[t];return tt(o)&&!tt(n)?(o.value=n,!0):Reflect.set(e,t,n,r)}};function lt(e){return Je(e)?e:new Proxy(e,st)}class it{constructor(e,t){this._object=e,this._key=t,this.__v_isRef=!0}get value(){return this._object[this._key]}set value(e){this._object[this._key]=e}}class ct{constructor(e,t,n){this._setter=t,this._dirty=!0,this.__v_isRef=!0,this.effect=Y(e,{lazy:!0,scheduler:()=>{this._dirty||(this._dirty=!0,ie(Ze(this),"set","value"))}}),this.__v_isReadonly=n}get value(){const e=Ze(this);return e._dirty&&(e._value=this.effect(),e._dirty=!1),le(e,0,"value"),e._value}set value(e){this._setter(e)}}function at(e,t,n,r){let o;try{o=r?e(...r):e()}catch(s){ft(s,t,n)}return o}function ut(e,t,n,r){if(O(e)){const o=at(e,t,n,r);return o&&P(o)&&o.catch((e=>{ft(e,t,n)})),o}const o=[];for(let s=0;s>>1;Ft(ht[e])-1?ht.splice(t,0,e):ht.push(e),Ct()}}function Ct(){pt||dt||(dt=!0,Et=xt.then(jt))}function Rt(e,t,n,r){x(e)?n.push(...e):t&&t.includes(e,e.allowRecurse?r+1:r)||n.push(e),Ct()}function At(e,t=null){if(gt.length){for(kt=t,vt=[...new Set(gt)],gt.length=0,yt=0;ytFt(e)-Ft(t))),wt=0;wt<_t.length;wt++)_t[wt]();_t=null,wt=0}}const Ft=e=>null==e.id?1/0:e.id;function jt(e){dt=!1,pt=!0,At(e),ht.sort(((e,t)=>Ft(e)-Ft(t)));try{for(mt=0;mte.trim())):t&&(o=n.map(K))}let i,c=r[i=D(t)]||r[i=D(V(t))];!c&&s&&(c=r[i=D(L(t))]),c&&ut(c,e,6,o);const a=r[i+"Once"];if(a){if(e.emitted){if(e.emitted[i])return}else(e.emitted={})[i]=!0;ut(a,e,6,o)}}function Mt(e,t,n=!1){if(!t.deopt&&void 0!==e.__emits)return e.__emits;const r=e.emits;let o={},s=!1;if(!O(e)){const r=e=>{const n=Mt(e,t,!0);n&&(s=!0,y(o,n))};!n&&t.mixins.length&&t.mixins.forEach(r),e.extends&&r(e.extends),e.mixins&&e.mixins.forEach(r)}return r||s?(x(r)?r.forEach((e=>o[e]=null)):y(o,r),e.__emits=o):e.__emits=null}function Tt(e,t){return!(!e||!g(t))&&(t=t.slice(2).replace(/Once$/,""),w(e,t[0].toLowerCase()+t.slice(1))||w(e,L(t))||w(e,t))}let It=0;const Ut=e=>It+=e;function Vt(e,t,n={},r,o){let s=e[t];It++,Xn();const l=s&&Nt(s(n)),i=Qn(qn,{key:n.key||`_${t}`},l||(r?r():[]),l&&1===e._?64:-2);return!o&&i.scopeId&&(i.slotScopeIds=[i.scopeId+"-s"]),It--,i}function Nt(e){return e.some((e=>!Yn(e)||e.type!==Wn&&!(e.type===qn&&!Nt(e.children))))?e:null}let Lt=null,Bt=null;function Dt(e){const t=Lt;return Lt=e,Bt=e&&e.type.__scopeId||null,t}function qt(e,t=Lt){if(!t)return e;const n=(...n)=>{It||Xn(!0);const r=Dt(t),o=e(...n);return Dt(r),It||Jn(),o};return n._c=!0,n}function zt(e){const{type:t,vnode:n,proxy:r,withProxy:o,props:s,propsOptions:[l],slots:i,attrs:c,emit:a,render:u,renderCache:f,data:p,setupState:d,ctx:h}=e;let m;const g=Dt(e);try{let e;if(4&n.shapeFlag){const t=o||r;m=ir(u.call(t,t,f,s,d,p,h)),e=c}else{const n=t;0,m=ir(n.length>1?n(s,{attrs:c,slots:i,emit:a}):n(s,null)),e=t.props?c:Kt(c)}let g=m;if(!1!==t.inheritAttrs&&e){const t=Object.keys(e),{shapeFlag:n}=g;t.length&&(1&n||6&n)&&(l&&t.some(v)&&(e=Gt(e,l)),g=or(g,e))}n.dirs&&(g.dirs=g.dirs?g.dirs.concat(n.dirs):n.dirs),n.transition&&(g.transition=n.transition),m=g}catch(y){Gn.length=0,ft(y,e,1),m=rr(Wn)}return Dt(g),m}function Wt(e){let t;for(let n=0;n{let t;for(const n in e)("class"===n||"style"===n||g(n))&&((t||(t={}))[n]=e[n]);return t},Gt=(e,t)=>{const n={};for(const r in e)v(r)&&r.slice(9)in t||(n[r]=e[r]);return n};function Ht(e,t,n){const r=Object.keys(t);if(r.length!==Object.keys(e).length)return!0;for(let o=0;o{l=!0;const[n,r]=Zt(e,t,!0);y(o,n),r&&s.push(...r)};!n&&t.mixins.length&&t.mixins.forEach(r),e.extends&&r(e.extends),e.mixins&&e.mixins.forEach(r)}if(!r&&!l)return e.__props=p;if(x(r))for(let i=0;i-1,n[1]=r<0||t-1||w(n,"default"))&&s.push(e)}}}return e.__props=[o,s]}function en(e){return"$"!==e[0]}function tn(e){const t=e&&e.toString().match(/^\s*function (\w+)/);return t?t[1]:""}function nn(e,t){return tn(e)===tn(t)}function rn(e,t){return x(t)?t.findIndex((t=>nn(t,e))):O(t)&&nn(t,e)?0:-1}function on(e,t,n=Or,r=!1){if(n){const o=n[e]||(n[e]=[]),s=t.__weh||(t.__weh=(...r)=>{if(n.isUnmounted)return;oe(),Cr(n);const o=ut(t,n,e,r);return Cr(null),se(),o});return r?o.unshift(s):o.push(s),s}}const sn=e=>(t,n=Or)=>!Ar&&on(e,t,n),ln=sn("bm"),cn=sn("m"),an=sn("bu"),un=sn("u"),fn=sn("bum"),pn=sn("um"),dn=sn("rtg"),hn=sn("rtc"),mn={};function gn(e,t,n){return vn(e,t,n)}function vn(e,t,{immediate:n,deep:r,flush:o,onTrack:s,onTrigger:l}=f,i=Or){let c,a,u=!1;if(tt(e)?(c=()=>e.value,u=!!e._shallow):Je(e)?(c=()=>e,r=!0):c=x(e)?()=>e.map((e=>tt(e)?e.value:Je(e)?bn(e):O(e)?at(e,i,2,[i&&i.proxy]):void 0)):O(e)?t?()=>at(e,i,2,[i&&i.proxy]):()=>{if(!i||!i.isUnmounted)return a&&a(),ut(e,i,3,[p])}:d,t&&r){const e=c;c=()=>bn(e())}let p=e=>{a=v.options.onStop=()=>{at(e,i,4)}},h=x(e)?[]:mn;const m=()=>{if(v.active)if(t){const e=v();(r||u||q(e,h))&&(a&&a(),ut(t,i,3,[e,h===mn?void 0:h,p]),h=e)}else v()};let g;m.allowRecurse=!!t,g="sync"===o?m:"post"===o?()=>Tn(m,i&&i.suspense):()=>{!i||i.isMounted?function(e){Rt(e,vt,gt,yt)}(m):m()};const v=Y(c,{lazy:!0,onTrack:s,onTrigger:l,scheduler:g});return jr(v,i),t?n?m():h=v():"post"===o?Tn(v,i&&i.suspense):v(),()=>{Z(v),i&&b(i.effects,v)}}function yn(e,t,n){const r=this.proxy;return vn(C(e)?()=>r[e]:e.bind(r),t.bind(r),n,this)}function bn(e,t=new Set){if(!A(e)||t.has(e))return e;if(t.add(e),tt(e))bn(e.value,t);else if(x(e))for(let n=0;n{bn(e,t)}));else for(const n in e)bn(e[n],t);return e}const _n=e=>e.type.__isKeepAlive;function wn(e,t,n=Or){const r=e.__wdc||(e.__wdc=()=>{let t=n;for(;t;){if(t.isDeactivated)return;t=t.parent}e()});if(on(t,r,n),n){let e=n.parent;for(;e&&e.parent;)_n(e.parent.vnode)&&xn(r,t,n,e),e=e.parent}}function xn(e,t,n,r){const o=on(t,e,r,!0);pn((()=>{b(r[t],o)}),n)}const En=e=>"_"===e[0]||"$stable"===e,kn=e=>x(e)?e.map(ir):[ir(e)],Sn=(e,t,n)=>qt((e=>kn(t(e))),n),On=(e,t)=>{const n=e._ctx;for(const r in e){if(En(r))continue;const o=e[r];if(O(o))t[r]=Sn(0,o,n);else if(null!=o){const e=kn(o);t[r]=()=>e}}},Cn=(e,t)=>{const n=kn(t);e.slots.default=()=>n};function Rn(e,t){if(null===Lt)return e;const n=Lt.proxy,r=e.dirs||(e.dirs=[]);for(let o=0;o(s.has(e)||(e&&O(e.install)?(s.add(e),e.install(i,...t)):O(e)&&(s.add(e),e(i,...t))),i),mixin:e=>(o.mixins.includes(e)||(o.mixins.push(e),(e.props||e.emits)&&(o.deopt=!0)),i),component:(e,t)=>t?(o.components[e]=t,i):o.components[e],directive:(e,t)=>t?(o.directives[e]=t,i):o.directives[e],mount(s,c,a){if(!l){const u=rr(n,r);return u.appContext=o,c&&t?t(u,s):e(u,s,a),l=!0,i._container=s,s.__vue_app__=i,u.component.proxy}},unmount(){l&&(e(null,i._container),delete i._container.__vue_app__)},provide:(e,t)=>(o.provides[e]=t,i)};return i}}function $n(e){return O(e)?{setup:e,name:e.name}:e}const Mn={scheduler:Ot,allowRecurse:!0},Tn=function(e,t){t&&t.pendingBranch?x(e)?t.effects.push(...e):t.effects.push(e):Rt(e,_t,bt,wt)},In=(e,t,n,r)=>{if(x(e))return void e.forEach(((e,o)=>In(e,t&&(x(t)?t[o]:t),n,r)));let o;if(r){if(r.type.__asyncLoader)return;o=4&r.shapeFlag?r.component.exposed||r.component.proxy:r.el}else o=null;const{i:s,r:l}=e,i=t&&t.r,c=s.refs===f?s.refs={}:s.refs,a=s.setupState;if(null!=i&&i!==l&&(C(i)?(c[i]=null,w(a,i)&&(a[i]=null)):tt(i)&&(i.value=null)),C(l)){const e=()=>{c[l]=o,w(a,l)&&(a[l]=o)};o?(e.id=-1,Tn(e,n)):e()}else if(tt(l)){const e=()=>{l.value=o};o?(e.id=-1,Tn(e,n)):e()}else O(l)&&at(l,s,12,[o,c])};function Un(e){return function(e,t){const{insert:n,remove:r,patchProp:o,forcePatchProp:s,createElement:l,createText:i,createComment:c,setText:a,setElementText:u,parentNode:h,nextSibling:m,setScopeId:g=d,cloneNode:v,insertStaticContent:b}=e,_=(e,t,n,r=null,o=null,s=null,l=!1,i=null,c=!1)=>{e&&!Zn(e,t)&&(r=re(e),J(e,o,s,!0),e=null),-2===t.patchFlag&&(c=!1,t.dynamicChildren=null);const{type:a,ref:u,shapeFlag:f}=t;switch(a){case zn:x(e,t,n,r);break;case Wn:E(e,t,n,r);break;case Kn:null==e&&k(t,n,r,l);break;case qn:I(e,t,n,r,o,s,l,i,c);break;default:1&f?C(e,t,n,r,o,s,l,i,c):6&f?U(e,t,n,r,o,s,l,i,c):(64&f||128&f)&&a.process(e,t,n,r,o,s,l,i,c,ce)}null!=u&&o&&In(u,e&&e.ref,s,t)},x=(e,t,r,o)=>{if(null==e)n(t.el=i(t.children),r,o);else{const n=t.el=e.el;t.children!==e.children&&a(n,t.children)}},E=(e,t,r,o)=>{null==e?n(t.el=c(t.children||""),r,o):t.el=e.el},k=(e,t,n,r)=>{[e.el,e.anchor]=b(e.children,t,n,r)},S=({el:e,anchor:t},r,o)=>{let s;for(;e&&e!==t;)s=m(e),n(e,r,o),e=s;n(t,r,o)},O=({el:e,anchor:t})=>{let n;for(;e&&e!==t;)n=m(e),r(e),e=n;r(t)},C=(e,t,n,r,o,s,l,i,c)=>{l=l||"svg"===t.type,null==e?R(t,n,r,o,s,l,i,c):j(e,t,o,s,l,i,c)},R=(e,t,r,s,i,c,a,f)=>{let p,d;const{type:h,props:m,shapeFlag:g,transition:y,patchFlag:b,dirs:_}=e;if(e.el&&void 0!==v&&-1===b)p=e.el=v(e.el);else{if(p=e.el=l(e.type,c,m&&m.is,m),8&g?u(p,e.children):16&g&&F(e.children,p,null,s,i,c&&"foreignObject"!==h,a,f||!!e.dynamicChildren),_&&An(e,null,s,"created"),m){for(const t in m)T(t)||o(p,t,null,m[t],c,e.children,s,i,ne);(d=m.onVnodeBeforeMount)&&Vn(d,s,e)}A(p,e,e.scopeId,a,s)}_&&An(e,null,s,"beforeMount");const w=(!i||i&&!i.pendingBranch)&&y&&!y.persisted;w&&y.beforeEnter(p),n(p,t,r),((d=m&&m.onVnodeMounted)||w||_)&&Tn((()=>{d&&Vn(d,s,e),w&&y.enter(p),_&&An(e,null,s,"mounted")}),i)},A=(e,t,n,r,o)=>{if(n&&g(e,n),r)for(let s=0;s{for(let a=c;a{const a=t.el=e.el;let{patchFlag:p,dynamicChildren:d,dirs:h}=t;p|=16&e.patchFlag;const m=e.props||f,g=t.props||f;let v;if((v=g.onVnodeBeforeUpdate)&&Vn(v,n,t,e),h&&An(t,e,n,"beforeUpdate"),p>0){if(16&p)M(a,t,m,g,n,r,l);else if(2&p&&m.class!==g.class&&o(a,"class",null,g.class,l),4&p&&o(a,"style",m.style,g.style,l),8&p){const i=t.dynamicProps;for(let t=0;t{v&&Vn(v,n,t,e),h&&An(t,e,n,"updated")}),r)},$=(e,t,n,r,o,s,l)=>{for(let i=0;i{if(n!==r){for(const a in r){if(T(a))continue;const u=r[a],f=n[a];(u!==f||s&&s(e,a))&&o(e,a,f,u,c,t.children,l,i,ne)}if(n!==f)for(const s in n)T(s)||s in r||o(e,s,n[s],null,c,t.children,l,i,ne)}},I=(e,t,r,o,s,l,c,a,u)=>{const f=t.el=e?e.el:i(""),p=t.anchor=e?e.anchor:i("");let{patchFlag:d,dynamicChildren:h,slotScopeIds:m}=t;d>0&&(u=!0),m&&(a=a?a.concat(m):m),null==e?(n(f,r,o),n(p,r,o),F(t.children,r,p,s,l,c,a,u)):d>0&&64&d&&h&&e.dynamicChildren?($(e.dynamicChildren,h,r,s,l,c,a),(null!=t.key||s&&t===s.subTree)&&Nn(e,t,!0)):K(e,t,r,p,s,l,c,a,u)},U=(e,t,n,r,o,s,l,i,c)=>{t.slotScopeIds=i,null==e?512&t.shapeFlag?o.ctx.activate(t,n,r,l,c):N(t,n,r,o,s,l,c):B(e,t,c)},N=(e,t,n,r,o,s,l)=>{const i=e.component=function(e,t,n){const r=e.type,o=(t?t.appContext:e.appContext)||kr,s={uid:Sr++,vnode:e,type:r,parent:t,appContext:o,root:null,next:null,subTree:null,update:null,render:null,proxy:null,exposed:null,withProxy:null,effects:null,provides:t?t.provides:Object.create(o.provides),accessCache:null,renderCache:[],components:null,directives:null,propsOptions:Zt(r,o),emitsOptions:Mt(r,o),emit:null,emitted:null,propsDefaults:f,ctx:f,data:f,props:f,attrs:f,slots:f,refs:f,setupState:f,setupContext:null,suspense:n,suspenseId:n?n.pendingId:0,asyncDep:null,asyncResolved:!1,isMounted:!1,isUnmounted:!1,isDeactivated:!1,bc:null,c:null,bm:null,m:null,bu:null,u:null,um:null,bum:null,da:null,a:null,rtg:null,rtc:null,ec:null};return s.ctx={_:s},s.root=t?t.root:s,s.emit=$t.bind(null,s),s}(e,r,o);if(_n(e)&&(i.ctx.renderer=ce),function(e,t=!1){Ar=t;const{props:n,children:r}=e.vnode,o=Rr(e);Jt(e,n,o,t),((e,t)=>{if(32&e.vnode.shapeFlag){const n=t._;n?(e.slots=t,W(t,"_",n)):On(t,e.slots={})}else e.slots={},t&&Cn(e,t);W(e.slots,er,1)})(e,r);const s=o?function(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,xr);const{setup:r}=n;if(r){const n=e.setupContext=r.length>1?function(e){const t=t=>{e.exposed=lt(t)};return{attrs:e.attrs,slots:e.slots,emit:e.emit,expose:t}}(e):null;Or=e,oe();const o=at(r,e,0,[e.props,n]);if(se(),Or=null,P(o)){if(t)return o.then((t=>{Pr(e,t)})).catch((t=>{ft(t,e,0)}));e.asyncDep=o}else Pr(e,o)}else Fr(e)}(e,t):void 0;Ar=!1}(i),i.asyncDep){if(o&&o.registerDep(i,D),!e.el){const e=i.subTree=rr(Wn);E(null,e,t,n)}}else D(i,e,t,n,o,s,l)},B=(e,t,n)=>{const r=t.component=e.component;if(function(e,t,n){const{props:r,children:o,component:s}=e,{props:l,children:i,patchFlag:c}=t,a=s.emitsOptions;if(t.dirs||t.transition)return!0;if(!(n&&c>=0))return!(!o&&!i||i&&i.$stable)||r!==l&&(r?!l||Ht(r,l,a):!!l);if(1024&c)return!0;if(16&c)return r?Ht(r,l,a):!!l;if(8&c){const e=t.dynamicProps;for(let t=0;tmt&&ht.splice(t,1)}(r.update),r.update()}else t.component=e.component,t.el=e.el,r.vnode=t},D=(e,t,n,r,o,s,l)=>{e.update=Y((function(){if(e.isMounted){let t,{next:n,bu:r,u:i,parent:c,vnode:a}=e,u=n;n?(n.el=a.el,q(e,n,l)):n=a,r&&z(r),(t=n.props&&n.props.onVnodeBeforeUpdate)&&Vn(t,c,n,a);const f=zt(e),p=e.subTree;e.subTree=f,_(p,f,h(p.el),re(p),e,o,s),n.el=f.el,null===u&&function({vnode:e,parent:t},n){for(;t&&t.subTree===e;)(e=t.vnode).el=n,t=t.parent}(e,f.el),i&&Tn(i,o),(t=n.props&&n.props.onVnodeUpdated)&&Tn((()=>{Vn(t,c,n,a)}),o)}else{let l;const{el:i,props:c}=t,{bm:a,m:u,parent:f}=e;a&&z(a),(l=c&&c.onVnodeBeforeMount)&&Vn(l,f,t);const p=e.subTree=zt(e);if(i&&ue?ue(t.el,p,e,o,null):(_(null,p,n,r,e,o,s),t.el=p.el),u&&Tn(u,o),l=c&&c.onVnodeMounted){const e=t;Tn((()=>{Vn(l,f,e)}),o)}const{a:d}=e;d&&256&t.shapeFlag&&Tn(d,o),e.isMounted=!0,t=n=r=null}}),Mn)},q=(e,t,n)=>{t.component=e;const r=e.vnode.props;e.vnode=t,e.next=null,function(e,t,n,r){const{props:o,attrs:s,vnode:{patchFlag:l}}=e,i=Ze(o),[c]=e.propsOptions;if(!(r||l>0)||16&l){let r;Qt(e,t,o,s);for(const s in i)t&&(w(t,s)||(r=L(s))!==s&&w(t,r))||(c?!n||void 0===n[s]&&void 0===n[r]||(o[s]=Yt(c,t||f,s,void 0,e)):delete o[s]);if(s!==i)for(const e in s)t&&w(t,e)||delete s[e]}else if(8&l){const n=e.vnode.dynamicProps;for(let r=0;r{const{vnode:r,slots:o}=e;let s=!0,l=f;if(32&r.shapeFlag){const e=t._;e?n&&1===e?s=!1:(y(o,t),n||1!==e||delete o._):(s=!t.$stable,On(t,o)),l=t}else t&&(Cn(e,t),l={default:1});if(s)for(const i in o)En(i)||i in l||delete o[i]})(e,t.children,n),oe(),At(void 0,e.update),se()},K=(e,t,n,r,o,s,l,i,c=!1)=>{const a=e&&e.children,f=e?e.shapeFlag:0,p=t.children,{patchFlag:d,shapeFlag:h}=t;if(d>0){if(128&d)return void H(a,p,n,r,o,s,l,i,c);if(256&d)return void G(a,p,n,r,o,s,l,i,c)}8&h?(16&f&&ne(a,o,s),p!==a&&u(n,p)):16&f?16&h?H(a,p,n,r,o,s,l,i,c):ne(a,o,s,!0):(8&f&&u(n,""),16&h&&F(p,n,r,o,s,l,i,c))},G=(e,t,n,r,o,s,l,i,c)=>{t=t||p;const a=(e=e||p).length,u=t.length,f=Math.min(a,u);let d;for(d=0;du?ne(e,o,s,!0,!1,f):F(t,n,r,o,s,l,i,c,f)},H=(e,t,n,r,o,s,l,i,c)=>{let a=0;const u=t.length;let f=e.length-1,d=u-1;for(;a<=f&&a<=d;){const r=e[a],u=t[a]=c?cr(t[a]):ir(t[a]);if(!Zn(r,u))break;_(r,u,n,null,o,s,l,i,c),a++}for(;a<=f&&a<=d;){const r=e[f],a=t[d]=c?cr(t[d]):ir(t[d]);if(!Zn(r,a))break;_(r,a,n,null,o,s,l,i,c),f--,d--}if(a>f){if(a<=d){const e=d+1,f=ed)for(;a<=f;)J(e[a],o,s,!0),a++;else{const h=a,m=a,g=new Map;for(a=m;a<=d;a++){const e=t[a]=c?cr(t[a]):ir(t[a]);null!=e.key&&g.set(e.key,a)}let v,y=0;const b=d-m+1;let w=!1,x=0;const E=new Array(b);for(a=0;a=b){J(r,o,s,!0);continue}let u;if(null!=r.key)u=g.get(r.key);else for(v=m;v<=d;v++)if(0===E[v-m]&&Zn(r,t[v])){u=v;break}void 0===u?J(r,o,s,!0):(E[u-m]=a+1,u>=x?x=u:w=!0,_(r,t[u],n,null,o,s,l,i,c),y++)}const k=w?function(e){const t=e.slice(),n=[0];let r,o,s,l,i;const c=e.length;for(r=0;r0&&(t[r]=n[s-1]),n[s]=r)}}s=n.length,l=n[s-1];for(;s-- >0;)n[s]=l,l=t[l];return n}(E):p;for(v=k.length-1,a=b-1;a>=0;a--){const e=m+a,f=t[e],p=e+1{const{el:l,type:i,transition:c,children:a,shapeFlag:u}=e;if(6&u)return void X(e.component.subTree,t,r,o);if(128&u)return void e.suspense.move(t,r,o);if(64&u)return void i.move(e,t,r,ce);if(i===qn){n(l,t,r);for(let e=0;ec.enter(l)),s);else{const{leave:e,delayLeave:o,afterLeave:s}=c,i=()=>n(l,t,r),a=()=>{e(l,(()=>{i(),s&&s()}))};o?o(l,i,a):a()}else n(l,t,r)},J=(e,t,n,r=!1,o=!1)=>{const{type:s,props:l,ref:i,children:c,dynamicChildren:a,shapeFlag:u,patchFlag:f,dirs:p}=e;if(null!=i&&In(i,null,n,null),256&u)return void t.ctx.deactivate(e);const d=1&u&&p;let h;if((h=l&&l.onVnodeBeforeUnmount)&&Vn(h,t,e),6&u)te(e.component,n,r);else{if(128&u)return void e.suspense.unmount(n,r);d&&An(e,null,t,"beforeUnmount"),64&u?e.type.remove(e,t,n,o,ce,r):a&&(s!==qn||f>0&&64&f)?ne(a,t,n,!1,!0):(s===qn&&(128&f||256&f)||!o&&16&u)&&ne(c,t,n),r&&Q(e)}((h=l&&l.onVnodeUnmounted)||d)&&Tn((()=>{h&&Vn(h,t,e),d&&An(e,null,t,"unmounted")}),n)},Q=e=>{const{type:t,el:n,anchor:o,transition:s}=e;if(t===qn)return void ee(n,o);if(t===Kn)return void O(e);const l=()=>{r(n),s&&!s.persisted&&s.afterLeave&&s.afterLeave()};if(1&e.shapeFlag&&s&&!s.persisted){const{leave:t,delayLeave:r}=s,o=()=>t(n,l);r?r(e.el,l,o):o()}else l()},ee=(e,t)=>{let n;for(;e!==t;)n=m(e),r(e),e=n;r(t)},te=(e,t,n)=>{const{bum:r,effects:o,update:s,subTree:l,um:i}=e;if(r&&z(r),o)for(let c=0;c{e.isUnmounted=!0}),t),t&&t.pendingBranch&&!t.isUnmounted&&e.asyncDep&&!e.asyncResolved&&e.suspenseId===t.pendingId&&(t.deps--,0===t.deps&&t.resolve())},ne=(e,t,n,r=!1,o=!1,s=0)=>{for(let l=s;l6&e.shapeFlag?re(e.component.subTree):128&e.shapeFlag?e.suspense.next():m(e.anchor||e.el),le=(e,t,n)=>{null==e?t._vnode&&J(t._vnode,null,null,!0):_(t._vnode||null,e,t,null,null,null,n),Pt(),t._vnode=e},ce={p:_,um:J,m:X,r:Q,mt:N,mc:F,pc:K,pbc:$,n:re,o:e};let ae,ue;t&&([ae,ue]=t(ce));return{render:le,hydrate:ae,createApp:jn(le,ae)}}(e)}function Vn(e,t,n,r=null){ut(e,t,7,[n,r])}function Nn(e,t,n=!1){const r=e.children,o=t.children;if(x(r)&&x(o))for(let s=0;snull!=e?e:null,nr=({ref:e})=>null!=e?C(e)||tt(e)||O(e)?{i:Lt,r:e}:e:null,rr=function(e,t=null,n=null,o=0,s=null,l=!1){e&&e!==Bn||(e=Wn);if(Yn(e)){const r=or(e,t,!0);return n&&ar(r,n),r}c=e,O(c)&&"__vccOpts"in c&&(e=e.__vccOpts);var c;if(t){(Ye(t)||er in t)&&(t=y({},t));let{class:e,style:n}=t;e&&!C(e)&&(t.class=i(e)),A(n)&&(Ye(n)&&!x(n)&&(n=y({},n)),t.style=r(n))}const a=C(e)?1:(e=>e.__isSuspense)(e)?128:(e=>e.__isTeleport)(e)?64:A(e)?4:O(e)?2:0,u={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&tr(t),ref:t&&nr(t),scopeId:Bt,slotScopeIds:null,children:null,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:a,patchFlag:o,dynamicProps:s,dynamicChildren:null,appContext:null};if(ar(u,n),128&a){const{content:e,fallback:t}=function(e){const{shapeFlag:t,children:n}=e;let r,o;return 32&t?(r=Xt(n.default),o=Xt(n.fallback)):(r=Xt(n),o=ir(null)),{content:r,fallback:o}}(u);u.ssContent=e,u.ssFallback=t}!l&&Hn&&(o>0||6&a)&&32!==o&&Hn.push(u);return u};function or(e,t,n=!1){const{props:o,ref:s,patchFlag:l,children:c}=e,a=t?function(...e){const t=y({},e[0]);for(let n=1;n1)return n&&O(t)?t():t}}let pr=!0;function dr(e,t,n=[],r=[],o=[],s=!1){const{mixins:l,extends:i,data:c,computed:a,methods:u,watch:p,provide:h,inject:m,components:g,directives:v,beforeMount:b,mounted:_,beforeUpdate:w,updated:E,activated:k,deactivated:S,beforeDestroy:C,beforeUnmount:R,destroyed:P,unmounted:F,render:j,renderTracked:$,renderTriggered:M,errorCaptured:T,expose:I}=t,U=e.proxy,V=e.ctx,N=e.appContext.mixins;if(s&&j&&e.render===d&&(e.render=j),s||(pr=!1,hr("beforeCreate","bc",t,e,N),pr=!0,gr(e,N,n,r,o)),i&&dr(e,i,n,r,o,!0),l&&gr(e,l,n,r,o),m)if(x(m))for(let f=0;fvr(e,t,U))),c&&vr(e,c,U)),a)for(const f in a){const e=a[f],t=Mr({get:O(e)?e.bind(U,U):O(e.get)?e.get.bind(U,U):d,set:!O(e)&&O(e.set)?e.set.bind(U):d});Object.defineProperty(V,f,{enumerable:!0,configurable:!0,get:()=>t.value,set:e=>t.value=e})}var L;if(p&&r.push(p),!s&&r.length&&r.forEach((e=>{for(const t in e)yr(e[t],V,U,t)})),h&&o.push(h),!s&&o.length&&o.forEach((e=>{const t=O(e)?e.call(U):e;Reflect.ownKeys(t).forEach((e=>{ur(e,t[e])}))})),s&&(g&&y(e.components||(e.components=y({},e.type.components)),g),v&&y(e.directives||(e.directives=y({},e.type.directives)),v)),s||hr("created","c",t,e,N),b&&ln(b.bind(U)),_&&cn(_.bind(U)),w&&an(w.bind(U)),E&&un(E.bind(U)),k&&wn(k.bind(U),"a",L),S&&function(e,t){wn(e,"da",t)}(S.bind(U)),T&&((e,t=Or)=>{on("ec",e,t)})(T.bind(U)),$&&hn($.bind(U)),M&&dn(M.bind(U)),R&&fn(R.bind(U)),F&&pn(F.bind(U)),x(I)&&!s)if(I.length){const t=e.exposed||(e.exposed=lt({}));I.forEach((e=>{t[e]=function(e,t){return tt(e[t])?e[t]:new it(e,t)}(U,e)}))}else e.exposed||(e.exposed=f)}function hr(e,t,n,r,o){for(let s=0;s{let t=e;for(let e=0;en[r];if(C(e)){const n=t[e];O(n)&&gn(o,n)}else if(O(e))gn(o,e.bind(n));else if(A(e))if(x(e))e.forEach((e=>yr(e,t,n,r)));else{const r=O(e.handler)?e.handler.bind(n):t[e.handler];O(r)&&gn(o,r,e)}}function br(e,t,n){const r=n.appContext.config.optionMergeStrategies,{mixins:o,extends:s}=t;s&&br(e,s,n),o&&o.forEach((t=>br(e,t,n)));for(const l in t)r&&w(r,l)?e[l]=r[l](e[l],t[l],n.proxy,l):e[l]=t[l]}const _r=e=>e?Rr(e)?e.exposed?e.exposed:e.proxy:_r(e.parent):null,wr=y(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>_r(e.parent),$root:e=>_r(e.root),$emit:e=>e.emit,$options:e=>function(e){const t=e.type,{__merged:n,mixins:r,extends:o}=t;if(n)return n;const s=e.appContext.mixins;if(!s.length&&!r&&!o)return t;const l={};return s.forEach((t=>br(l,t,e))),br(l,t,e),t.__merged=l}(e),$forceUpdate:e=>()=>Ot(e.update),$nextTick:e=>St.bind(e.proxy),$watch:e=>yn.bind(e)}),xr={get({_:e},t){const{ctx:n,setupState:r,data:o,props:s,accessCache:l,type:i,appContext:c}=e;if("__v_skip"===t)return!0;let a;if("$"!==t[0]){const i=l[t];if(void 0!==i)switch(i){case 0:return r[t];case 1:return o[t];case 3:return n[t];case 2:return s[t]}else{if(r!==f&&w(r,t))return l[t]=0,r[t];if(o!==f&&w(o,t))return l[t]=1,o[t];if((a=e.propsOptions[0])&&w(a,t))return l[t]=2,s[t];if(n!==f&&w(n,t))return l[t]=3,n[t];pr&&(l[t]=4)}}const u=wr[t];let p,d;return u?("$attrs"===t&&le(e,0,t),u(e)):(p=i.__cssModules)&&(p=p[t])?p:n!==f&&w(n,t)?(l[t]=3,n[t]):(d=c.config.globalProperties,w(d,t)?d[t]:void 0)},set({_:e},t,n){const{data:r,setupState:o,ctx:s}=e;if(o!==f&&w(o,t))o[t]=n;else if(r!==f&&w(r,t))r[t]=n;else if(w(e.props,t))return!1;return("$"!==t[0]||!(t.slice(1)in e))&&(s[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:r,appContext:o,propsOptions:s}},l){let i;return void 0!==n[l]||e!==f&&w(e,l)||t!==f&&w(t,l)||(i=s[0])&&w(i,l)||w(r,l)||w(wr,l)||w(o.config.globalProperties,l)}},Er=y({},xr,{get(e,t){if(t!==Symbol.unscopables)return xr.get(e,t,e)},has:(e,n)=>"_"!==n[0]&&!t(n)}),kr=Pn();let Sr=0;let Or=null;const Cr=e=>{Or=e};function Rr(e){return 4&e.vnode.shapeFlag}let Ar=!1;function Pr(e,t,n){O(t)?e.render=t:A(t)&&(e.setupState=lt(t)),Fr(e)}function Fr(e,t){const n=e.type;e.render||(e.render=n.render||d,e.render._rc&&(e.withProxy=new Proxy(e.ctx,Er))),Or=e,oe(),dr(e,n),se(),Or=null}function jr(e,t=Or){t&&(t.effects||(t.effects=[])).push(e)}function $r(e){return O(e)&&e.displayName||e.name}function Mr(e){const t=function(e){let t,n;return O(e)?(t=e,n=d):(t=e.get,n=e.set),new ct(t,n,O(e)||!e.set)}(e);return jr(t.effect),t}function Tr(e,t,n){const r=arguments.length;return 2===r?A(t)&&!x(t)?Yn(t)?rr(e,null,[t]):rr(e,t):rr(e,null,t):(r>3?n=Array.prototype.slice.call(arguments,2):3===r&&Yn(n)&&(n=[n]),rr(e,t,n))}function Ir(e,t){let n;if(x(e)||C(e)){n=new Array(e.length);for(let r=0,o=e.length;r{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{const o=t?Nr.createElementNS(Vr,e):Nr.createElement(e,n?{is:n}:void 0);return"select"===e&&r&&null!=r.multiple&&o.setAttribute("multiple",r.multiple),o},createText:e=>Nr.createTextNode(e),createComment:e=>Nr.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Nr.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},cloneNode(e){const t=e.cloneNode(!0);return"_value"in e&&(t._value=e._value),t},insertStaticContent(e,t,n,r){const o=r?Br||(Br=Nr.createElementNS(Vr,"svg")):Lr||(Lr=Nr.createElement("div"));o.innerHTML=e;const s=o.firstChild;let l=s,i=l;for(;l;)i=l,Dr.insert(l,t,n),l=o.firstChild;return[s,i]}};const qr=/\s*!important$/;function zr(e,t,n){if(x(n))n.forEach((n=>zr(e,t,n)));else if(t.startsWith("--"))e.setProperty(t,n);else{const r=function(e,t){const n=Kr[t];if(n)return n;let r=V(t);if("filter"!==r&&r in e)return Kr[t]=r;r=B(r);for(let o=0;odocument.createEvent("Event").timeStamp&&(Hr=()=>performance.now());const e=navigator.userAgent.match(/firefox\/(\d+)/i);Xr=!!(e&&Number(e[1])<=53)}let Jr=0;const Qr=Promise.resolve(),Yr=()=>{Jr=0};function Zr(e,t,n,r){e.addEventListener(t,n,r)}function eo(e,t,n,r,o=null){const s=e._vei||(e._vei={}),l=s[t];if(r&&l)l.value=r;else{const[n,i]=function(e){let t;if(to.test(e)){let n;for(t={};n=e.match(to);)e=e.slice(0,e.length-n[0].length),t[n[0].toLowerCase()]=!0}return[L(e.slice(2)),t]}(t);if(r){Zr(e,n,s[t]=function(e,t){const n=e=>{const r=e.timeStamp||Hr();(Xr||r>=n.attached-1)&&ut(function(e,t){if(x(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map((e=>t=>!t._stopped&&e(t)))}return t}(e,n.value),t,5,[e])};return n.value=e,n.attached=(()=>Jr||(Qr.then(Yr),Jr=Hr()))(),n}(r,o),i)}else l&&(!function(e,t,n,r){e.removeEventListener(t,n,r)}(e,n,l,i),s[t]=void 0)}}const to=/(?:Once|Passive|Capture)$/;const no=/^on[a-z]/;const ro=e=>{const t=e.props["onUpdate:modelValue"];return x(t)?e=>z(t,e):t};function oo(e){e.target.composing=!0}function so(e){const t=e.target;t.composing&&(t.composing=!1,function(e,t){const n=document.createEvent("HTMLEvents");n.initEvent(t,!0,!0),e.dispatchEvent(n)}(t,"input"))}const lo={created(e,{modifiers:{lazy:t,trim:n,number:r}},o){e._assign=ro(o);const s=r||"number"===e.type;Zr(e,t?"change":"input",(t=>{if(t.target.composing)return;let r=e.value;n?r=r.trim():s&&(r=K(r)),e._assign(r)})),n&&Zr(e,"change",(()=>{e.value=e.value.trim()})),t||(Zr(e,"compositionstart",oo),Zr(e,"compositionend",so),Zr(e,"change",so))},mounted(e,{value:t}){e.value=null==t?"":t},beforeUpdate(e,{value:t,modifiers:{trim:n,number:r}},o){if(e._assign=ro(o),e.composing)return;if(document.activeElement===e){if(n&&e.value.trim()===t)return;if((r||"number"===e.type)&&K(e.value)===t)return}const s=null==t?"":t;e.value!==s&&(e.value=s)}},io={created(e,{value:t},n){e.checked=c(t,n.props.value),e._assign=ro(n),Zr(e,"change",(()=>{e._assign(function(e){return"_value"in e?e._value:e.value}(e))}))},beforeUpdate(e,{value:t,oldValue:n},r){e._assign=ro(r),t!==n&&(e.checked=c(t,r.props.value))}};const co=["ctrl","shift","alt","meta"],ao={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&0!==e.button,middle:e=>"button"in e&&1!==e.button,right:e=>"button"in e&&2!==e.button,exact:(e,t)=>co.some((n=>e[`${n}Key`]&&!t.includes(n)))},uo=(e,t)=>(n,...r)=>{for(let e=0;e{po(e,!1)})):po(e,t))},beforeUnmount(e,{value:t}){po(e,t)}};function po(e,t){e.style.display=t?e._vod:"none"}const ho=y({patchProp:(e,t,r,o,s=!1,l,i,c,a)=>{switch(t){case"class":!function(e,t,n){if(null==t&&(t=""),n)e.setAttribute("class",t);else{const n=e._vtc;n&&(t=(t?[t,...n]:[...n]).join(" ")),e.className=t}}(e,o,s);break;case"style":!function(e,t,n){const r=e.style;if(n)if(C(n)){if(t!==n){const t=r.display;r.cssText=n,"_vod"in e&&(r.display=t)}}else{for(const e in n)zr(r,e,n[e]);if(t&&!C(t))for(const e in t)null==n[e]&&zr(r,e,"")}else e.removeAttribute("style")}(e,r,o);break;default:g(t)?v(t)||eo(e,t,0,o,i):function(e,t,n,r){if(r)return"innerHTML"===t||!!(t in e&&no.test(t)&&O(n));if("spellcheck"===t||"draggable"===t)return!1;if("form"===t)return!1;if("list"===t&&"INPUT"===e.tagName)return!1;if("type"===t&&"TEXTAREA"===e.tagName)return!1;if(no.test(t)&&C(n))return!1;return t in e}(e,t,o,s)?function(e,t,n,r,o,s,l){if("innerHTML"===t||"textContent"===t)return r&&l(r,o,s),void(e[t]=null==n?"":n);if("value"!==t||"PROGRESS"===e.tagName){if(""===n||null==n){const r=typeof e[t];if(""===n&&"boolean"===r)return void(e[t]=!0);if(null==n&&"string"===r)return e[t]="",void e.removeAttribute(t);if("number"===r)return e[t]=0,void e.removeAttribute(t)}try{e[t]=n}catch(i){}}else{e._value=n;const t=null==n?"":n;e.value!==t&&(e.value=t)}}(e,t,o,l,i,c,a):("true-value"===t?e._trueValue=o:"false-value"===t&&(e._falseValue=o),function(e,t,r,o){if(o&&t.startsWith("xlink:"))null==r?e.removeAttributeNS(Gr,t.slice(6,t.length)):e.setAttributeNS(Gr,t,r);else{const o=n(t);null==r||o&&!1===r?e.removeAttribute(t):e.setAttribute(t,o?"":r)}}(e,t,o,s))}},forcePatchProp:(e,t)=>"value"===t},Dr);let mo;const go=(...e)=>{const t=(mo||(mo=Un(ho))).createApp(...e),{mount:n}=t;return t.mount=e=>{const r=function(e){if(C(e)){return document.querySelector(e)}return e} +/*! + * vue-router v4.0.8 + * (c) 2021 Eduardo San Martin Morote + * @license MIT + */(e);if(!r)return;const o=t._component;O(o)||o.render||o.template||(o.template=r.innerHTML),r.innerHTML="";const s=n(r,!1,r instanceof SVGElement);return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")),s},t};const vo="function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag,yo=e=>vo?Symbol(e):"_vr_"+e,bo=yo("rvlm"),_o=yo("rvd"),wo=yo("r"),xo=yo("rl"),Eo=yo("rvl"),ko="undefined"!=typeof window;const So=Object.assign;function Oo(e,t){const n={};for(const r in t){const o=t[r];n[r]=Array.isArray(o)?o.map(e):e(o)}return n}let Co=()=>{};const Ro=/\/$/;function Ao(e,t,n="/"){let r,o={},s="",l="";const i=t.indexOf("?"),c=t.indexOf("#",i>-1?i:0);return i>-1&&(r=t.slice(0,i),s=t.slice(i+1,c>-1?c:t.length),o=e(s)),c>-1&&(r=r||t.slice(0,c),l=t.slice(c,t.length)),r=function(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t.split("/"),r=e.split("/");let o,s,l=n.length-1;for(o=0;oe===t[n])):1===e.length&&e[0]===t}var To,Io,Uo,Vo;function No(e){if(!e)if(ko){const t=document.querySelector("base");e=(e=t&&t.getAttribute("href")||"/").replace(/^\w+:\/\/[^\/]+/,"")}else e="/";return"/"!==e[0]&&"#"!==e[0]&&(e="/"+e),e.replace(Ro,"")}(Io=To||(To={})).pop="pop",Io.push="push",(Vo=Uo||(Uo={})).back="back",Vo.forward="forward",Vo.unknown="";const Lo=/^[^#]+#/;function Bo(e,t){return e.replace(Lo,"#")+t}const Do=()=>({left:window.pageXOffset,top:window.pageYOffset});function qo(e){let t;if("el"in e){let n=e.el;const r="string"==typeof n&&n.startsWith("#"),o="string"==typeof n?r?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!o)return;t=function(e,t){const n=document.documentElement.getBoundingClientRect(),r=e.getBoundingClientRect();return{behavior:t.behavior,left:r.left-n.left-(t.left||0),top:r.top-n.top-(t.top||0)}}(o,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(null!=t.left?t.left:window.pageXOffset,null!=t.top?t.top:window.pageYOffset)}function zo(e,t){return(history.state?history.state.position-t:-1)+e}const Wo=new Map;function Ko(e,t){const{pathname:n,search:r,hash:o}=t,s=e.indexOf("#");if(s>-1){let t=o.includes(e.slice(s))?e.slice(s).length:1,n=o.slice(t);return"/"!==n[0]&&(n="/"+n),Po(n,"")}return Po(n,e)+r+o}function Go(e,t,n,r=!1,o=!1){return{back:e,current:t,forward:n,replaced:r,position:window.history.length,scroll:o?Do():null}}function Ho(e){const{history:t,location:n}=window;let r={value:Ko(e,n)},o={value:t.state};function s(r,s,l){const i=e.indexOf("#"),c=i>-1?(n.host&&document.querySelector("base")?e:e.slice(i))+r:location.protocol+"//"+location.host+e+r;try{t[l?"replaceState":"pushState"](s,"",c),o.value=s}catch(a){console.error(a),n[l?"replace":"assign"](c)}}return o.value||s(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0),{location:r,state:o,push:function(e,n){const l=So({},o.value,t.state,{forward:e,scroll:Do()});s(l.current,l,!0),s(e,So({},Go(r.value,e,null),{position:l.position+1},n),!1),r.value=e},replace:function(e,n){s(e,So({},t.state,Go(o.value.back,e,o.value.forward,!0),n,{position:o.value.position}),!0),r.value=e}}}function Xo(e){const t=Ho(e=No(e)),n=function(e,t,n,r){let o=[],s=[],l=null;const i=({state:s})=>{const i=Ko(e,location),c=n.value,a=t.value;let u=0;if(s){if(n.value=i,t.value=s,l&&l===c)return void(l=null);u=a?s.position-a.position:0}else r(i);o.forEach((e=>{e(n.value,c,{delta:u,type:To.pop,direction:u?u>0?Uo.forward:Uo.back:Uo.unknown})}))};function c(){const{history:e}=window;e.state&&e.replaceState(So({},e.state,{scroll:Do()}),"")}return window.addEventListener("popstate",i),window.addEventListener("beforeunload",c),{pauseListeners:function(){l=n.value},listen:function(e){o.push(e);const t=()=>{const t=o.indexOf(e);t>-1&&o.splice(t,1)};return s.push(t),t},destroy:function(){for(const e of s)e();s=[],window.removeEventListener("popstate",i),window.removeEventListener("beforeunload",c)}}}(e,t.state,t.location,t.replace);const r=So({location:"",base:e,go:function(e,t=!0){t||n.pauseListeners(),history.go(e)},createHref:Bo.bind(null,e)},t,n);return Object.defineProperty(r,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(r,"state",{enumerable:!0,get:()=>t.state.value}),r}function Jo(e){return(e=location.host?e||location.pathname+location.search:"").indexOf("#")<0&&(e+="#"),Xo(e)}function Qo(e){return"string"==typeof e||"symbol"==typeof e}const Yo={path:"/",name:void 0,params:{},query:{},hash:"",fullPath:"/",matched:[],meta:{},redirectedFrom:void 0},Zo=yo("nf");var es,ts;function ns(e,t){return So(new Error,{type:e,[Zo]:!0},t)}function rs(e,t){return e instanceof Error&&Zo in e&&(null==t||!!(e.type&t))}(ts=es||(es={}))[ts.aborted=4]="aborted",ts[ts.cancelled=8]="cancelled",ts[ts.duplicated=16]="duplicated";const os={sensitive:!1,strict:!1,start:!0,end:!0},ss=/[.+*?^${}()[\]/\\]/g;function ls(e,t){let n=0;for(;nt.length?1===t.length&&80===t[0]?1:-1:0}function is(e,t){let n=0;const r=e.score,o=t.score;for(;n1&&("*"===i||"+"===i)&&t(`A repeatable param (${a}) must be alone in its segment. eg: '/:ids+.`),s.push({type:1,value:a,regexp:u,repeatable:"*"===i||"+"===i,optional:"*"===i||"?"===i})):t("Invalid state to consume buffer"),a="")}function p(){a+=i}for(;c{s(p)}:Co}function s(e){if(Qo(e)){const t=r.get(e);t&&(r.delete(e),n.splice(n.indexOf(t),1),t.children.forEach(s),t.alias.forEach(s))}else{let t=n.indexOf(e);t>-1&&(n.splice(t,1),e.record.name&&r.delete(e.record.name),e.children.forEach(s),e.alias.forEach(s))}}function l(e){let t=0;for(;t=0;)t++;n.splice(t,0,e),e.record.name&&!ds(e)&&r.set(e.record.name,e)}return t=ms({strict:!1,end:!0,sensitive:!1},t),e.forEach((e=>o(e))),{addRoute:o,resolve:function(e,t){let o,s,l,i={};if("name"in e&&e.name){if(o=r.get(e.name),!o)throw ns(1,{location:e});l=o.record.name,i=So(function(e,t){let n={};for(let r of t)r in e&&(n[r]=e[r]);return n}(t.params,o.keys.filter((e=>!e.optional)).map((e=>e.name))),e.params),s=o.stringify(i)}else if("path"in e)s=e.path,o=n.find((e=>e.re.test(s))),o&&(i=o.parse(s),l=o.record.name);else{if(o=t.name?r.get(t.name):n.find((e=>e.re.test(t.path))),!o)throw ns(1,{location:e,currentLocation:t});l=o.record.name,i=So({},t.params,e.params),s=o.stringify(i)}const c=[];let a=o;for(;a;)c.unshift(a.record),a=a.parent;return{name:l,path:s,params:i,matched:c,meta:hs(c)}},removeRoute:s,getRoutes:function(){return n},getRecordMatcher:function(e){return r.get(e)}}}function ps(e){const t={},n=e.props||!1;if("component"in e)t.default=n;else for(let r in e.components)t[r]="boolean"==typeof n?n:n[r];return t}function ds(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function hs(e){return e.reduce(((e,t)=>So(e,t.meta)),{})}function ms(e,t){let n={};for(let r in e)n[r]=r in t?t[r]:e[r];return n}const gs=/#/g,vs=/&/g,ys=/\//g,bs=/=/g,_s=/\?/g,ws=/\+/g,xs=/%5B/g,Es=/%5D/g,ks=/%5E/g,Ss=/%60/g,Os=/%7B/g,Cs=/%7C/g,Rs=/%7D/g,As=/%20/g;function Ps(e){return encodeURI(""+e).replace(Cs,"|").replace(xs,"[").replace(Es,"]")}function Fs(e){return Ps(e).replace(ws,"%2B").replace(As,"+").replace(gs,"%23").replace(vs,"%26").replace(Ss,"`").replace(Os,"{").replace(Rs,"}").replace(ks,"^")}function js(e){return function(e){return Ps(e).replace(gs,"%23").replace(_s,"%3F")}(e).replace(ys,"%2F")}function $s(e){try{return decodeURIComponent(""+e)}catch(t){}return""+e}function Ms(e){const t={};if(""===e||"?"===e)return t;const n=("?"===e[0]?e.slice(1):e).split("&");for(let r=0;re&&Fs(e))):[r&&Fs(r)]).forEach((e=>{void 0!==e&&(t+=(t.length?"&":"")+n,null!=e&&(t+="="+e))}))}return t}function Is(e){const t={};for(let n in e){let r=e[n];void 0!==r&&(t[n]=Array.isArray(r)?r.map((e=>null==e?null:""+e)):null==r?r:""+r)}return t}function Us(){let e=[];return{add:function(t){return e.push(t),()=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)}},list:()=>e,reset:function(){e=[]}}}function Vs(e,t,n,r,o){const s=r&&(r.enterCallbacks[o]=r.enterCallbacks[o]||[]);return()=>new Promise(((l,i)=>{const c=e=>{var c;!1===e?i(ns(4,{from:n,to:t})):e instanceof Error?i(e):"string"==typeof(c=e)||c&&"object"==typeof c?i(ns(2,{from:t,to:e})):(s&&r.enterCallbacks[o]===s&&"function"==typeof e&&s.push(e),l())},a=e.call(r&&r.instances[o],t,n,c);let u=Promise.resolve(a);e.length<3&&(u=u.then(c)),u.catch((e=>i(e)))}))}function Ns(e,t,n,r){const o=[];for(const l of e)for(const e in l.components){let i=l.components[e];if("beforeRouteEnter"===t||l.instances[e])if("object"==typeof(s=i)||"displayName"in s||"props"in s||"__vccOpts"in s){const s=(i.__vccOpts||i)[t];s&&o.push(Vs(s,n,r,l,e))}else{let s=i();s=s.catch(console.error),o.push((()=>s.then((o=>{if(!o)return Promise.reject(new Error(`Couldn't resolve component "${e}" at "${l.path}"`));const s=(i=o).__esModule||vo&&"Module"===i[Symbol.toStringTag]?o.default:o;var i;l.components[e]=s;const c=(s.__vccOpts||s)[t];return c&&Vs(c,n,r,l,e)()}))))}}var s;return o}function Ls(e){const t=fr(wo),n=fr(xo),r=Mr((()=>t.resolve(ot(e.to)))),o=Mr((()=>{let{matched:e}=r.value,{length:t}=e;const o=e[t-1];let s=n.matched;if(!o||!s.length)return-1;let l=s.findIndex(Fo.bind(null,o));if(l>-1)return l;let i=Ds(e[t-2]);return t>1&&Ds(o)===i&&s[s.length-1].path!==i?s.findIndex(Fo.bind(null,e[t-2])):l})),s=Mr((()=>o.value>-1&&function(e,t){for(let n in t){let r=t[n],o=e[n];if("string"==typeof r){if(r!==o)return!1}else if(!Array.isArray(o)||o.length!==r.length||r.some(((e,t)=>e!==o[t])))return!1}return!0}(n.params,r.value.params))),l=Mr((()=>o.value>-1&&o.value===n.matched.length-1&&jo(n.params,r.value.params)));return{route:r,href:Mr((()=>r.value.href)),isActive:s,isExactActive:l,navigate:function(n={}){return function(e){if(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)return;if(e.defaultPrevented)return;if(void 0!==e.button&&0!==e.button)return;if(e.currentTarget&&e.currentTarget.getAttribute){const t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}e.preventDefault&&e.preventDefault();return!0}(n)?t[ot(e.replace)?"replace":"push"](ot(e.to)):Promise.resolve()}}}const Bs=$n({name:"RouterLink",props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:"page"}},setup(e,{slots:t}){const n=Ge(Ls(e)),{options:r}=fr(wo),o=Mr((()=>({[qs(e.activeClass,r.linkActiveClass,"router-link-active")]:n.isActive,[qs(e.exactActiveClass,r.linkExactActiveClass,"router-link-exact-active")]:n.isExactActive})));return()=>{const r=t.default&&t.default(n);return e.custom?r:Tr("a",{"aria-current":n.isExactActive?e.ariaCurrentValue:null,href:n.href,onClick:n.navigate,class:o.value},r)}}});function Ds(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}const qs=(e,t,n)=>null!=e?e:null!=t?t:n;function zs(e,t){if(!e)return null;const n=e(t);return 1===n.length?n[0]:n}const Ws=$n({name:"RouterView",inheritAttrs:!1,props:{name:{type:String,default:"default"},route:Object},setup(e,{attrs:t,slots:n}){const r=fr(Eo),o=Mr((()=>e.route||r.value)),s=fr(_o,0),l=Mr((()=>o.value.matched[s]));ur(_o,s+1),ur(bo,l),ur(Eo,o);const i=rt(c);var c;return gn((()=>[i.value,l.value,e.name]),(([e,t,n],[r,o,s])=>{t&&(t.instances[n]=e,o&&o!==t&&e&&e===r&&(t.leaveGuards.size||(t.leaveGuards=o.leaveGuards),t.updateGuards.size||(t.updateGuards=o.updateGuards))),!e||!t||o&&Fo(t,o)&&r||(t.enterCallbacks[n]||[]).forEach((t=>t(e)))}),{flush:"post"}),()=>{const r=o.value,s=l.value,c=s&&s.components[e.name],a=e.name;if(!c)return zs(n.default,{Component:c,route:r});const u=s.props[e.name],f=u?!0===u?r.params:"function"==typeof u?u(r):u:null,p=Tr(c,So({},f,t,{onVnodeUnmounted:e=>{e.component.isUnmounted&&(s.instances[a]=null)},ref:i}));return zs(n.default,{Component:p,route:r})||p}}});function Ks(e){const t=fs(e.routes,e);let n=e.parseQuery||Ms,r=e.stringifyQuery||Ts,o=e.history;const s=Us(),l=Us(),i=Us(),c=rt(Yo,!0);let a=Yo;ko&&e.scrollBehavior&&"scrollRestoration"in history&&(history.scrollRestoration="manual");const u=Oo.bind(null,(e=>""+e)),f=Oo.bind(null,js),p=Oo.bind(null,$s);function d(e,s){if(s=So({},s||c.value),"string"==typeof e){let r=Ao(n,e,s.path),l=t.resolve({path:r.path},s),i=o.createHref(r.fullPath);return So(r,l,{params:p(l.params),hash:$s(r.hash),redirectedFrom:void 0,href:i})}let l;"path"in e?l=So({},e,{path:Ao(n,e.path,s.path).path}):(l=So({},e,{params:f(e.params)}),s.params=f(s.params));let i=t.resolve(l,s);const a=e.hash||"";i.params=u(p(i.params));const d=function(e,t){let n=t.query?e(t.query):"";return t.path+(n&&"?")+n+(t.hash||"")}(r,So({},e,{hash:(h=a,Ps(h).replace(Os,"{").replace(Rs,"}").replace(ks,"^")),path:i.path}));var h;let m=o.createHref(d);return So({fullPath:d,hash:a,query:r===Ts?Is(e.query):e.query},i,{redirectedFrom:void 0,href:m})}function h(e){return"string"==typeof e?Ao(n,e,c.value.path):So({},e)}function m(e,t){if(a!==e)return ns(8,{from:t,to:e})}function g(e){return y(e)}function v(e){const t=e.matched[e.matched.length-1];if(t&&t.redirect){const{redirect:n}=t;let r="function"==typeof n?n(e):n;return"string"==typeof r&&(r=r.indexOf("?")>-1||r.indexOf("#")>-1?r=h(r):{path:r}),So({query:e.query,hash:e.hash,params:e.params},r)}}function y(e,t){const n=a=d(e),o=c.value,s=e.state,l=e.force,i=!0===e.replace,u=v(n);if(u)return y(So(h(u),{state:s,force:l,replace:i}),t||n);const f=n;let p;return f.redirectedFrom=t,!l&&function(e,t,n){let r=t.matched.length-1,o=n.matched.length-1;return r>-1&&r===o&&Fo(t.matched[r],n.matched[o])&&jo(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}(r,o,n)&&(p=ns(16,{to:f,from:o}),P(o,o,!0,!1)),(p?Promise.resolve(p):_(f,o)).catch((e=>rs(e)?e:R(e))).then((e=>{if(e){if(rs(e,2))return y(So(h(e.to),{state:s,force:l,replace:i}),t||f)}else e=x(f,o,!0,i,s);return w(f,o,e),e}))}function b(e,t){const n=m(e,t);return n?Promise.reject(n):Promise.resolve()}function _(e,t){let n;const[r,o,i]=function(e,t){const n=[],r=[],o=[],s=Math.max(t.matched.length,e.matched.length);for(let l=0;lFo(e,s)))?r.push(s):n.push(s));const i=e.matched[l];i&&(t.matched.find((e=>Fo(e,i)))||o.push(i))}return[n,r,o]}(e,t);n=Ns(r.reverse(),"beforeRouteLeave",e,t);for(const s of r)s.leaveGuards.forEach((r=>{n.push(Vs(r,e,t))}));const c=b.bind(null,e,t);return n.push(c),Gs(n).then((()=>{n=[];for(const r of s.list())n.push(Vs(r,e,t));return n.push(c),Gs(n)})).then((()=>{n=Ns(o,"beforeRouteUpdate",e,t);for(const r of o)r.updateGuards.forEach((r=>{n.push(Vs(r,e,t))}));return n.push(c),Gs(n)})).then((()=>{n=[];for(const r of e.matched)if(r.beforeEnter&&t.matched.indexOf(r)<0)if(Array.isArray(r.beforeEnter))for(const o of r.beforeEnter)n.push(Vs(o,e,t));else n.push(Vs(r.beforeEnter,e,t));return n.push(c),Gs(n)})).then((()=>(e.matched.forEach((e=>e.enterCallbacks={})),n=Ns(i,"beforeRouteEnter",e,t),n.push(c),Gs(n)))).then((()=>{n=[];for(const r of l.list())n.push(Vs(r,e,t));return n.push(c),Gs(n)})).catch((e=>rs(e,8)?e:Promise.reject(e)))}function w(e,t,n){for(const r of i.list())r(e,t,n)}function x(e,t,n,r,s){const l=m(e,t);if(l)return l;const i=t===Yo,a=ko?history.state:{};n&&(r||i?o.replace(e.fullPath,So({scroll:i&&a&&a.scroll},s)):o.push(e.fullPath,s)),c.value=e,P(e,t,n,i),A()}let E;function k(){E=o.listen(((e,t,n)=>{let r=d(e);const s=v(r);if(s)return void y(So(s,{replace:!0}),r).catch(Co);a=r;const l=c.value;var i,u;ko&&(i=zo(l.fullPath,n.delta),u=Do(),Wo.set(i,u)),_(r,l).catch((e=>rs(e,12)?e:rs(e,2)?(y(e.to,r).catch(Co),Promise.reject()):(n.delta&&o.go(-n.delta,!1),R(e)))).then((e=>{(e=e||x(r,l,!1))&&n.delta&&o.go(-n.delta,!1),w(r,l,e)})).catch(Co)}))}let S,O=Us(),C=Us();function R(e){return A(e),C.list().forEach((t=>t(e))),Promise.reject(e)}function A(e){S||(S=!0,k(),O.list().forEach((([t,n])=>e?n(e):t())),O.reset())}function P(t,n,r,o){const{scrollBehavior:s}=e;if(!ko||!s)return Promise.resolve();let l=!r&&function(e){const t=Wo.get(e);return Wo.delete(e),t}(zo(t.fullPath,0))||(o||!r)&&history.state&&history.state.scroll||null;return St().then((()=>s(t,n,l))).then((e=>e&&qo(e))).catch(R)}const F=e=>o.go(e);let j;const $=new Set;return{currentRoute:c,addRoute:function(e,n){let r,o;return Qo(e)?(r=t.getRecordMatcher(e),o=n):o=e,t.addRoute(o,r)},removeRoute:function(e){let n=t.getRecordMatcher(e);n&&t.removeRoute(n)},hasRoute:function(e){return!!t.getRecordMatcher(e)},getRoutes:function(){return t.getRoutes().map((e=>e.record))},resolve:d,options:e,push:g,replace:function(e){return g(So(h(e),{replace:!0}))},go:F,back:()=>F(-1),forward:()=>F(1),beforeEach:s.add,beforeResolve:l.add,afterEach:i.add,onError:C.add,isReady:function(){return S&&c.value!==Yo?Promise.resolve():new Promise(((e,t)=>{O.add([e,t])}))},install(e){e.component("RouterLink",Bs),e.component("RouterView",Ws),e.config.globalProperties.$router=this,Object.defineProperty(e.config.globalProperties,"$route",{enumerable:!0,get:()=>ot(c)}),ko&&!j&&c.value===Yo&&(j=!0,g(o.location).catch((e=>{})));const t={};for(let r in Yo)t[r]=Mr((()=>c.value[r]));e.provide(wo,this),e.provide(xo,Ge(t)),e.provide(Eo,c);let n=e.unmount;$.add(e),e.unmount=function(){$.delete(e),$.size<1&&(E(),c.value=Yo,j=!1,S=!1),n()}}}}function Gs(e){return e.reduce(((e,t)=>e.then((()=>t()))),Promise.resolve())}export{qn as F,rr as a,lr as b,Qn as c,$n as d,sr as e,Ir as f,Rn as g,io as h,Vt as i,uo as j,fo as k,Ks as l,Jo as m,go as n,Xn as o,Ln as r,a as t,lo as v,qt as w}; diff --git a/build/public/index.html b/build/public/index.html new file mode 100644 index 0000000..6dcc823 --- /dev/null +++ b/build/public/index.html @@ -0,0 +1,13 @@ + + + + 🧩 jigsaw.hyottoko.club + + + + + +
+ + + diff --git a/build/server/main.js b/build/server/main.js new file mode 100644 index 0000000..23fc05d --- /dev/null +++ b/build/server/main.js @@ -0,0 +1,1838 @@ +import WebSocket from 'ws'; +import express from 'express'; +import multer from 'multer'; +import fs from 'fs'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; +import sizeOf from 'image-size'; +import exif from 'exif'; +import sharp from 'sharp'; +import bodyParser from 'body-parser'; +import v8 from 'v8'; + +class Rng { + constructor(seed) { + this.rand_high = seed || 0xDEADC0DE; + this.rand_low = seed ^ 0x49616E42; + } + random(min, max) { + this.rand_high = ((this.rand_high << 16) + (this.rand_high >> 16) + this.rand_low) & 0xffffffff; + this.rand_low = (this.rand_low + this.rand_high) & 0xffffffff; + var n = (this.rand_high >>> 0) / 0xffffffff; + return (min + n * (max - min + 1)) | 0; + } + static serialize(rng) { + return { + rand_high: rng.rand_high, + rand_low: rng.rand_low + }; + } + static unserialize(rngSerialized) { + const rng = new Rng(0); + rng.rand_high = rngSerialized.rand_high; + rng.rand_low = rngSerialized.rand_low; + return rng; + } +} + +const pad = (x, pad) => { + const str = `${x}`; + if (str.length >= pad.length) { + return str; + } + return pad.substr(0, pad.length - str.length) + str; +}; +const logger = (...pre) => { + const log = (m) => (...args) => { + const d = new Date(); + const hh = pad(d.getHours(), '00'); + const mm = pad(d.getMinutes(), '00'); + const ss = pad(d.getSeconds(), '00'); + console[m](`${hh}:${mm}:${ss}`, ...pre, ...args); + }; + return { + log: log('log'), + error: log('error'), + info: log('info'), + }; +}; +// get a unique id +const uniqId = () => Date.now().toString(36) + Math.random().toString(36).substring(2); +// get a random int between min and max (inclusive) +const randomInt = (rng, min, max) => rng.random(min, max); +// get one random item from the given array +const choice = (rng, array) => array[randomInt(rng, 0, array.length - 1)]; +// return a shuffled (shallow) copy of the given array +const shuffle = (rng, array) => { + const arr = array.slice(); + for (let i = 0; i <= arr.length - 2; i++) { + const j = randomInt(rng, i, arr.length - 1); + const tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + return arr; +}; +function encodeShape(data) { + if (typeof data === 'number') { + return data; + } + /* encoded in 1 byte: + 00000000 + ^^ top + ^^ right + ^^ bottom + ^^ left + */ + return ((data.top + 1) << 0) + | ((data.right + 1) << 2) + | ((data.bottom + 1) << 4) + | ((data.left + 1) << 6); +} +function decodeShape(data) { + if (typeof data !== 'number') { + return data; + } + return { + top: (data >> 0 & 0b11) - 1, + right: (data >> 2 & 0b11) - 1, + bottom: (data >> 4 & 0b11) - 1, + left: (data >> 6 & 0b11) - 1, + }; +} +function encodeTile(data) { + if (Array.isArray(data)) { + return data; + } + return [data.idx, data.pos.x, data.pos.y, data.z, data.owner, data.group]; +} +function decodeTile(data) { + if (!Array.isArray(data)) { + return data; + } + return { + idx: data[0], + pos: { + x: data[1], + y: data[2], + }, + z: data[3], + owner: data[4], + group: data[5], + }; +} +function encodePlayer(data) { + if (Array.isArray(data)) { + return data; + } + return [ + data.id, + data.x, + data.y, + data.d, + data.name, + data.color, + data.bgcolor, + data.points, + data.ts, + ]; +} +function decodePlayer(data) { + if (!Array.isArray(data)) { + return data; + } + return { + id: data[0], + x: data[1], + y: data[2], + d: data[3], + name: data[4], + color: data[5], + bgcolor: data[6], + points: data[7], + ts: data[8], + }; +} +function encodeGame(data) { + if (Array.isArray(data)) { + return data; + } + return [ + data.id, + data.rng.type, + Rng.serialize(data.rng.obj), + data.puzzle, + data.players, + data.evtInfos, + data.scoreMode, + ]; +} +function decodeGame(data) { + if (!Array.isArray(data)) { + return data; + } + return { + id: data[0], + rng: { + type: data[1], + obj: Rng.unserialize(data[2]), + }, + puzzle: data[3], + players: data[4], + evtInfos: data[5], + scoreMode: data[6], + }; +} +function coordByTileIdx(info, tileIdx) { + const wTiles = info.width / info.tileSize; + return { + x: tileIdx % wTiles, + y: Math.floor(tileIdx / wTiles), + }; +} +const hash = (str) => { + let hash = 0; + for (let i = 0; i < str.length; i++) { + let chr = str.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; // Convert to 32bit integer + } + return hash; +}; +var Util = { + hash, + uniqId, + randomInt, + choice, + shuffle, + encodeShape, + decodeShape, + encodeTile, + decodeTile, + encodePlayer, + decodePlayer, + encodeGame, + decodeGame, + coordByTileIdx, +}; + +const log$4 = logger('WebSocketServer.js'); +/* +Example config + +config = { + hostname: 'localhost', + port: 1338, + connectstring: `ws://localhost:1338/ws`, +} +*/ +class EvtBus { + constructor() { + this._on = {}; + } + on(type, callback) { + this._on[type] = this._on[type] || []; + this._on[type].push(callback); + } + dispatch(type, ...args) { + (this._on[type] || []).forEach((cb) => { + cb(...args); + }); + } +} +class WebSocketServer { + constructor(config) { + this.config = config; + this._websocketserver = null; + this.evt = new EvtBus(); + } + on(type, callback) { + this.evt.on(type, callback); + } + listen() { + this._websocketserver = new WebSocket.Server(this.config); + this._websocketserver.on('connection', (socket, request) => { + const pathname = new URL(this.config.connectstring).pathname; + if (request.url.indexOf(pathname) !== 0) { + log$4.log('bad request url: ', request.url); + socket.close(); + return; + } + socket.on('message', (data) => { + log$4.log(`ws`, socket.protocol, data); + this.evt.dispatch('message', { socket, data }); + }); + socket.on('close', () => { + this.evt.dispatch('close', { socket }); + }); + }); + } + close() { + if (this._websocketserver) { + this._websocketserver.close(); + } + } + notifyOne(data, socket) { + socket.send(JSON.stringify(data)); + } +} + +/* +SERVER_CLIENT_MESSAGE_PROTOCOL +NOTE: clients always send game id and their id + when creating sockets (via socket.protocol), so + this doesn't need to be set in each message data + +NOTE: first value in the array is always the type of event/message + when describing them below, the value each has is used + instead of writing EVENT_TYPE or something ismilar + + +EV_CLIENT_EVENT: event triggered by clients and sent to server +[ + EV_CLIENT_EVENT, // constant value, type of event + CLIENT_SEQ, // sequence number sent by client. + EV_DATA, // (eg. mouse input info) +] + +EV_SERVER_EVENT: event sent to clients after recieving a client + event and processing it +[ + EV_SERVER_EVENT, // constant value, type of event + CLIENT_ID, // user who sent the client event + CLIENT_SEQ, // sequence number of the client event msg + CHANGES_TRIGGERED_BY_CLIENT_EVENT, +] + +EV_CLIENT_INIT: event sent by client to enter a game +[ + EV_CLIENT_INIT, // constant value, type of event +] + +EV_SERVER_INIT: event sent to one client after that client + connects to a game +[ + EV_SERVER_INIT, // constant value, type of event + GAME, // complete game instance required by + // client to build client side of the game +] +*/ +const EV_SERVER_EVENT = 1; +const EV_SERVER_INIT = 4; +const EV_SERVER_INIT_REPLAY = 5; +const EV_CLIENT_EVENT = 2; +const EV_CLIENT_INIT = 3; +const EV_CLIENT_INIT_REPLAY = 6; +const LOG_HEADER = 1; +const LOG_ADD_PLAYER = 2; +const LOG_UPDATE_PLAYER = 4; +const LOG_HANDLE_INPUT = 3; +const INPUT_EV_MOUSE_DOWN = 1; +const INPUT_EV_MOUSE_UP = 2; +const INPUT_EV_MOUSE_MOVE = 3; +const INPUT_EV_ZOOM_IN = 4; +const INPUT_EV_ZOOM_OUT = 5; +const INPUT_EV_BG_COLOR = 6; +const INPUT_EV_PLAYER_COLOR = 7; +const INPUT_EV_PLAYER_NAME = 8; +const INPUT_EV_MOVE = 9; +const INPUT_EV_TOGGLE_PREVIEW = 10; +const CHANGE_DATA = 1; +const CHANGE_TILE = 2; +const CHANGE_PLAYER = 3; +var Protocol = { + EV_SERVER_EVENT, + EV_SERVER_INIT, + EV_SERVER_INIT_REPLAY, + EV_CLIENT_EVENT, + EV_CLIENT_INIT, + EV_CLIENT_INIT_REPLAY, + LOG_HEADER, + LOG_ADD_PLAYER, + LOG_UPDATE_PLAYER, + LOG_HANDLE_INPUT, + INPUT_EV_MOVE, + INPUT_EV_MOUSE_DOWN, + INPUT_EV_MOUSE_UP, + INPUT_EV_MOUSE_MOVE, + INPUT_EV_ZOOM_IN, + INPUT_EV_ZOOM_OUT, + INPUT_EV_BG_COLOR, + INPUT_EV_PLAYER_COLOR, + INPUT_EV_PLAYER_NAME, + INPUT_EV_TOGGLE_PREVIEW, + CHANGE_DATA, + CHANGE_TILE, + CHANGE_PLAYER, +}; + +function pointSub(a, b) { + return { x: a.x - b.x, y: a.y - b.y }; +} +function pointAdd(a, b) { + return { x: a.x + b.x, y: a.y + b.y }; +} +function pointDistance(a, b) { + const diffX = a.x - b.x; + const diffY = a.y - b.y; + return Math.sqrt(diffX * diffX + diffY * diffY); +} +function pointInBounds(pt, rect) { + return pt.x >= rect.x + && pt.x <= rect.x + rect.w + && pt.y >= rect.y + && pt.y <= rect.y + rect.h; +} +function rectCenter(rect) { + return { + x: rect.x + (rect.w / 2), + y: rect.y + (rect.h / 2), + }; +} +/** + * Returns a rectangle with same dimensions as the given one, but + * location (x/y) moved by x and y. + * + * @param {x, y, w,, h} rect + * @param number x + * @param number y + * @returns {x, y, w, h} + */ +function rectMoved(rect, x, y) { + return { + x: rect.x + x, + y: rect.y + y, + w: rect.w, + h: rect.h, + }; +} +/** + * Returns true if the rectangles overlap, including their borders. + * + * @param {x, y, w, h} rectA + * @param {x, y, w, h} rectB + * @returns bool + */ +function rectsOverlap(rectA, rectB) { + return !(rectB.x > (rectA.x + rectA.w) + || rectA.x > (rectB.x + rectB.w) + || rectB.y > (rectA.y + rectA.h) + || rectA.y > (rectB.y + rectB.h)); +} +function rectCenterDistance(rectA, rectB) { + return pointDistance(rectCenter(rectA), rectCenter(rectB)); +} +var Geometry = { + pointSub, + pointAdd, + pointDistance, + pointInBounds, + rectCenter, + rectMoved, + rectCenterDistance, + rectsOverlap, +}; + +const MS = 1; +const SEC = MS * 1000; +const MIN = SEC * 60; +const HOUR = MIN * 60; +const DAY = HOUR * 24; +const timestamp = () => { + const d = new Date(); + return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds()); +}; +const durationStr = (duration) => { + const d = Math.floor(duration / DAY); + duration = duration % DAY; + const h = Math.floor(duration / HOUR); + duration = duration % HOUR; + const m = Math.floor(duration / MIN); + duration = duration % MIN; + const s = Math.floor(duration / SEC); + return `${d}d ${h}h ${m}m ${s}s`; +}; +const timeDiffStr = (from, to) => durationStr(to - from); +var Time = { + MS, + SEC, + MIN, + HOUR, + DAY, + timestamp, + timeDiffStr, + durationStr, +}; + +const SCORE_MODE_FINAL = 0; +const SCORE_MODE_ANY = 1; +const IDLE_TIMEOUT_SEC = 30; +// Map +const GAMES = {}; +function exists$1(gameId) { + return (!!GAMES[gameId]) || false; +} +function __createPlayerObject(id, ts) { + return { + id: id, + x: 0, + y: 0, + d: 0, + name: null, + color: null, + bgcolor: null, + points: 0, + ts: ts, + }; +} +function setGame(gameId, game) { + GAMES[gameId] = game; +} +function getPlayerIndexById(gameId, playerId) { + let i = 0; + for (let player of GAMES[gameId].players) { + if (Util.decodePlayer(player).id === playerId) { + return i; + } + i++; + } + return -1; +} +function getPlayerIdByIndex(gameId, playerIndex) { + if (GAMES[gameId].players.length > playerIndex) { + return Util.decodePlayer(GAMES[gameId].players[playerIndex]).id; + } + return null; +} +function getPlayer(gameId, playerId) { + let idx = getPlayerIndexById(gameId, playerId); + return Util.decodePlayer(GAMES[gameId].players[idx]); +} +function setPlayer(gameId, playerId, player) { + let idx = getPlayerIndexById(gameId, playerId); + if (idx === -1) { + GAMES[gameId].players.push(Util.encodePlayer(player)); + } + else { + GAMES[gameId].players[idx] = Util.encodePlayer(player); + } +} +function setTile(gameId, tileIdx, tile) { + GAMES[gameId].puzzle.tiles[tileIdx] = Util.encodeTile(tile); +} +function setPuzzleData(gameId, data) { + GAMES[gameId].puzzle.data = data; +} +function playerExists(gameId, playerId) { + const idx = getPlayerIndexById(gameId, playerId); + return idx !== -1; +} +function getActivePlayers(gameId, ts) { + const minTs = ts - IDLE_TIMEOUT_SEC * Time.SEC; + return getAllPlayers(gameId).filter((p) => p.ts >= minTs); +} +function getIdlePlayers(gameId, ts) { + const minTs = ts - IDLE_TIMEOUT_SEC * Time.SEC; + return getAllPlayers(gameId).filter((p) => p.ts < minTs && p.points > 0); +} +function addPlayer$1(gameId, playerId, ts) { + if (!playerExists(gameId, playerId)) { + setPlayer(gameId, playerId, __createPlayerObject(playerId, ts)); + } + else { + changePlayer(gameId, playerId, { ts }); + } +} +function getEvtInfo(gameId, playerId) { + if (playerId in GAMES[gameId].evtInfos) { + return GAMES[gameId].evtInfos[playerId]; + } + return { + _last_mouse: null, + _last_mouse_down: null, + }; +} +function setEvtInfo(gameId, playerId, evtInfo) { + GAMES[gameId].evtInfos[playerId] = evtInfo; +} +function getAllGames() { + return Object.values(GAMES).sort((a, b) => { + // when both have same finished state, sort by started + if (isFinished(a.id) === isFinished(b.id)) { + return b.puzzle.data.started - a.puzzle.data.started; + } + // otherwise, sort: unfinished, finished + return isFinished(a.id) ? 1 : -1; + }); +} +function getAllPlayers(gameId) { + return GAMES[gameId] + ? GAMES[gameId].players.map(Util.decodePlayer) + : []; +} +function get$1(gameId) { + return GAMES[gameId]; +} +function getTileCount(gameId) { + return GAMES[gameId].puzzle.tiles.length; +} +function getImageUrl(gameId) { + return GAMES[gameId].puzzle.info.imageUrl; +} +function setImageUrl(gameId, imageUrl) { + GAMES[gameId].puzzle.info.imageUrl = imageUrl; +} +function getScoreMode(gameId) { + return GAMES[gameId].scoreMode || SCORE_MODE_FINAL; +} +function isFinished(gameId) { + return getFinishedTileCount(gameId) === getTileCount(gameId); +} +function getFinishedTileCount(gameId) { + let count = 0; + for (let t of GAMES[gameId].puzzle.tiles) { + if (Util.decodeTile(t).owner === -1) { + count++; + } + } + return count; +} +function getTilesSortedByZIndex(gameId) { + const tiles = GAMES[gameId].puzzle.tiles.map(Util.decodeTile); + return tiles.sort((t1, t2) => t1.z - t2.z); +} +function changePlayer(gameId, playerId, change) { + const player = getPlayer(gameId, playerId); + for (let k of Object.keys(change)) { + player[k] = change[k]; + } + setPlayer(gameId, playerId, player); +} +function changeData(gameId, change) { + for (let k of Object.keys(change)) { + // @ts-ignore + GAMES[gameId].puzzle.data[k] = change[k]; + } +} +function changeTile(gameId, tileIdx, change) { + for (let k of Object.keys(change)) { + const tile = Util.decodeTile(GAMES[gameId].puzzle.tiles[tileIdx]); + tile[k] = change[k]; + GAMES[gameId].puzzle.tiles[tileIdx] = Util.encodeTile(tile); + } +} +const getTile = (gameId, tileIdx) => { + return Util.decodeTile(GAMES[gameId].puzzle.tiles[tileIdx]); +}; +const getTileGroup = (gameId, tileIdx) => { + const tile = getTile(gameId, tileIdx); + return tile.group; +}; +const getFinalTilePos = (gameId, tileIdx) => { + const info = GAMES[gameId].puzzle.info; + const boardPos = { + x: (info.table.width - info.width) / 2, + y: (info.table.height - info.height) / 2 + }; + const srcPos = srcPosByTileIdx(gameId, tileIdx); + return Geometry.pointAdd(boardPos, srcPos); +}; +const getTilePos = (gameId, tileIdx) => { + const tile = getTile(gameId, tileIdx); + return tile.pos; +}; +// todo: instead, just make the table bigger and use that :) +const getBounds = (gameId) => { + const tw = getTableWidth(gameId); + const th = getTableHeight(gameId); + const overX = Math.round(tw / 4); + const overY = Math.round(th / 4); + return { + x: 0 - overX, + y: 0 - overY, + w: tw + 2 * overX, + h: th + 2 * overY, + }; +}; +const getTileBounds = (gameId, tileIdx) => { + const s = getTileSize(gameId); + const tile = getTile(gameId, tileIdx); + return { + x: tile.pos.x, + y: tile.pos.y, + w: s, + h: s, + }; +}; +const getTileZIndex = (gameId, tileIdx) => { + const tile = getTile(gameId, tileIdx); + return tile.z; +}; +const getFirstOwnedTileIdx = (gameId, playerId) => { + for (let t of GAMES[gameId].puzzle.tiles) { + const tile = Util.decodeTile(t); + if (tile.owner === playerId) { + return tile.idx; + } + } + return -1; +}; +const getFirstOwnedTile = (gameId, playerId) => { + const idx = getFirstOwnedTileIdx(gameId, playerId); + return idx < 0 ? null : GAMES[gameId].puzzle.tiles[idx]; +}; +const getTileDrawOffset = (gameId) => { + return GAMES[gameId].puzzle.info.tileDrawOffset; +}; +const getTileDrawSize = (gameId) => { + return GAMES[gameId].puzzle.info.tileDrawSize; +}; +const getTileSize = (gameId) => { + return GAMES[gameId].puzzle.info.tileSize; +}; +const getStartTs = (gameId) => { + return GAMES[gameId].puzzle.data.started; +}; +const getFinishTs = (gameId) => { + return GAMES[gameId].puzzle.data.finished; +}; +const getMaxGroup = (gameId) => { + return GAMES[gameId].puzzle.data.maxGroup; +}; +const getMaxZIndex = (gameId) => { + return GAMES[gameId].puzzle.data.maxZ; +}; +const getMaxZIndexByTileIdxs = (gameId, tileIdxs) => { + let maxZ = 0; + for (let tileIdx of tileIdxs) { + let tileZIndex = getTileZIndex(gameId, tileIdx); + if (tileZIndex > maxZ) { + maxZ = tileZIndex; + } + } + return maxZ; +}; +function srcPosByTileIdx(gameId, tileIdx) { + const info = GAMES[gameId].puzzle.info; + const c = Util.coordByTileIdx(info, tileIdx); + const cx = c.x * info.tileSize; + const cy = c.y * info.tileSize; + return { x: cx, y: cy }; +} +function getSurroundingTilesByIdx(gameId, tileIdx) { + const info = GAMES[gameId].puzzle.info; + const c = Util.coordByTileIdx(info, tileIdx); + return [ + // top + (c.y > 0) ? (tileIdx - info.tilesX) : -1, + // right + (c.x < info.tilesX - 1) ? (tileIdx + 1) : -1, + // bottom + (c.y < info.tilesY - 1) ? (tileIdx + info.tilesX) : -1, + // left + (c.x > 0) ? (tileIdx - 1) : -1, + ]; +} +const setTilesZIndex = (gameId, tileIdxs, zIndex) => { + for (let tilesIdx of tileIdxs) { + changeTile(gameId, tilesIdx, { z: zIndex }); + } +}; +const moveTileDiff = (gameId, tileIdx, diff) => { + const oldPos = getTilePos(gameId, tileIdx); + const pos = Geometry.pointAdd(oldPos, diff); + changeTile(gameId, tileIdx, { pos }); +}; +const moveTilesDiff = (gameId, tileIdxs, diff) => { + const tileDrawSize = getTileDrawSize(gameId); + const bounds = getBounds(gameId); + const cappedDiff = diff; + for (let tileIdx of tileIdxs) { + const t = getTile(gameId, tileIdx); + if (t.pos.x + diff.x < bounds.x) { + cappedDiff.x = Math.max(bounds.x - t.pos.x, cappedDiff.x); + } + else if (t.pos.x + tileDrawSize + diff.x > bounds.x + bounds.w) { + cappedDiff.x = Math.min(bounds.x + bounds.w - t.pos.x + tileDrawSize, cappedDiff.x); + } + if (t.pos.y + diff.y < bounds.y) { + cappedDiff.y = Math.max(bounds.y - t.pos.y, cappedDiff.y); + } + else if (t.pos.y + tileDrawSize + diff.y > bounds.y + bounds.h) { + cappedDiff.y = Math.min(bounds.y + bounds.h - t.pos.y + tileDrawSize, cappedDiff.y); + } + } + for (let tileIdx of tileIdxs) { + moveTileDiff(gameId, tileIdx, cappedDiff); + } +}; +const finishTiles = (gameId, tileIdxs) => { + for (let tileIdx of tileIdxs) { + changeTile(gameId, tileIdx, { owner: -1, z: 1 }); + } +}; +const setTilesOwner = (gameId, tileIdxs, owner) => { + for (let tileIdx of tileIdxs) { + changeTile(gameId, tileIdx, { owner }); + } +}; +// get all grouped tiles for a tile +function getGroupedTileIdxs(gameId, tileIdx) { + const tiles = GAMES[gameId].puzzle.tiles; + const tile = Util.decodeTile(tiles[tileIdx]); + const grouped = []; + if (tile.group) { + for (let other of tiles) { + const otherTile = Util.decodeTile(other); + if (otherTile.group === tile.group) { + grouped.push(otherTile.idx); + } + } + } + else { + grouped.push(tile.idx); + } + return grouped; +} +// Returns the index of the puzzle tile with the highest z index +// that is not finished yet and that matches the position +const freeTileIdxByPos = (gameId, pos) => { + let info = GAMES[gameId].puzzle.info; + let tiles = GAMES[gameId].puzzle.tiles; + let maxZ = -1; + let tileIdx = -1; + for (let idx = 0; idx < tiles.length; idx++) { + const tile = Util.decodeTile(tiles[idx]); + if (tile.owner !== 0) { + continue; + } + const collisionRect = { + x: tile.pos.x, + y: tile.pos.y, + w: info.tileSize, + h: info.tileSize, + }; + if (Geometry.pointInBounds(pos, collisionRect)) { + if (maxZ === -1 || tile.z > maxZ) { + maxZ = tile.z; + tileIdx = idx; + } + } + } + return tileIdx; +}; +const getPlayerBgColor = (gameId, playerId) => { + const p = getPlayer(gameId, playerId); + return p ? p.bgcolor : null; +}; +const getPlayerColor = (gameId, playerId) => { + const p = getPlayer(gameId, playerId); + return p ? p.color : null; +}; +const getPlayerName = (gameId, playerId) => { + const p = getPlayer(gameId, playerId); + return p ? p.name : null; +}; +const getPlayerPoints = (gameId, playerId) => { + const p = getPlayer(gameId, playerId); + return p ? p.points : null; +}; +// determine if two tiles are grouped together +const areGrouped = (gameId, tileIdx1, tileIdx2) => { + const g1 = getTileGroup(gameId, tileIdx1); + const g2 = getTileGroup(gameId, tileIdx2); + return g1 && g1 === g2; +}; +const getTableWidth = (gameId) => { + return GAMES[gameId].puzzle.info.table.width; +}; +const getTableHeight = (gameId) => { + return GAMES[gameId].puzzle.info.table.height; +}; +const getPuzzle = (gameId) => { + return GAMES[gameId].puzzle; +}; +const getRng = (gameId) => { + return GAMES[gameId].rng.obj; +}; +const getPuzzleWidth = (gameId) => { + return GAMES[gameId].puzzle.info.width; +}; +const getPuzzleHeight = (gameId) => { + return GAMES[gameId].puzzle.info.height; +}; +function handleInput$1(gameId, playerId, input, ts) { + const puzzle = GAMES[gameId].puzzle; + const evtInfo = getEvtInfo(gameId, playerId); + const changes = []; + const _dataChange = () => { + changes.push([Protocol.CHANGE_DATA, puzzle.data]); + }; + const _tileChange = (tileIdx) => { + changes.push([ + Protocol.CHANGE_TILE, + Util.encodeTile(getTile(gameId, tileIdx)), + ]); + }; + const _tileChanges = (tileIdxs) => { + for (const tileIdx of tileIdxs) { + _tileChange(tileIdx); + } + }; + const _playerChange = () => { + changes.push([ + Protocol.CHANGE_PLAYER, + Util.encodePlayer(getPlayer(gameId, playerId)), + ]); + }; + // put both tiles (and their grouped tiles) in the same group + const groupTiles = (gameId, tileIdx1, tileIdx2) => { + const tiles = GAMES[gameId].puzzle.tiles; + const group1 = getTileGroup(gameId, tileIdx1); + const group2 = getTileGroup(gameId, tileIdx2); + let group; + const searchGroups = []; + if (group1) { + searchGroups.push(group1); + } + if (group2) { + searchGroups.push(group2); + } + if (group1) { + group = group1; + } + else if (group2) { + group = group2; + } + else { + const maxGroup = getMaxGroup(gameId) + 1; + changeData(gameId, { maxGroup }); + _dataChange(); + group = getMaxGroup(gameId); + } + changeTile(gameId, tileIdx1, { group }); + _tileChange(tileIdx1); + changeTile(gameId, tileIdx2, { group }); + _tileChange(tileIdx2); + // TODO: strange + if (searchGroups.length > 0) { + for (const t of tiles) { + const tile = Util.decodeTile(t); + if (searchGroups.includes(tile.group)) { + changeTile(gameId, tile.idx, { group }); + _tileChange(tile.idx); + } + } + } + }; + const type = input[0]; + if (type === Protocol.INPUT_EV_BG_COLOR) { + const bgcolor = input[1]; + changePlayer(gameId, playerId, { bgcolor, ts }); + _playerChange(); + } + else if (type === Protocol.INPUT_EV_PLAYER_COLOR) { + const color = input[1]; + changePlayer(gameId, playerId, { color, ts }); + _playerChange(); + } + else if (type === Protocol.INPUT_EV_PLAYER_NAME) { + const name = `${input[1]}`.substr(0, 16); + changePlayer(gameId, playerId, { name, ts }); + _playerChange(); + } + else if (type === Protocol.INPUT_EV_MOUSE_DOWN) { + const x = input[1]; + const y = input[2]; + const pos = { x, y }; + changePlayer(gameId, playerId, { d: 1, ts }); + _playerChange(); + evtInfo._last_mouse_down = pos; + const tileIdxAtPos = freeTileIdxByPos(gameId, pos); + if (tileIdxAtPos >= 0) { + let maxZ = getMaxZIndex(gameId) + 1; + changeData(gameId, { maxZ }); + _dataChange(); + const tileIdxs = getGroupedTileIdxs(gameId, tileIdxAtPos); + setTilesZIndex(gameId, tileIdxs, getMaxZIndex(gameId)); + setTilesOwner(gameId, tileIdxs, playerId); + _tileChanges(tileIdxs); + } + evtInfo._last_mouse = pos; + } + else if (type === Protocol.INPUT_EV_MOUSE_MOVE) { + const x = input[1]; + const y = input[2]; + const pos = { x, y }; + if (evtInfo._last_mouse_down === null) { + // player is just moving the hand + changePlayer(gameId, playerId, { x, y, ts }); + _playerChange(); + } + else { + let tileIdx = getFirstOwnedTileIdx(gameId, playerId); + if (tileIdx >= 0) { + // player is moving a tile (and hand) + changePlayer(gameId, playerId, { x, y, ts }); + _playerChange(); + // check if pos is on the tile, otherwise dont move + // (mouse could be out of table, but tile stays on it) + const tileIdxs = getGroupedTileIdxs(gameId, tileIdx); + let anyOk = Geometry.pointInBounds(pos, getBounds(gameId)) + && Geometry.pointInBounds(evtInfo._last_mouse_down, getBounds(gameId)); + for (let idx of tileIdxs) { + const bounds = getTileBounds(gameId, idx); + if (Geometry.pointInBounds(pos, bounds)) { + anyOk = true; + break; + } + } + if (anyOk) { + const diffX = x - evtInfo._last_mouse_down.x; + const diffY = y - evtInfo._last_mouse_down.y; + const diff = { x: diffX, y: diffY }; + moveTilesDiff(gameId, tileIdxs, diff); + _tileChanges(tileIdxs); + } + } + else { + // player is just moving map, so no change in position! + changePlayer(gameId, playerId, { ts }); + _playerChange(); + } + evtInfo._last_mouse_down = pos; + } + evtInfo._last_mouse = pos; + } + else if (type === Protocol.INPUT_EV_MOUSE_UP) { + const x = input[1]; + const y = input[2]; + const pos = { x, y }; + const d = 0; + evtInfo._last_mouse_down = null; + let tileIdx = getFirstOwnedTileIdx(gameId, playerId); + if (tileIdx >= 0) { + // drop the tile(s) + let tileIdxs = getGroupedTileIdxs(gameId, tileIdx); + setTilesOwner(gameId, tileIdxs, 0); + _tileChanges(tileIdxs); + // Check if the tile was dropped near the final location + let tilePos = getTilePos(gameId, tileIdx); + let finalPos = getFinalTilePos(gameId, tileIdx); + if (Geometry.pointDistance(finalPos, tilePos) < puzzle.info.snapDistance) { + let diff = Geometry.pointSub(finalPos, tilePos); + // Snap the tile to the final destination + moveTilesDiff(gameId, tileIdxs, diff); + finishTiles(gameId, tileIdxs); + _tileChanges(tileIdxs); + let points = getPlayerPoints(gameId, playerId); + if (getScoreMode(gameId) === SCORE_MODE_FINAL) { + points += tileIdxs.length; + } + else if (getScoreMode(gameId) === SCORE_MODE_ANY) { + points += 1; + } + else ; + changePlayer(gameId, playerId, { d, ts, points }); + _playerChange(); + // check if the puzzle is finished + if (getFinishedTileCount(gameId) === getTileCount(gameId)) { + changeData(gameId, { finished: ts }); + _dataChange(); + } + } + else { + // Snap to other tiles + const check = (gameId, tileIdx, otherTileIdx, off) => { + let info = GAMES[gameId].puzzle.info; + if (otherTileIdx < 0) { + return false; + } + if (areGrouped(gameId, tileIdx, otherTileIdx)) { + return false; + } + const tilePos = getTilePos(gameId, tileIdx); + const dstPos = Geometry.pointAdd(getTilePos(gameId, otherTileIdx), { x: off[0] * info.tileSize, y: off[1] * info.tileSize }); + if (Geometry.pointDistance(tilePos, dstPos) < info.snapDistance) { + let diff = Geometry.pointSub(dstPos, tilePos); + let tileIdxs = getGroupedTileIdxs(gameId, tileIdx); + moveTilesDiff(gameId, tileIdxs, diff); + groupTiles(gameId, tileIdx, otherTileIdx); + tileIdxs = getGroupedTileIdxs(gameId, tileIdx); + const zIndex = getMaxZIndexByTileIdxs(gameId, tileIdxs); + setTilesZIndex(gameId, tileIdxs, zIndex); + _tileChanges(tileIdxs); + return true; + } + return false; + }; + let snapped = false; + for (let tileIdxTmp of getGroupedTileIdxs(gameId, tileIdx)) { + let othersIdxs = getSurroundingTilesByIdx(gameId, tileIdxTmp); + if (check(gameId, tileIdxTmp, othersIdxs[0], [0, 1]) // top + || check(gameId, tileIdxTmp, othersIdxs[1], [-1, 0]) // right + || check(gameId, tileIdxTmp, othersIdxs[2], [0, -1]) // bottom + || check(gameId, tileIdxTmp, othersIdxs[3], [1, 0]) // left + ) { + snapped = true; + break; + } + } + if (snapped && getScoreMode(gameId) === SCORE_MODE_ANY) { + const points = getPlayerPoints(gameId, playerId) + 1; + changePlayer(gameId, playerId, { d, ts, points }); + _playerChange(); + } + else { + changePlayer(gameId, playerId, { d, ts }); + _playerChange(); + } + } + } + else { + changePlayer(gameId, playerId, { d, ts }); + _playerChange(); + } + evtInfo._last_mouse = pos; + } + else if (type === Protocol.INPUT_EV_ZOOM_IN) { + const x = input[1]; + const y = input[2]; + changePlayer(gameId, playerId, { x, y, ts }); + _playerChange(); + evtInfo._last_mouse = { x, y }; + } + else if (type === Protocol.INPUT_EV_ZOOM_OUT) { + const x = input[1]; + const y = input[2]; + changePlayer(gameId, playerId, { x, y, ts }); + _playerChange(); + evtInfo._last_mouse = { x, y }; + } + else { + changePlayer(gameId, playerId, { ts }); + _playerChange(); + } + setEvtInfo(gameId, playerId, evtInfo); + return changes; +} +var GameCommon = { + __createPlayerObject, + setGame, + exists: exists$1, + playerExists, + getActivePlayers, + getIdlePlayers, + addPlayer: addPlayer$1, + getFinishedTileCount, + getTileCount, + getImageUrl, + setImageUrl, + get: get$1, + getAllGames, + getPlayerBgColor, + getPlayerColor, + getPlayerName, + getPlayerIndexById, + getPlayerIdByIndex, + changePlayer, + setPlayer, + setTile, + setPuzzleData, + getTableWidth, + getTableHeight, + getPuzzle, + getRng, + getPuzzleWidth, + getPuzzleHeight, + getTilesSortedByZIndex, + getFirstOwnedTile, + getTileDrawOffset, + getTileDrawSize, + getFinalTilePos, + getStartTs, + getFinishTs, + handleInput: handleInput$1, + SCORE_MODE_FINAL, + SCORE_MODE_ANY, +}; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const BASE_DIR = `${__dirname}/../..`; +const DATA_DIR = `${BASE_DIR}/data`; +const UPLOAD_DIR = `${BASE_DIR}/data/uploads`; +const UPLOAD_URL = `/uploads`; +const PUBLIC_DIR = `${BASE_DIR}/build/public/`; + +const log$3 = logger('GameLog.js'); +const filename = (gameId) => `${DATA_DIR}/log_${gameId}.log`; +const create = (gameId) => { + const file = filename(gameId); + if (!fs.existsSync(file)) { + fs.appendFileSync(file, ''); + } +}; +const exists = (gameId) => { + const file = filename(gameId); + return fs.existsSync(file); +}; +const _log = (gameId, ...args) => { + const file = filename(gameId); + if (!fs.existsSync(file)) { + return; + } + const str = JSON.stringify(args); + fs.appendFileSync(file, str + "\n"); +}; +const get = (gameId) => { + const file = filename(gameId); + if (!fs.existsSync(file)) { + return []; + } + const lines = fs.readFileSync(file, 'utf-8').split("\n"); + return lines.filter((line) => !!line).map((line) => { + try { + return JSON.parse(line); + } + catch (e) { + log$3.log(line); + log$3.log(e); + } + }); +}; +var GameLog = { + create, + exists, + log: _log, + get, +}; + +const resizeImage = async (filename) => { + if (!filename.toLowerCase().match(/\.(jpe?g|webp|png)$/)) { + return; + } + const imagePath = `${UPLOAD_DIR}/${filename}`; + const imageOutPath = `${UPLOAD_DIR}/r/${filename}`; + const orientation = await getExifOrientation(imagePath); + let sharpImg = sharp(imagePath, { failOnError: false }); + // when image is rotated to the left or right, switch width/height + // https://jdhao.github.io/2019/07/31/image_rotation_exif_info/ + if (orientation === 6) { + sharpImg = sharpImg.rotate(); + } + else if (orientation === 3) { + sharpImg = sharpImg.rotate().rotate(); + } + else if (orientation === 8) { + sharpImg = sharpImg.rotate().rotate().rotate(); + } + const sizes = [ + [150, 100], + [375, 210], + ]; + for (let [w, h] of sizes) { + console.log(w, h, imagePath); + await sharpImg.resize(w, h, { fit: 'contain' }).toFile(`${imageOutPath}-${w}x${h}.webp`); + } +}; +async function getExifOrientation(imagePath) { + return new Promise((resolve, reject) => { + new exif.ExifImage({ image: imagePath }, function (error, exifData) { + if (error) { + resolve(0); + } + else { + resolve(exifData.image.Orientation); + } + }); + }); +} +const allImages = () => { + const images = fs.readdirSync(UPLOAD_DIR) + .filter(f => f.toLowerCase().match(/\.(jpe?g|webp|png)$/)) + .map(f => ({ + filename: f, + file: `${UPLOAD_DIR}/${f}`, + url: `${UPLOAD_URL}/${encodeURIComponent(f)}`, + })) + .sort((a, b) => { + return fs.statSync(b.file).mtime.getTime() - + fs.statSync(a.file).mtime.getTime(); + }); + return images; +}; +async function getDimensions(imagePath) { + let dimensions = sizeOf(imagePath); + const orientation = await getExifOrientation(imagePath); + // when image is rotated to the left or right, switch width/height + // https://jdhao.github.io/2019/07/31/image_rotation_exif_info/ + if (orientation === 6 || orientation === 8) { + return { + width: dimensions.height, + height: dimensions.width, + }; + } + return dimensions; +} +var Images = { + allImages, + resizeImage, + getDimensions, +}; + +// cut size of each puzzle tile in the +// final resized version of the puzzle image +const TILE_SIZE = 64; +async function createPuzzle(rng, targetTiles, image, ts) { + const imagePath = image.file; + const imageUrl = image.url; + // determine puzzle information from the image dimensions + const dim = await Images.getDimensions(imagePath); + if (!dim || !dim.width || !dim.height) { + throw `[ 2021-05-16 invalid dimension for path ${imagePath} ]`; + } + const info = determinePuzzleInfo(dim.width, dim.height, targetTiles); + let tiles = new Array(info.tiles); + for (let i = 0; i < tiles.length; i++) { + tiles[i] = { idx: i }; + } + const shapes = determinePuzzleTileShapes(rng, info); + let positions = new Array(info.tiles); + for (let tile of tiles) { + const coord = Util.coordByTileIdx(info, tile.idx); + positions[tile.idx] = { + // instead of info.tileSize, we use info.tileDrawSize + // to spread the tiles a bit + x: coord.x * info.tileSize * 1.5, + y: coord.y * info.tileSize * 1.5, + }; + } + const tableWidth = info.width * 3; + const tableHeight = info.height * 3; + const off = info.tileSize * 1.5; + let last = { + x: info.width - (1 * off), + y: info.height - (2 * off), + }; + let countX = Math.ceil(info.width / off) + 2; + let countY = Math.ceil(info.height / off) + 2; + let diffX = off; + let diffY = 0; + let index = 0; + for (let pos of positions) { + pos.x = last.x; + pos.y = last.y; + last.x += diffX; + last.y += diffY; + index++; + // did we move horizontally? + if (diffX !== 0) { + if (index === countX) { + diffY = diffX; + countY++; + diffX = 0; + index = 0; + } + } + else { + if (index === countY) { + diffX = -diffY; + countX++; + diffY = 0; + index = 0; + } + } + } + // then shuffle the positions + positions = Util.shuffle(rng, positions); + tiles = tiles.map(tile => { + return Util.encodeTile({ + idx: tile.idx, + group: 0, + z: 0, + // who owns the tile + // 0 = free for taking + // -1 = finished + // other values: id of player who has the tile + owner: 0, + // physical current position of the tile (x/y in pixels) + // this position is the initial position only and is the + // value that changes when moving a tile + pos: positions[tile.idx], + }); + }); + // Complete puzzle object + return { + // tiles array + tiles, + // game data for puzzle, data changes during the game + data: { + // TODO: maybe calculate this each time? + maxZ: 0, + maxGroup: 0, + started: ts, + finished: 0, // finish timestamp + }, + // static puzzle information. stays same for complete duration of + // the game + info: { + table: { + width: tableWidth, + height: tableHeight, + }, + // information that was used to create the puzzle + targetTiles: targetTiles, + imageUrl, + width: info.width, + height: info.height, + tileSize: info.tileSize, + tileDrawSize: info.tileDrawSize, + tileMarginWidth: info.tileMarginWidth, + // offset in x and y when drawing tiles, so that they appear to be at pos + tileDrawOffset: (info.tileDrawSize - info.tileSize) / -2, + // max distance between tile and destination that + // makes the tile snap to destination + snapDistance: info.tileSize / 2, + tiles: info.tiles, + tilesX: info.tilesX, + tilesY: info.tilesY, + // ( index => {x, y} ) + // this is not the physical coordinate, but + // the tile_coordinate + // this can be used to determine where the + // final destination of a tile is + shapes: shapes, // tile shapes + }, + }; +} +function determinePuzzleTileShapes(rng, info) { + const tabs = [-1, 1]; + const shapes = new Array(info.tiles); + for (let i = 0; i < info.tiles; i++) { + let coord = Util.coordByTileIdx(info, i); + shapes[i] = { + top: coord.y === 0 ? 0 : shapes[i - info.tilesX].bottom * -1, + right: coord.x === info.tilesX - 1 ? 0 : Util.choice(rng, tabs), + left: coord.x === 0 ? 0 : shapes[i - 1].right * -1, + bottom: coord.y === info.tilesY - 1 ? 0 : Util.choice(rng, tabs), + }; + } + return shapes.map(Util.encodeShape); +} +const determineTilesXY = (w, h, targetTiles) => { + const w_ = w < h ? (w * h) : (w * w); + const h_ = w < h ? (h * h) : (w * h); + let size = 0; + let tiles = 0; + do { + size++; + tiles = Math.floor(w_ / size) * Math.floor(h_ / size); + } while (tiles >= targetTiles); + size--; + return { + tilesX: Math.round(w_ / size), + tilesY: Math.round(h_ / size), + }; +}; +const determinePuzzleInfo = (w, h, targetTiles) => { + const { tilesX, tilesY } = determineTilesXY(w, h, targetTiles); + const tiles = tilesX * tilesY; + const tileSize = TILE_SIZE; + const width = tilesX * tileSize; + const height = tilesY * tileSize; + const tileMarginWidth = tileSize * .5; + const tileDrawSize = Math.round(tileSize + tileMarginWidth * 2); + return { + width, + height, + tileSize, + tileMarginWidth, + tileDrawSize, + tiles, + tilesX, + tilesY, + }; +}; + +const log$2 = logger('GameStorage.js'); +const DIRTY_GAMES = {}; +function setDirty(gameId) { + DIRTY_GAMES[gameId] = true; +} +function setClean(gameId) { + delete DIRTY_GAMES[gameId]; +} +function loadGames() { + const files = fs.readdirSync(DATA_DIR); + for (const f of files) { + const m = f.match(/^([a-z0-9]+)\.json$/); + if (!m) { + continue; + } + const gameId = m[1]; + loadGame(gameId); + } +} +function loadGame(gameId) { + const file = `${DATA_DIR}/${gameId}.json`; + const contents = fs.readFileSync(file, 'utf-8'); + let game; + try { + game = JSON.parse(contents); + } + catch { + log$2.log(`[ERR] unable to load game from file ${file}`); + } + if (typeof game.puzzle.data.started === 'undefined') { + game.puzzle.data.started = Math.round(fs.statSync(file).ctimeMs); + } + if (typeof game.puzzle.data.finished === 'undefined') { + let unfinished = game.puzzle.tiles.map(Util.decodeTile).find((t) => t.owner !== -1); + game.puzzle.data.finished = unfinished ? 0 : Time.timestamp(); + } + if (!Array.isArray(game.players)) { + game.players = Object.values(game.players); + } + const gameObject = { + id: game.id, + rng: { + type: game.rng ? game.rng.type : '_fake_', + obj: game.rng ? Rng.unserialize(game.rng.obj) : new Rng(0), + }, + puzzle: game.puzzle, + players: game.players, + evtInfos: {}, + scoreMode: game.scoreMode || GameCommon.SCORE_MODE_FINAL, + }; + GameCommon.setGame(gameObject.id, gameObject); +} +function persistGames() { + for (const gameId of Object.keys(DIRTY_GAMES)) { + persistGame(gameId); + } +} +function persistGame(gameId) { + const game = GameCommon.get(gameId); + if (game.id in DIRTY_GAMES) { + setClean(game.id); + } + fs.writeFileSync(`${DATA_DIR}/${game.id}.json`, JSON.stringify({ + id: game.id, + rng: { + type: game.rng.type, + obj: Rng.serialize(game.rng.obj), + }, + puzzle: game.puzzle, + players: game.players, + scoreMode: game.scoreMode, + })); + log$2.info(`[INFO] persisted game ${game.id}`); +} +var GameStorage = { + loadGames, + loadGame, + persistGames, + persistGame, + setDirty, +}; + +async function createGameObject(gameId, targetTiles, image, ts, scoreMode) { + const seed = Util.hash(gameId + ' ' + ts); + const rng = new Rng(seed); + return { + id: gameId, + rng: { type: 'Rng', obj: rng }, + puzzle: await createPuzzle(rng, targetTiles, image, ts), + players: [], + evtInfos: {}, + scoreMode, + }; +} +async function createGame(gameId, targetTiles, image, ts, scoreMode) { + const gameObject = await createGameObject(gameId, targetTiles, image, ts, scoreMode); + GameLog.create(gameId); + GameLog.log(gameId, Protocol.LOG_HEADER, 1, targetTiles, image, ts, scoreMode); + GameCommon.setGame(gameObject.id, gameObject); + GameStorage.setDirty(gameId); +} +function addPlayer(gameId, playerId, ts) { + const idx = GameCommon.getPlayerIndexById(gameId, playerId); + const diff = ts - GameCommon.getStartTs(gameId); + if (idx === -1) { + GameLog.log(gameId, Protocol.LOG_ADD_PLAYER, playerId, diff); + } + else { + GameLog.log(gameId, Protocol.LOG_UPDATE_PLAYER, idx, diff); + } + GameCommon.addPlayer(gameId, playerId, ts); + GameStorage.setDirty(gameId); +} +function handleInput(gameId, playerId, input, ts) { + const idx = GameCommon.getPlayerIndexById(gameId, playerId); + const diff = ts - GameCommon.getStartTs(gameId); + GameLog.log(gameId, Protocol.LOG_HANDLE_INPUT, idx, input, diff); + const ret = GameCommon.handleInput(gameId, playerId, input, ts); + GameStorage.setDirty(gameId); + return ret; +} +var Game = { + createGameObject, + createGame, + addPlayer, + handleInput, + getAllGames: GameCommon.getAllGames, + getActivePlayers: GameCommon.getActivePlayers, + getFinishedTileCount: GameCommon.getFinishedTileCount, + getImageUrl: GameCommon.getImageUrl, + getTileCount: GameCommon.getTileCount, + exists: GameCommon.exists, + playerExists: GameCommon.playerExists, + get: GameCommon.get, + getStartTs: GameCommon.getStartTs, + getFinishTs: GameCommon.getFinishTs, +}; + +const log$1 = logger('GameSocket.js'); +// Map +const SOCKETS = {}; +function socketExists(gameId, socket) { + if (!(gameId in SOCKETS)) { + return false; + } + return SOCKETS[gameId].includes(socket); +} +function removeSocket(gameId, socket) { + if (!(gameId in SOCKETS)) { + return; + } + SOCKETS[gameId] = SOCKETS[gameId].filter((s) => s !== socket); + log$1.log('removed socket: ', gameId, socket.protocol); + log$1.log('socket count: ', Object.keys(SOCKETS[gameId]).length); +} +function addSocket(gameId, socket) { + if (!(gameId in SOCKETS)) { + SOCKETS[gameId] = []; + } + if (!SOCKETS[gameId].includes(socket)) { + SOCKETS[gameId].push(socket); + log$1.log('added socket: ', gameId, socket.protocol); + log$1.log('socket count: ', Object.keys(SOCKETS[gameId]).length); + } +} +function getSockets(gameId) { + if (!(gameId in SOCKETS)) { + return []; + } + return SOCKETS[gameId]; +} +var GameSockets = { + addSocket, + removeSocket, + socketExists, + getSockets, +}; + +let configFile = ''; +let last = ''; +for (const val of process.argv) { + if (last === '-c') { + configFile = val; + } + last = val; +} +if (configFile === '') { + process.exit(2); +} +const config = JSON.parse(String(fs.readFileSync(configFile))); +const log = logger('main.js'); +const port = config.http.port; +const hostname = config.http.hostname; +const app = express(); +const storage = multer.diskStorage({ + destination: UPLOAD_DIR, + filename: function (req, file, cb) { + cb(null, file.originalname); + } +}); +const upload = multer({ storage }).single('file'); +app.get('/api/conf', (req, res) => { + res.send({ + WS_ADDRESS: config.ws.connectstring, + }); +}); +app.get('/api/newgame-data', (req, res) => { + res.send({ + images: Images.allImages(), + }); +}); +app.get('/api/index-data', (req, res) => { + const ts = Time.timestamp(); + const games = [ + ...Game.getAllGames().map((game) => ({ + id: game.id, + hasReplay: GameLog.exists(game.id), + started: Game.getStartTs(game.id), + finished: Game.getFinishTs(game.id), + tilesFinished: Game.getFinishedTileCount(game.id), + tilesTotal: Game.getTileCount(game.id), + players: Game.getActivePlayers(game.id, ts).length, + imageUrl: Game.getImageUrl(game.id), + })), + ]; + res.send({ + gamesRunning: games.filter(g => !g.finished), + gamesFinished: games.filter(g => !!g.finished), + }); +}); +app.post('/upload', (req, res) => { + upload(req, res, async (err) => { + if (err) { + log.log(err); + res.status(400).send("Something went wrong!"); + } + try { + await Images.resizeImage(req.file.filename); + } + catch (err) { + log.log(err); + res.status(400).send("Something went wrong!"); + } + res.send({ + image: { + file: `${UPLOAD_DIR}/${req.file.filename}`, + url: `${UPLOAD_URL}/${req.file.filename}`, + }, + }); + }); +}); +app.post('/newgame', bodyParser.json(), async (req, res) => { + log.log(req.body.tiles, req.body.image); + const gameId = Util.uniqId(); + if (!Game.exists(gameId)) { + const ts = Time.timestamp(); + await Game.createGame(gameId, req.body.tiles, req.body.image, ts, req.body.scoreMode); + } + res.send({ id: gameId }); +}); +app.use('/uploads/', express.static(UPLOAD_DIR)); +app.use('/', express.static(PUBLIC_DIR)); +const wss = new WebSocketServer(config.ws); +const notify = (data, sockets) => { + // TODO: throttle? + for (let socket of sockets) { + wss.notifyOne(data, socket); + } +}; +wss.on('close', async ({ socket }) => { + try { + const proto = socket.protocol.split('|'); + const clientId = proto[0]; + const gameId = proto[1]; + GameSockets.removeSocket(gameId, socket); + } + catch (e) { + log.error(e); + } +}); +wss.on('message', async ({ socket, data }) => { + try { + const proto = socket.protocol.split('|'); + const clientId = proto[0]; + const gameId = proto[1]; + const msg = JSON.parse(data); + const msgType = msg[0]; + switch (msgType) { + case Protocol.EV_CLIENT_INIT_REPLAY: + { + if (!GameLog.exists(gameId)) { + throw `[gamelog ${gameId} does not exist... ]`; + } + const log = GameLog.get(gameId); + const game = await Game.createGameObject(gameId, log[0][2], log[0][3], log[0][4], log[0][5] || GameCommon.SCORE_MODE_FINAL); + notify([Protocol.EV_SERVER_INIT_REPLAY, Util.encodeGame(game), log], [socket]); + } + break; + case Protocol.EV_CLIENT_INIT: + { + if (!Game.exists(gameId)) { + throw `[game ${gameId} does not exist... ]`; + } + const ts = Time.timestamp(); + Game.addPlayer(gameId, clientId, ts); + GameSockets.addSocket(gameId, socket); + const game = Game.get(gameId); + notify([Protocol.EV_SERVER_INIT, Util.encodeGame(game)], [socket]); + } + break; + case Protocol.EV_CLIENT_EVENT: + { + if (!Game.exists(gameId)) { + throw `[game ${gameId} does not exist... ]`; + } + const clientSeq = msg[1]; + const clientEvtData = msg[2]; + const ts = Time.timestamp(); + let sendGame = false; + if (!Game.playerExists(gameId, clientId)) { + Game.addPlayer(gameId, clientId, ts); + sendGame = true; + } + if (!GameSockets.socketExists(gameId, socket)) { + GameSockets.addSocket(gameId, socket); + sendGame = true; + } + if (sendGame) { + const game = Game.get(gameId); + notify([Protocol.EV_SERVER_INIT, Util.encodeGame(game)], [socket]); + } + const changes = Game.handleInput(gameId, clientId, clientEvtData, ts); + notify([Protocol.EV_SERVER_EVENT, clientId, clientSeq, changes], GameSockets.getSockets(gameId)); + } + break; + } + } + catch (e) { + log.error(e); + } +}); +GameStorage.loadGames(); +const server = app.listen(port, hostname, () => log.log(`server running on http://${hostname}:${port}`)); +wss.listen(); +const memoryUsageHuman = () => { + const totalHeapSize = v8.getHeapStatistics().total_available_size; + let totalHeapSizeInGB = (totalHeapSize / 1024 / 1024 / 1024).toFixed(2); + log.log(`Total heap size (bytes) ${totalHeapSize}, (GB ~${totalHeapSizeInGB})`); + const used = process.memoryUsage().heapUsed / 1024 / 1024; + log.log(`Mem: ${Math.round(used * 100) / 100}M`); +}; +memoryUsageHuman(); +// persist games in fixed interval +const persistInterval = setInterval(() => { + log.log('Persisting games...'); + GameStorage.persistGames(); + memoryUsageHuman(); +}, config.persistence.interval); +const gracefulShutdown = (signal) => { + log.log(`${signal} received...`); + log.log('clearing persist interval...'); + clearInterval(persistInterval); + log.log('persisting games...'); + GameStorage.persistGames(); + log.log('shutting down webserver...'); + server.close(); + log.log('shutting down websocketserver...'); + wss.close(); + log.log('shutting down...'); + process.exit(); +}; +// used by nodemon +process.once('SIGUSR2', function () { + gracefulShutdown('SIGUSR2'); +}); +process.once('SIGINT', function (code) { + gracefulShutdown('SIGINT'); +}); +process.once('SIGTERM', function (code) { + gracefulShutdown('SIGTERM'); +});