diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..6d31f6f --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +node_modules +build +src/frontend/shims-vue.d.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..2080395 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,17 @@ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + plugins: [ + '@typescript-eslint', + ], + parserOptions: { + ecmaVersion: 2020, + }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + ], + rules: { + '@typescript-eslint/no-inferrable-types': 'off' + } +}; diff --git a/build/public/assets/index.643c957c.js b/build/public/assets/index.643c957c.js deleted file mode 100644 index 77b35d4..0000000 --- a/build/public/assets/index.643c957c.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as n,w as l,b as i,r as o,o as a,e as s,t as r,F as d,f as c,g as u,h as g,v as p,i as h,j as m,k as y,l as f,m as w,n as v,p as b,q as x,s as C}from"./vendor.b622ee49.js";var k=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const A={id:"app"},z={key:0,class:"nav"},S=s("Index"),P=s("New game");k.render=function(e,s,r,d,c,u){const g=o("router-link"),p=o("router-view");return a(),t("div",A,[e.showNav?(a(),t("ul",z,[n("li",null,[n(g,{class:"btn",to:{name:"index"}},{default:l((()=>[S])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:l((()=>[P])),_:1})])])):i("",!0),n(p)])};const I=864e5,T=e=>{const t=Math.floor(e/I);e%=I;const n=Math.floor(e/36e5);e%=36e5;const l=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${l}m ${Math.floor(e/1e3)}s`};var _=1e3,D=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>T(t-e),E=T,O=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?"🏁":"⏳",l=e,i=t||D();return`${n} ${B(l,i)}`}}});const U={class:"game-info-text"},N=n("br",null,null,-1),M=n("br",null,null,-1),G=n("br",null,null,-1),$=s(" β†ͺ️ Watch replay ");O.render=function(e,d,c,u,g,p){const h=o("router-link");return a(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:l((()=>[n("span",U,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),N,s(" πŸ‘₯ "+r(e.game.players),1),M,s(" "+r(e.time(e.game.started,e.game.finished)),1),G])])),_:1},8,["to"]),e.game.hasReplay?(a(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:l((()=>[$])),_:1},8,["to"])):i("",!0)],4)};var R=e({components:{GameTeaser:O},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 V=n("h1",null,"Running games",-1),j=n("h1",null,"Finished games",-1);R.render=function(e,l,i,s,r,u){const g=o("game-teaser");return a(),t("div",null,[V,(a(!0),t(d,null,c(e.gamesRunning,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128)),j,(a(!0),t(d,null,c(e.gamesFinished,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128))])};var F=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});F.render=function(e,l,i,o,s,r){return a(),t("div",{class:"imageteaser",style:e.style,onClick:l[2]||(l[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:l[1]||(l[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var L=e({name:"image-library",components:{ImageTeaser:F},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});L.render=function(e,n,l,i,s,r){const u=o("image-teaser");return a(),t("div",null,[(a(!0),t(d,null,c(e.images,((n,l)=>(a(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:l},null,8,["image","onClick","onEditClick"])))),128))])};const W={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};W.render=function(e,n,l,i,o,s){return a(),t("div",{style:s.style,title:l.title},null,12,["title"])};var q=e({name:"tags-input",props:{modelValue:{type:Array,required:!0}},emits:{"update:modelValue":null},data:()=>({input:"",values:[]}),created(){this.values=this.modelValue},methods:{onKeyUp(e){if(","===e.key)return this.add(),e.stopPropagation(),!1},add(){const e=this.input.replace(/,/g,"").trim();e&&(this.values.includes(e)||this.values.push(e),this.input="",this.$emit("update:modelValue",this.values))},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const H=m()(((e,l,i,o,s,u)=>(a(),t("div",null,[g(n("input",{class:"input",type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.input=t),placeholder:"Plants, People",onKeydown:l[2]||(l[2]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:l[3]||(l[3]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[p,e.input]]),(a(!0),t(d,null,c(e.values,((n,l)=>(a(),t("span",{key:l,class:"bit",onClick:t=>e.rm(n)},r(n)+" βœ–",9,["onClick"])))),128))]))));q.render=H,q.__scopeId="data-v-771460ae";var Q=e({name:"new-image-dialog",components:{ResponsiveImage:W,TagsInput:q},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const l=new FileReader;l.readAsDataURL(n),l.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const Y={key:0,class:"has-image"},K={key:1},Z={class:"upload"},J=n("span",{class:"btn"},"Upload File",-1),X={class:"area-settings"},ee=n("td",null,[n("label",null,"Title")],-1),te=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ne=n("td",null,[n("label",null,"Tags")],-1),le={class:"area-buttons"},ie=s("🧩 Post to gallery "),oe=n("br",null,null,-1),ae=s(" + set up game");Q.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay new-image-dialog",onClick:l[8]||(l[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[7]||(l[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(a(),t("div",Y,[n("span",{class:"remove btn",onClick:l[1]||(l[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(a(),t("div",K,[n("label",Z,[n("input",{type:"file",style:{display:"none"},onChange:l[2]||(l[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),J])]))],2),n("div",X,[n("table",null,[n("tr",null,[ee,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[3]||(l[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),te,n("tr",null,[ne,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[4]||(l[4]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",le,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:l[5]||(l[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"πŸ–ΌοΈ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:l[6]||(l[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[ie,oe,ae],8,["disabled"])])])])};var se=e({name:"edit-image-dialog",components:{ResponsiveImage:W,TagsInput:q},props:{image:{type:Object,required:!0}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const re={class:"area-image"},de={class:"has-image"},ce={class:"area-settings"},ue=n("td",null,[n("label",null,"Title")],-1),ge=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),pe=n("td",null,[n("label",null,"Tags")],-1),he={class:"area-buttons"};function me(e,t){const n=e.x-t.x,l=e.y-t.y;return Math.sqrt(n*n+l*l)}function ye(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}se.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay edit-image-dialog",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("div",re,[n("div",de,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ce,[n("table",null,[n("tr",null,[ue,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ge,n("tr",null,[pe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[2]||(l[2]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",he,[n("button",{class:"btn",onClick:l[3]||(l[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"πŸ–ΌοΈ Save image")])])])};var fe={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:me,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:ye,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 me(ye(e),ye(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 we=1,ve=4,be=2,xe=3,Ce=2,ke=4,Ae=3,ze=9,Se=1,Pe=2,Ie=3,Te=4,_e=5,De=6,Be=7,Ee=8,Oe=10,Ue=1,Ne=2,Me=3;class Ge{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}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),l=t[n];t[n]=t[e],t[e]=l}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new Ge(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const $e=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},Re=(...e)=>{const t=t=>(...n)=>{const l=new Date,i=$e(l.getHours(),"00"),o=$e(l.getMinutes(),"00"),a=$e(l.getSeconds(),"00");console[t](`${i}:${o}:${a}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var Ve,je,Fe,Le,We={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{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]}},encodeGame:function(e){return[e.id,e.rng.type||"",Ge.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Fe.FINAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:Ge.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}},coordByTileIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(let n in e){const l=[n,e[n]].map(encodeURIComponent);t.push(l.join("="))}return 0===t.length?"":`?${t.join("&")}`}};(je=Ve||(Ve={}))[je.Flat=0]="Flat",je[je.Out=1]="Out",je[je.In=-1]="In",(Le=Fe||(Fe={}))[Le.FINAL=0]="FINAL",Le[Le.ANY=1]="ANY";const qe={};function He(e,t){let n=0;for(let l of qe[e].players){if(We.decodePlayer(l).id===t)return n;n++}return-1}function Qe(e,t){const n=He(e,t);return-1===n?null:We.decodePlayer(qe[e].players[n])}function Ye(e,t,n){const l=He(e,t);-1===l?qe[e].players.push(We.encodePlayer(n)):qe[e].players[l]=We.encodePlayer(n)}function Ke(e,t){return-1!==He(e,t)}function Ze(e){return qe[e]?qe[e].players.map(We.decodePlayer):[]}function Je(e){return qe[e].puzzle.tiles.length}function Xe(e){return qe[e].scoreMode||0}function et(e){return tt(e)===Je(e)}function tt(e){let t=0;for(let n of qe[e].puzzle.tiles)-1===We.decodePiece(n).owner&&t++;return t}function nt(e,t,n){const l=Qe(e,t);if(null!==l){for(let e of Object.keys(n))l[e]=n[e];Ye(e,t,l)}}function lt(e,t){for(let n of Object.keys(t))qe[e].puzzle.data[n]=t[n]}function it(e,t,n){for(let l of Object.keys(n)){const i=We.decodePiece(qe[e].puzzle.tiles[t]);i[l]=n[l],qe[e].puzzle.tiles[t]=We.encodePiece(i)}}const ot=(e,t)=>We.decodePiece(qe[e].puzzle.tiles[t]),at=(e,t)=>ot(e,t).group,st=(e,t)=>{const n=qe[e].puzzle.info,l={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},i=function(e,t){const n=qe[e].puzzle.info,l=We.coordByTileIdx(n,t),i=l.x*n.tileSize,o=l.y*n.tileSize;return{x:i,y:o}}(e,t);return fe.pointAdd(l,i)},rt=(e,t)=>ot(e,t).pos,dt=e=>{const t=At(e),n=zt(e),l=Math.round(t/4),i=Math.round(n/4);return{x:0-l,y:0-i,w:t+2*l,h:n+2*i}},ct=(e,t)=>{const n=ht(e),l=ot(e,t);return{x:l.pos.x,y:l.pos.y,w:n,h:n}},ut=(e,t)=>ot(e,t).z,gt=(e,t)=>{for(let n of qe[e].puzzle.tiles){const e=We.decodePiece(n);if(e.owner===t)return e.idx}return-1},pt=e=>qe[e].puzzle.info.tileDrawSize,ht=e=>qe[e].puzzle.info.tileSize,mt=e=>qe[e].puzzle.data.maxGroup,yt=e=>qe[e].puzzle.data.maxZ;function ft(e,t){const n=qe[e].puzzle.info,l=We.coordByTileIdx(n,t);return[l.y>0?t-n.tilesX:-1,l.x0?t-1:-1]}const wt=(e,t,n)=>{for(let l of t)it(e,l,{z:n})},vt=(e,t,n)=>{const l=rt(e,t);it(e,t,{pos:fe.pointAdd(l,n)})},bt=(e,t,n)=>{const l=pt(e),i=dt(e),o=n;for(let a of t){const t=ot(e,a);t.pos.x+n.xi.x+i.w&&(o.x=Math.min(i.x+i.w-t.pos.x+l,o.x)),t.pos.y+n.yi.y+i.h&&(o.y=Math.min(i.y+i.h-t.pos.y+l,o.y))}for(let a of t)vt(e,a,o)},xt=(e,t,n)=>{for(let l of t)it(e,l,{owner:n})};function Ct(e,t){const n=qe[e].puzzle.tiles,l=We.decodePiece(n[t]),i=[];if(l.group)for(let o of n){const e=We.decodePiece(o);e.group===l.group&&i.push(e.idx)}else i.push(l.idx);return i}const kt=(e,t)=>{const n=Qe(e,t);return n?n.points:0},At=e=>qe[e].puzzle.info.table.width,zt=e=>qe[e].puzzle.info.table.height;var St={setGame:function(e,t){qe[e]=t},exists:function(e){return!!qe[e]||!1},playerExists:Ke,getActivePlayers:function(e,t){const n=t-30*_;return Ze(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return Ze(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Ke(e,t)?nt(e,t,{ts:n}):Ye(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:tt,getPieceCount:Je,getImageUrl:function(e){return qe[e].puzzle.info.imageUrl},setImageUrl:function(e,t){qe[e].puzzle.info.imageUrl=t},get:function(e){return qe[e]},getAllGames:function(){return Object.values(qe).sort(((e,t)=>et(e.id)===et(t.id)?t.puzzle.data.started-e.puzzle.data.started:et(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Qe(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Qe(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Qe(e,t);return n?n.name:null},getPlayerIndexById:He,getPlayerIdByIndex:function(e,t){return qe[e].players.length>t?We.decodePlayer(qe[e].players[t]).id:null},changePlayer:nt,setPlayer:Ye,setPiece:function(e,t,n){qe[e].puzzle.tiles[t]=We.encodePiece(n)},setPuzzleData:function(e,t){qe[e].puzzle.data=t},getTableWidth:At,getTableHeight:zt,getPuzzle:e=>qe[e].puzzle,getRng:e=>qe[e].rng.obj,getPuzzleWidth:e=>qe[e].puzzle.info.width,getPuzzleHeight:e=>qe[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return qe[e].puzzle.tiles.map(We.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=gt(e,t);return n<0?null:qe[e].puzzle.tiles[n]},getPieceDrawOffset:e=>qe[e].puzzle.info.tileDrawOffset,getPieceDrawSize:pt,getFinalPiecePos:st,getStartTs:e=>qe[e].puzzle.data.started,getFinishTs:e=>qe[e].puzzle.data.finished,handleInput:function(e,t,n,l){const i=qe[e].puzzle,o=function(e,t){return t in qe[e].evtInfos?qe[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),a=[],s=()=>{a.push([Ue,i.data])},r=t=>{a.push([Ne,We.encodePiece(ot(e,t))])},d=e=>{for(const t of e)r(t)},c=()=>{const n=Qe(e,t);n&&a.push([Me,We.encodePlayer(n)])},u=n[0];if(u===De){const i=n[1];nt(e,t,{bgcolor:i,ts:l}),c()}else if(u===Be){const i=n[1];nt(e,t,{color:i,ts:l}),c()}else if(u===Ee){const i=`${n[1]}`.substr(0,16);nt(e,t,{name:i,ts:l}),c()}else if(u===Se){const i={x:n[1],y:n[2]};nt(e,t,{d:1,ts:l}),c(),o._last_mouse_down=i;const a=((e,t)=>{let n=qe[e].puzzle.info,l=qe[e].puzzle.tiles,i=-1,o=-1;for(let a=0;ai)&&(i=e.z,o=a)}return o})(e,i);if(a>=0){let n=yt(e)+1;lt(e,{maxZ:n}),s();const l=Ct(e,a);wt(e,l,yt(e)),xt(e,l,t),d(l)}o._last_mouse=i}else if(u===Ie){const i=n[1],a=n[2],s={x:i,y:a};if(null===o._last_mouse_down)nt(e,t,{x:i,y:a,ts:l}),c();else{let n=gt(e,t);if(n>=0){nt(e,t,{x:i,y:a,ts:l}),c();const r=Ct(e,n);let u=fe.pointInBounds(s,dt(e))&&fe.pointInBounds(o._last_mouse_down,dt(e));for(let t of r){const n=ct(e,t);if(fe.pointInBounds(s,n)){u=!0;break}}if(u){const t=i-o._last_mouse_down.x,n=a-o._last_mouse_down.y;bt(e,r,{x:t,y:n}),d(r)}}else nt(e,t,{ts:l}),c();o._last_mouse_down=s}o._last_mouse=s}else if(u===Pe){const a={x:n[1],y:n[2]},u=0;o._last_mouse_down=null;let g=gt(e,t);if(g>=0){let n=Ct(e,g);xt(e,n,0),d(n);let o=rt(e,g),a=st(e,g);if(fe.pointDistance(a,o){for(let n of t)it(e,n,{owner:-1,z:1})})(e,n),d(n);let r=kt(e,t);0===Xe(e)?r+=n.length:1===Xe(e)&&(r+=1),nt(e,t,{d:u,ts:l,points:r}),c(),tt(e)===Je(e)&&(lt(e,{finished:l}),s())}else{const n=(e,t,n,l)=>{let i=qe[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const l=at(e,t),i=at(e,n);return!(!l||l!==i)})(e,t,n))return!1;const o=rt(e,t),a=fe.pointAdd(rt(e,n),{x:l[0]*i.tileSize,y:l[1]*i.tileSize});if(fe.pointDistance(o,a){const l=qe[e].puzzle.tiles,i=at(e,t),o=at(e,n);let a;const d=[];i&&d.push(i),o&&d.push(o),i?a=i:o?a=o:(lt(e,{maxGroup:mt(e)+1}),s(),a=mt(e));if(it(e,t,{group:a}),r(t),it(e,n,{group:a}),r(n),d.length>0)for(const s of l){const t=We.decodePiece(s);d.includes(t.group)&&(it(e,t.idx,{group:a}),r(t.idx))}})(e,t,n),i=Ct(e,t);const c=((e,t)=>{let n=0;for(let l of t){let t=ut(e,l);t>n&&(n=t)}return n})(e,i);return wt(e,i,c),d(i),!0}return!1};let i=!1;for(let t of Ct(e,g)){let l=ft(e,t);if(n(e,t,l[0],[0,1])||n(e,t,l[1],[-1,0])||n(e,t,l[2],[0,-1])||n(e,t,l[3],[1,0])){i=!0;break}}if(i&&1===Xe(e)){const n=kt(e,t)+1;nt(e,t,{d:u,ts:l,points:n}),c()}else nt(e,t,{d:u,ts:l}),c()}}else nt(e,t,{d:u,ts:l}),c();o._last_mouse=a}else if(u===Te){const i=n[1],a=n[2];nt(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else if(u===_e){const i=n[1],a=n[2];nt(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else nt(e,t,{ts:l}),c();return function(e,t,n){qe[e].evtInfos[t]=n}(e,t,o),a}},Pt=e({name:"new-game-dialog",components:{ResponsiveImage:W},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Fe.ANY}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const It={class:"area-image"},Tt={class:"has-image"},_t={class:"area-settings"},Dt=n("td",null,[n("label",null,"Pieces")],-1),Bt=n("td",null,[n("label",null,"Scoring: ")],-1),Et=s(" Any (Score when pieces are connected to each other or on final location)"),Ot=n("br",null,null,-1),Ut=s(" Final (Score when pieces are put to their final location)"),Nt={class:"area-buttons"};Pt.render=function(e,l,i,s,r,d){const c=o("responsive-image");return a(),t("div",{class:"overlay new-game-dialog",onClick:l[6]||(l[6]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[5]||(l[5]=u((()=>{}),["stop"]))},[n("div",It,[n("div",Tt,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",_t,[n("table",null,[n("tr",null,[Dt,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Bt,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[2]||(l[2]=t=>e.scoreMode=t),value:"1"},null,512),[[y,e.scoreMode]]),Et]),Ot,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[3]||(l[3]=t=>e.scoreMode=t),value:"0"},null,512),[[y,e.scoreMode]]),Ut])])])])]),n("div",Nt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:l[4]||(l[4]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var Mt=e({components:{ImageLibrary:L,NewImageDialog:Q,EditImageDialog:se,NewGameDialog:Pt},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},methods:{toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${We.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},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}})}}}});const Gt={class:"upload-image-teaser"},$t=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),Rt={key:0},Vt=s(" Tags: "),jt=s(" Sort by: "),Ft=n("option",{value:"date_desc"},"Newest first",-1),Lt=n("option",{value:"date_asc"},"Oldest first",-1),Wt=n("option",{value:"alpha_asc"},"A-Z",-1),qt=n("option",{value:"alpha_desc"},"Z-A",-1);Mt.render=function(e,l,s,u,p,h){const m=o("image-library"),y=o("new-image-dialog"),w=o("edit-image-dialog"),v=o("new-game-dialog");return a(),t("div",null,[n("div",Gt,[n("div",{class:"btn btn-big",onClick:l[1]||(l[1]=t=>e.dialog="new-image")},"Upload your image"),$t]),n("div",null,[e.tags.length>0?(a(),t("label",Rt,[Vt,(a(!0),t(d,null,c(e.tags,((n,l)=>(a(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:l,onClick:t=>e.toggleTag(n)},r(n.title),11,["onClick"])))),128))])):i("",!0),n("label",null,[jt,g(n("select",{"onUpdate:modelValue":l[2]||(l[2]=t=>e.filters.sort=t),onChange:l[3]||(l[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[Ft,Lt,Wt,qt],544),[[f,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(a(),t(y,{key:0,onBgclick:l[4]||(l[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["onPostToGalleryClick","onSetupGameClick"])):i("",!0),"edit-image"===e.dialog?(a(),t(w,{key:1,onBgclick:l[5]||(l[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["onSaveClick","image"])):i("",!0),e.image&&"new-game"===e.dialog?(a(),t(v,{key:2,onBgclick:l[6]||(l[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):i("",!0)])};var Ht=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 Qt={class:"scores"},Yt=n("div",null,"Scores",-1),Kt=n("td",null,"⚑",-1),Zt=n("td",null,"πŸ’€",-1);Ht.render=function(e,l,i,o,s,u){return a(),t("div",Qt,[Yt,n("table",null,[(a(!0),t(d,null,c(e.actives,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[Kt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(a(!0),t(d,null,c(e.idles,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[Zt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var Jt=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 E(this.duration)}}});const Xt={class:"timer"};Jt.render=function(e,l,i,o,s,d){return a(),t("div",Xt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),w(e.$slots,"default")])};var en=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 tn=n("td",null,[n("label",null,"Background: ")],-1),nn=n("td",null,[n("label",null,"Color: ")],-1),ln=n("td",null,[n("label",null,"Name: ")],-1);en.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("tr",null,[tn,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[1]||(l[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[nn,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[2]||(l[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[ln,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":l[3]||(l[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])])])])};var on=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const an={class:"preview"};on.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay",onClick:l[1]||(l[1]=t=>e.$emit("bgclick"))},[n("div",an,[n("div",{class:"img",style:e.previewStyle},null,4)])])};const sn=Re("Communication.js");let rn,dn=e=>{},cn=e=>{};let un=0;const gn=e=>{un!==e&&(un=e,cn(e))};function pn(e){if(2===un)try{rn.send(JSON.stringify(e))}catch(t){sn.info("unable to send message.. maybe because ws is invalid?")}}let hn,mn;var yn={connect:function(e,t,n){return hn=0,mn={},gn(3),new Promise((l=>{rn=new WebSocket(e,n+"|"+t),rn.onopen=e=>{gn(2),pn([xe])},rn.onmessage=e=>{const t=JSON.parse(e.data),i=t[0];if(i===ve){const e=t[1];l(e)}else{if(i!==we)throw`[ 2021-05-09 invalid connect msgType ${i} ]`;{const e=t[1],l=t[2];if(e===n&&mn[l])return void delete mn[l];dn(t)}}},rn.onerror=e=>{throw gn(1),"[ 2021-05-15 onerror ]"},rn.onclose=e=>{4e3===e.code||1001===e.code?gn(4):gn(1)}}))},requestReplayData:async function(e,t,n){const l={gameId:e,offset:t,size:n},i=await fetch(`/api/replay-data${We.asQueryArgs(l)}`);return await i.json()},disconnect:function(){rn&&rn.close(4e3),hn=0,mn={}},sendClientEvent:function(e){hn++,mn[hn]=e,pn([be,hn,mn[hn]])},onServerChange:function(e){dn=e},onConnectionStateChange:function(e){cn=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},fn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===yn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===yn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const wn={key:0,class:"overlay connection-lost"},vn={key:0,class:"overlay-content"},bn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),xn={key:1,class:"overlay-content"},Cn=n("div",null,"Connecting...",-1);fn.render=function(e,l,o,s,r,d){return e.show?(a(),t("div",wn,[e.lostConnection?(a(),t("div",vn,[bn,n("span",{class:"btn",onClick:l[1]||(l[1]=t=>e.$emit("reconnect"))},"Reconnect")])):i("",!0),e.connecting?(a(),t("div",xn,[Cn])):i("",!0)])):i("",!0)};var kn=e({name:"help-overlay",emits:{bgclick:null}});const An=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/πŸ–±οΈ")])])],-1),zn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/πŸ–±οΈ")])])],-1),Sn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/πŸ–±οΈ")])])],-1),Pn=n("tr",null,[n("td",null,"➑️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"β†’"),s("/πŸ–±οΈ")])])],-1),In=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Tn=n("tr",null,[n("td",null,"πŸ”+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/πŸ–±οΈ-Wheel")])])],-1),_n=n("tr",null,[n("td",null,"πŸ”- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/πŸ–±οΈ-Wheel")])])],-1),Dn=n("tr",null,[n("td",null,"πŸ–ΌοΈ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Bn=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);kn.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[An,zn,Sn,Pn,In,Tn,_n,Dn,Bn,En])])};var On=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Un=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Mn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Gn(){let e=0,t=0,n=1;const l=(l,i)=>{e+=l/n,t+=i/n},i=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},o=l=>({x:l.x/n-e,y:l.y/n-t}),a=l=>({x:(l.x+e)*n,y:(l.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n});return{move:l,canZoom:e=>n!=i(e),zoom:(e,t)=>((e,t)=>{if(n==e)return!1;const i=1-n/e;return l(-t.x*i,-t.y*i),n=e,!0})(i(e),t),worldToViewport:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:a,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=o(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:o}}function $n(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Rn={createCanvas:$n,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 l=$n(t,n);return l.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(l)},colorizedCanvas:function(e,t,n){const l=$n(e.width,e.height),i=l.getContext("2d");return i.save(),i.drawImage(t,0,0),i.fillStyle=n,i.globalCompositeOperation="source-in",i.fillRect(0,0,t.width,t.height),i.restore(),i.save(),i.globalCompositeOperation="destination-over",i.drawImage(e,0,0),i.restore(),l}};const Vn=Re("Debug.js");let jn=0,Fn=0;var Ln=e=>{jn=performance.now(),Fn=e},Wn=e=>{const t=performance.now(),n=t-jn;n>Fn&&Vn.log(e+": "+n),jn=t};const qn=Re("PuzzleGraphics.js");function Hn(e,t){const n=We.coordByTileIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Qn={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){qn.log("start createPuzzleTileBitmaps");var l=n.tileSize,i=n.tileMarginWidth,o=n.tileDrawSize,a=l/100,s=[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,o={x:i,y:i},r=fe.pointAdd(o,{x:l,y:0}),c=fe.pointAdd(r,{x:0,y:l}),u=fe.pointSub(c,{x:l,y:0});if(n.moveTo(o.x,o.y),0!==e.top)for(let l=0;l=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;Zn=t/2,Jn=t-Zn;const n=1/4*this.canvas.width/(t/2);Yn=-n,Kn=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Xn(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Xn(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(!h[t]){const n=e.d?a:s;if(e.color){const l=e.d?r:d;h[t]=await createImageBitmap(Rn.colorizedCanvas(n,l,e.color))}else h[t]=n}return h[t]},y=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,ol=!0})),t}(i,Rn.createCanvas()),f={final:!1,requesting:!0,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,dataOffset:0,dataSize:1e4};yn.onConnectionStateChange((e=>{o.setConnectionState(e)}));const w=async e=>{f.requesting=!0;const t=await yn.requestReplayData(e,f.dataOffset,f.dataSize);return f.dataOffset+=f.dataSize,f.requesting=!1,t};let v=()=>0;const b=async()=>{if("play"===l){const l=await yn.connect(n,e,t),i=We.decodeGame(l);St.setGame(i.id,i),v=()=>D()}else{if("replay"!==l)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await w(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=We.decodeGame(t.game);St.setGame(n.id,n),f.requesting=!1,f.log=t.log,f.lastRealTs=D(),f.gameStartTs=parseInt(f.log[0][4],10),f.lastGameTs=f.gameStartTs,v=()=>f.lastGameTs}}ol=!0};await b();const x=St.getPieceDrawOffset(e),C=St.getPieceDrawSize(e),k=St.getPuzzleWidth(e),A=St.getPuzzleHeight(e),z=St.getTableWidth(e),S=St.getTableHeight(e),P={x:(z-k)/2,y:(S-A)/2},I={w:k,h:A},T={w:C,h:C},_=await Qn.loadPuzzleBitmaps(St.getPuzzle(e)),B=new tl(y,St.getRng(e));B.init();const E=y.getContext("2d");y.classList.add("loaded");const O=Gn();O.move(-(z-y.width)/2,-(S-y.height)/2);const U=function(e,t,n){let l=[],i=!0,o=!1,a=!1,s=!1,r=!1,d=!1,c=!1,u=!1;const g=(e,t)=>{const l=n.viewportToWorld({x:e,y:t});return[l.x,l.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{i&&("Shift"===t.key?u=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?s=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?r=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?o=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?a=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};e.addEventListener("mousedown",(e=>{0===e.button&&y([Se,...p(e)])})),e.addEventListener("mouseup",(e=>{0===e.button&&y([Pe,...p(e)])})),e.addEventListener("mousemove",(e=>{y([Ie,...p(e)])})),e.addEventListener("wheel",(e=>{if(n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Te:_e;y([t,...p(e)])}})),t.addEventListener("keydown",(e=>m(!0,e))),t.addEventListener("keyup",(e=>m(!1,e))),t.addEventListener("keypress",(e=>{i&&(" "===e.key&&y([Oe]),"F"!==e.key&&"f"!==e.key||(ll=!ll,ol=!0),"G"!==e.key&&"g"!==e.key||(il=!il,ol=!0))}));const y=e=>{l.push(e)};return{addEvent:y,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=u?20:10,t=(o?e:0)-(a?e:0),l=(s?e:0)-(r?e:0);0===t&&0===l||y([ze,t,l]),d&&c||(d?n.canZoom("in")&&y([Te,...h()]):c&&n.canZoom("out")&&y([_e,...h()]))},setHotkeys:e=>{i=e}}}(y,window,O),N=St.getImageUrl(e),M=()=>{const t=St.getStartTs(e),n=St.getFinishTs(e),l=v();o.setFinished(!!n),o.setDuration((n||l)-t)};M(),o.setPiecesDone(St.getFinishedPiecesCount(e)),o.setPiecesTotal(St.getPieceCount(e));const G=v();o.setActivePlayers(St.getActivePlayers(e,G)),o.setIdlePlayers(St.getIdlePlayers(e,G));const $=!!St.getFinishTs(e);let R=$;const V=()=>R&&!$,j=()=>St.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",F=()=>St.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let L="",W="",q=!1;const H=e=>{q=e;const[t,n]=e?[L,"grab"]:[W,"default"];y.style.cursor=`url('${t}') ${u} ${p}, ${n}`},Q=e=>{L=Rn.colorizedCanvas(a,r,e).toDataURL(),W=Rn.colorizedCanvas(s,d,e).toDataURL(),H(q)};Q(F());const Y=()=>{o.setReplaySpeed&&o.setReplaySpeed(f.speeds[f.speedIdx]),o.setReplayPaused&&o.setReplayPaused(f.paused)};if("play"===l?setInterval(M,1e3):"replay"===l&&Y(),"play"===l)yn.onServerChange((n=>{n[0],n[1],n[2];const l=n[3];for(const[i,o]of l)switch(i){case Me:{const n=We.decodePlayer(o);n.id!==t&&(St.setPlayer(e,n.id,n),ol=!0)}break;case Ne:{const t=We.decodePiece(o);St.setPiece(e,t.idx,t),ol=!0}break;case Ue:St.setPuzzleData(e,o),ol=!0}R=!!St.getFinishTs(e)}));else if("replay"===l){let t=setInterval((()=>{const n=D();if(f.requesting)return void(f.lastRealTs=n);if(f.logPointer+1>=f.log.length)return f.lastRealTs=n,void(async e=>{const t=await w(e);f.log=f.log.slice(f.logPointer),f.logPointer=0,f.log.push(...t.log),t.log.length=f.log.length){f.final&&clearInterval(t);break}const l=f.log[n],o=f.gameStartTs+l[l.length-1];if(o>i)break;const a=l.slice();if(a[0]===Ce){const t=a[1];St.addPlayer(e,t,o),ol=!0}else if(a[0]===ke){const t=St.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";St.addPlayer(e,t,o),ol=!0}else if(a[0]===Ae){const t=St.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=a[2];St.handleInput(e,t,n,o),ol=!0}f.logPointer=n}f.lastRealTs=n,f.lastGameTs=i,M()}),50)}let K=null;return(e=>{const t=e.fps||60,n=e.slow||1,l=e.update,i=e.render,o=window.requestAnimationFrame,a=1/t,s=n*a;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>s;)d-=s,l(a);i(d/n),c=r,o(u)};o(u)})({update:()=>{U.createKeyEvents();for(const n of U.consumeAll())if("play"===l){const l=n[0];if(l===ze){const e=n[1],t=n[2];ol=!0,O.move(e,t)}else if(l===Ie){if(K&&!St.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=O.worldToViewport(e),l=Math.round(t.x-K.x),i=Math.round(t.y-K.y);ol=!0,O.move(l,i),K=t}}else if(l===Be)Q(n[1]);else if(l===Se){const e={x:n[1],y:n[2]};K=O.worldToViewport(e),H(!0)}else if(l===Pe)K=null,H(!1);else if(l===Te){const e={x:n[1],y:n[2]};ol=!0,O.zoom("in",O.worldToViewport(e))}else if(l===_e){const e={x:n[1],y:n[2]};ol=!0,O.zoom("out",O.worldToViewport(e))}else l===Oe&&o.togglePreview();const i=v();St.handleInput(e,t,n,i).length>0&&(ol=!0),yn.sendClientEvent(n)}else if("replay"===l){const e=n[0];if(e===ze){const e=n[1],t=n[2];ol=!0,O.move(e,t)}else if(e===Ie){if(K){const e={x:n[1],y:n[2]},t=O.worldToViewport(e),l=Math.round(t.x-K.x),i=Math.round(t.y-K.y);ol=!0,O.move(l,i),K=t}}else if(e===Se){const e={x:n[1],y:n[2]};K=O.worldToViewport(e)}else if(e===Pe)K=null;else if(e===Te){const e={x:n[1],y:n[2]};ol=!0,O.zoom("in",O.worldToViewport(e))}else if(e===_e){const e={x:n[1],y:n[2]};ol=!0,O.zoom("out",O.worldToViewport(e))}else e===Oe&&o.togglePreview()}R=!!St.getFinishTs(e),V()&&(B.update(),ol=!0)},render:async()=>{if(!ol)return;const n=v();let i,a,s;window.DEBUG&&Ln(0),E.fillStyle=j(),E.fillRect(0,0,y.width,y.height),window.DEBUG&&Wn("clear done"),i=O.worldToViewportRaw(P),a=O.worldDimToViewportRaw(I),E.fillStyle="rgba(255, 255, 255, .3)",E.fillRect(i.x,i.y,a.w,a.h),window.DEBUG&&Wn("board done");const r=St.getPiecesSortedByZIndex(e);window.DEBUG&&Wn("get tiles done"),a=O.worldDimToViewportRaw(T);for(const e of r)(-1===e.owner?ll:il)&&(s=_[e.idx],i=O.worldToViewportRaw({x:x+e.pos.x,y:x+e.pos.y}),E.drawImage(s,0,0,s.width,s.height,i.x,i.y,a.w,a.h));window.DEBUG&&Wn("tiles done");const d=[];for(const o of St.getActivePlayers(e,n))c=o,("replay"===l||c.id!==t)&&(s=await m(o),i=O.worldToViewport(o),E.drawImage(s,i.x-u,i.y-p),d.push([`${o.name} (${o.points})`,i.x,i.y+g]));var c;E.fillStyle="white",E.textAlign="center";for(const[e,t,l]of d)E.fillText(e,t,l);window.DEBUG&&Wn("players done"),o.setActivePlayers(St.getActivePlayers(e,n)),o.setIdlePlayers(St.getIdlePlayers(e,n)),o.setPiecesDone(St.getFinishedPiecesCount(e)),window.DEBUG&&Wn("HUD done"),V()&&B.render(),ol=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([De,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([Be,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Ee,e])},replayOnSpeedUp:()=>{f.speedIdx+1{f.speedIdx>=1&&(f.speedIdx--,Y())},replayOnPauseToggle:()=>{f.paused=!f.paused,Y()},previewImageUrl:N,player:{background:j(),color:F(),name:St.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon"},disconnect:yn.disconnect,connect:b}}var sl=e({name:"game",components:{PuzzleStatus:Jt,Scores:Ht,SettingsOverlay:en,PreviewOverlay:on,ConnectionOverlay:fn,HelpOverlay:kn},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 al(`${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 rl={id:"game"},dl={class:"menu"},cl={class:"tabs"},ul=s("🧩 Puzzles");sl.render=function(e,i,s,r,d,c){const u=o("settings-overlay"),p=o("preview-overlay"),h=o("help-overlay"),m=o("connection-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",rl,[g(n(u,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(p,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(h,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",dl,[n("div",cl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[ul])),_:1}),n("div",{class:"opener",onClick:i[5]||(i[5]=t=>e.toggle("preview",!1))},"πŸ–ΌοΈ Preview"),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("settings",!0))},"πŸ› οΈ Settings"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var gl=e({name:"replay",components:{PuzzleStatus:Jt,Scores:Ht,SettingsOverlay:en,PreviewOverlay:on,HelpOverlay:kn},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 al(`${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 pl={id:"replay"},hl={class:"menu"},ml={class:"tabs"},yl=s("🧩 Puzzles");gl.render=function(e,i,s,d,c,u){const p=o("settings-overlay"),h=o("preview-overlay"),m=o("help-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",pl,[g(n(p,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(m,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:l((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:i[5]||(i[5]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",hl,[n("div",ml,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[yl])),_:1}),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("preview",!1))},"πŸ–ΌοΈ Preview"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("settings",!0))},"πŸ› οΈ Settings"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=b({history:x(),routes:[{name:"index",path:"/",component:R},{name:"new-game",path:"/new-game",component:Mt},{name:"game",path:"/g/:id",component:sl},{name:"replay",path:"/replay/:id",component:gl}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const l=C(k);l.config.globalProperties.$config=t,l.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=We.uniqId(),localStorage.setItem("ID",e)),e}(),l.use(n),l.mount("#app")})(); diff --git a/build/public/assets/index.ae0a2617.js b/build/public/assets/index.ae0a2617.js new file mode 100644 index 0000000..d76b69f --- /dev/null +++ b/build/public/assets/index.ae0a2617.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as o,b as i,r as l,o as a,e as s,t as r,F as c,f as d,g as u,h as g,v as p,i as h,j as m,k as y,l as f,m as w,n as v,p as b,q as x,s as C}from"./vendor.b622ee49.js";var k=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const A={id:"app"},z={key:0,class:"nav"},S=s("Index"),P=s("New game");k.render=function(e,s,r,c,d,u){const g=l("router-link"),p=l("router-view");return a(),t("div",A,[e.showNav?(a(),t("ul",z,[n("li",null,[n(g,{class:"btn",to:{name:"index"}},{default:o((()=>[S])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:o((()=>[P])),_:1})])])):i("",!0),n(p)])};const I=864e5,T=e=>{const t=Math.floor(e/I);e%=I;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 _=1e3,D=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>T(t-e),E=T,O=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,i=t||D();return`${n} ${B(o,i)}`}}});const N={class:"game-info-text"},U=n("br",null,null,-1),M=n("br",null,null,-1),G=n("br",null,null,-1),$=s(" β†ͺ️ Watch replay ");O.render=function(e,c,d,u,g,p){const h=l("router-link");return a(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",N,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),U,s(" πŸ‘₯ "+r(e.game.players),1),M,s(" "+r(e.time(e.game.started,e.game.finished)),1),G])])),_:1},8,["to"]),e.game.hasReplay?(a(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[$])),_:1},8,["to"])):i("",!0)],4)};var R=e({components:{GameTeaser:O},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 V=n("h1",null,"Running games",-1),j=n("h1",null,"Finished games",-1);R.render=function(e,o,i,s,r,u){const g=l("game-teaser");return a(),t("div",null,[V,(a(!0),t(c,null,d(e.gamesRunning,((e,o)=>(a(),t("div",{class:"game-teaser-wrap",key:o},[n(g,{game:e},null,8,["game"])])))),128)),j,(a(!0),t(c,null,d(e.gamesFinished,((e,o)=>(a(),t("div",{class:"game-teaser-wrap",key:o},[n(g,{game:e},null,8,["game"])])))),128))])};var F=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});F.render=function(e,o,i,l,s,r){return a(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var L=e({name:"image-library",components:{ImageTeaser:F},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});L.render=function(e,n,o,i,s,r){const u=l("image-teaser");return a(),t("div",null,[(a(!0),t(c,null,d(e.images,((n,o)=>(a(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])};const W={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};W.render=function(e,n,o,i,l,s){return a(),t("div",{style:s.style,title:o.title},null,12,["title"])};var q=e({name:"tags-input",props:{modelValue:{type:Array,required:!0}},emits:{"update:modelValue":null},data:()=>({input:"",values:[]}),created(){this.values=this.modelValue},methods:{onKeyUp(e){if(","===e.key)return this.add(),e.stopPropagation(),!1},add(){const e=this.input.replace(/,/g,"").trim();e&&(this.values.includes(e)||this.values.push(e),this.input="",this.$emit("update:modelValue",this.values))},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const H=m()(((e,o,i,l,s,u)=>(a(),t("div",null,[g(n("input",{class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onKeydown:o[2]||(o[2]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[3]||(o[3]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[p,e.input]]),(a(!0),t(c,null,d(e.values,((n,o)=>(a(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" βœ–",9,["onClick"])))),128))]))));q.render=H,q.__scopeId="data-v-771460ae";var Q=e({name:"new-image-dialog",components:{ResponsiveImage:W,TagsInput:q},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const o=new FileReader;o.readAsDataURL(n),o.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const Y={key:0,class:"has-image"},K={key:1},Z={class:"upload"},J=n("span",{class:"btn"},"Upload File",-1),X={class:"area-settings"},ee=n("td",null,[n("label",null,"Title")],-1),te=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ne=n("td",null,[n("label",null,"Tags")],-1),oe={class:"area-buttons"},ie=s("🧩 Post to gallery "),le=n("br",null,null,-1),ae=s(" + set up game");Q.render=function(e,o,i,s,r,c){const d=l("responsive-image"),h=l("tags-input");return a(),t("div",{class:"overlay new-image-dialog",onClick:o[8]||(o[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[7]||(o[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(a(),t("div",Y,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(d,{src:e.previewUrl},null,8,["src"])])):(a(),t("div",K,[n("label",Z,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),J])]))],2),n("div",X,[n("table",null,[n("tr",null,[ee,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[3]||(o[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),te,n("tr",null,[ne,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[4]||(o[4]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",oe,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[5]||(o[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"πŸ–ΌοΈ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[6]||(o[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[ie,le,ae],8,["disabled"])])])])};var se=e({name:"edit-image-dialog",components:{ResponsiveImage:W,TagsInput:q},props:{image:{type:Object,required:!0}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const re={class:"area-image"},ce={class:"has-image"},de={class:"area-settings"},ue=n("td",null,[n("label",null,"Title")],-1),ge=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),pe=n("td",null,[n("label",null,"Tags")],-1),he={class:"area-buttons"};var me,ye,fe,we;se.render=function(e,o,i,s,r,c){const d=l("responsive-image"),h=l("tags-input");return a(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",re,[n("div",ce,[n(d,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",de,[n("table",null,[n("tr",null,[ue,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ge,n("tr",null,[pe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",he,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"πŸ–ΌοΈ Save image")])])])},(ye=me||(me={}))[ye.Flat=0]="Flat",ye[ye.Out=1]="Out",ye[ye.In=-1]="In",(we=fe||(fe={}))[we.FINAL=0]="FINAL",we[we.ANY=1]="ANY";var ve=e({name:"new-game-dialog",components:{ResponsiveImage:W},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:fe.ANY}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const be={class:"area-image"},xe={class:"has-image"},Ce={class:"area-settings"},ke=n("td",null,[n("label",null,"Pieces")],-1),Ae=n("td",null,[n("label",null,"Scoring: ")],-1),ze=s(" Any (Score when pieces are connected to each other or on final location)"),Se=n("br",null,null,-1),Pe=s(" Final (Score when pieces are put to their final location)"),Ie={class:"area-buttons"};ve.render=function(e,o,i,s,r,c){const d=l("responsive-image");return a(),t("div",{class:"overlay new-game-dialog",onClick:o[6]||(o[6]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[5]||(o[5]=u((()=>{}),["stop"]))},[n("div",be,[n("div",xe,[n(d,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ce,[n("table",null,[n("tr",null,[ke,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Ae,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[y,e.scoreMode]]),ze]),Se,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[y,e.scoreMode]]),Pe])])])])]),n("div",Ie,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[4]||(o[4]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};class Te{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){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;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new Te(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const _e=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},De=(...e)=>{const t=t=>(...n)=>{const o=new Date,i=_e(o.getHours(),"00"),l=_e(o.getMinutes(),"00"),a=_e(o.getSeconds(),"00");console[t](`${i}:${l}:${a}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var Be={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{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]}},encodeGame:function(e){return[e.id,e.rng.type||"",Te.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||fe.FINAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:Te.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}},Ee=e({components:{ImageLibrary:L,NewImageDialog:Q,EditImageDialog:se,NewGameDialog:ve},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},methods:{toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${Be.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},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}})}}}});const Oe={class:"upload-image-teaser"},Ne=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),Ue={key:0},Me=s(" Tags: "),Ge=s(" Sort by: "),$e=n("option",{value:"date_desc"},"Newest first",-1),Re=n("option",{value:"date_asc"},"Oldest first",-1),Ve=n("option",{value:"alpha_asc"},"A-Z",-1),je=n("option",{value:"alpha_desc"},"Z-A",-1);Ee.render=function(e,o,s,u,p,h){const m=l("image-library"),y=l("new-image-dialog"),w=l("edit-image-dialog"),v=l("new-game-dialog");return a(),t("div",null,[n("div",Oe,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),Ne]),n("div",null,[e.tags.length>0?(a(),t("label",Ue,[Me,(a(!0),t(c,null,d(e.tags,((n,o)=>(a(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title),11,["onClick"])))),128))])):i("",!0),n("label",null,[Ge,g(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[$e,Re,Ve,je],544),[[f,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(a(),t(y,{key:0,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["onPostToGalleryClick","onSetupGameClick"])):i("",!0),"edit-image"===e.dialog?(a(),t(w,{key:1,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["onSaveClick","image"])):i("",!0),e.image&&"new-game"===e.dialog?(a(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):i("",!0)])};var Fe=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 Le={class:"scores"},We=n("div",null,"Scores",-1),qe=n("td",null,"⚑",-1),He=n("td",null,"πŸ’€",-1);Fe.render=function(e,o,i,l,s,u){return a(),t("div",Le,[We,n("table",null,[(a(!0),t(c,null,d(e.actives,((e,o)=>(a(),t("tr",{key:o,style:{color:e.color}},[qe,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(a(!0),t(c,null,d(e.idles,((e,o)=>(a(),t("tr",{key:o,style:{color:e.color}},[He,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var Qe=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 E(this.duration)}}});const Ye={class:"timer"};Qe.render=function(e,o,i,l,s,c){return a(),t("div",Ye,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),w(e.$slots,"default")])};var Ke=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 Ze=n("td",null,[n("label",null,"Background: ")],-1),Je=n("td",null,[n("label",null,"Color: ")],-1),Xe=n("td",null,[n("label",null,"Name: ")],-1);Ke.render=function(e,o,i,l,s,r){return a(),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]=u((()=>{}),["stop"]))},[n("tr",null,[Ze,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[Je,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[Xe,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])])])])};var et=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const tt={class:"preview"};et.render=function(e,o,i,l,s,r){return a(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",tt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var nt=1,ot=4,it=2,lt=3,at=2,st=4,rt=3,ct=9,dt=1,ut=2,gt=3,pt=4,ht=5,mt=6,yt=7,ft=8,wt=10,vt=1,bt=2,xt=3;const Ct=De("Communication.js");let kt,At=e=>{},zt=e=>{};let St=0;const Pt=e=>{St!==e&&(St=e,zt(e))};function It(e){if(2===St)try{kt.send(JSON.stringify(e))}catch(t){Ct.info("unable to send message.. maybe because ws is invalid?")}}let Tt,_t;var Dt={connect:function(e,t,n){return Tt=0,_t={},Pt(3),new Promise((o=>{kt=new WebSocket(e,n+"|"+t),kt.onopen=()=>{Pt(2),It([lt])},kt.onmessage=e=>{const t=JSON.parse(e.data),i=t[0];if(i===ot){const e=t[1];o(e)}else{if(i!==nt)throw`[ 2021-05-09 invalid connect msgType ${i} ]`;{const e=t[1],o=t[2];if(e===n&&_t[o])return void delete _t[o];At(t)}}},kt.onerror=()=>{throw Pt(1),"[ 2021-05-15 onerror ]"},kt.onclose=e=>{4e3===e.code||1001===e.code?Pt(4):Pt(1)}}))},requestReplayData:async function(e,t,n){const o={gameId:e,offset:t,size:n},i=await fetch(`/api/replay-data${Be.asQueryArgs(o)}`);return await i.json()},disconnect:function(){kt&&kt.close(4e3),Tt=0,_t={}},sendClientEvent:function(e){Tt++,_t[Tt]=e,It([it,Tt,_t[Tt]])},onServerChange:function(e){At=e},onConnectionStateChange:function(e){zt=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},Bt=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Dt.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Dt.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const Et={key:0,class:"overlay connection-lost"},Ot={key:0,class:"overlay-content"},Nt=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Ut={key:1,class:"overlay-content"},Mt=n("div",null,"Connecting...",-1);Bt.render=function(e,o,l,s,r,c){return e.show?(a(),t("div",Et,[e.lostConnection?(a(),t("div",Ot,[Nt,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):i("",!0),e.connecting?(a(),t("div",Ut,[Mt])):i("",!0)])):i("",!0)};var Gt=e({name:"help-overlay",emits:{bgclick:null}});const $t=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/πŸ–±οΈ")])])],-1),Rt=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/πŸ–±οΈ")])])],-1),Vt=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/πŸ–±οΈ")])])],-1),jt=n("tr",null,[n("td",null,"➑️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"β†’"),s("/πŸ–±οΈ")])])],-1),Ft=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Lt=n("tr",null,[n("td",null,"πŸ”+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/πŸ–±οΈ-Wheel")])])],-1),Wt=n("tr",null,[n("td",null,"πŸ”- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/πŸ–±οΈ-Wheel")])])],-1),qt=n("tr",null,[n("td",null,"πŸ–ΌοΈ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Ht=n("tr",null,[n("td",null,"πŸ§©βœ”οΈ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Qt=n("tr",null,[n("td",null,"πŸ§©β“ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1);Gt.render=function(e,o,i,l,s,r){return a(),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]=u((()=>{}),["stop"]))},[$t,Rt,Vt,jt,Ft,Lt,Wt,qt,Ht,Qt])])};var Yt=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Kt=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Zt=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Jt=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Xt(){let e=0,t=0,n=1;const o=(o,i)=>{e+=o/n,t+=i/n},i=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},l=o=>({x:o.x/n-e,y:o.y/n-t}),a=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n});return{move:o,canZoom:e=>n!=i(e),zoom:(e,t)=>((e,t)=>{if(n==e)return!1;const i=1-n/e;return o(-t.x*i,-t.y*i),n=e,!0})(i(e),t),worldToViewport:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:a,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=l(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:l}}function en(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var tn={createCanvas:en,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=en(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=en(e.width,e.height),i=o.getContext("2d");return i.save(),i.drawImage(t,0,0),i.fillStyle=n,i.globalCompositeOperation="source-in",i.fillRect(0,0,t.width,t.height),i.restore(),i.save(),i.globalCompositeOperation="destination-over",i.drawImage(e,0,0),i.restore(),o}};const nn=De("Debug.js");let on=0,ln=0;var an=e=>{on=performance.now(),ln=e},sn=e=>{const t=performance.now(),n=t-on;n>ln&&nn.log(e+": "+n),on=t};function rn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function cn(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var dn={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:rn,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:cn,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 rn(cn(e),cn(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)}};const un=De("PuzzleGraphics.js");function gn(e,t){const n=Be.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var pn={loadPuzzleBitmaps:async function(e){const t=await tn.loadImageToBitmap(e.info.imageUrl),n=await tn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){un.log("start createPuzzleTileBitmaps");const o=n.tileSize,i=n.tileMarginWidth,l=n.tileDrawSize,a=o/100,s=[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],r=new Array(t.length),c={};function d(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(c[t])return c[t];const n=new Path2D,l={x:i,y:i},r=dn.pointAdd(l,{x:o,y:0}),d=dn.pointAdd(r,{x:0,y:o}),u=dn.pointSub(d,{x:o,y:0});if(n.moveTo(l.x,l.y),0!==e.top)for(let o=0;oBe.decodePiece(hn[e].puzzle.tiles[t]),In=(e,t)=>Pn(e,t).group,Tn=(e,t)=>{const n=hn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},i=function(e,t){const n=hn[e].puzzle.info,o=Be.coordByPieceIdx(n,t),i=o.x*n.tileSize,l=o.y*n.tileSize;return{x:i,y:l}}(e,t);return dn.pointAdd(o,i)},_n=(e,t)=>Pn(e,t).pos,Dn=e=>{const t=qn(e),n=Hn(e),o=Math.round(t/4),i=Math.round(n/4);return{x:0-o,y:0-i,w:t+2*o,h:n+2*i}},Bn=(e,t)=>{const n=Un(e),o=Pn(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},En=(e,t)=>Pn(e,t).z,On=(e,t)=>{for(const n of hn[e].puzzle.tiles){const e=Be.decodePiece(n);if(e.owner===t)return e.idx}return-1},Nn=e=>hn[e].puzzle.info.tileDrawSize,Un=e=>hn[e].puzzle.info.tileSize,Mn=e=>hn[e].puzzle.data.maxGroup,Gn=e=>hn[e].puzzle.data.maxZ;function $n(e,t){const n=hn[e].puzzle.info,o=Be.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Rn=(e,t,n)=>{for(const o of t)Sn(e,o,{z:n})},Vn=(e,t,n)=>{const o=_n(e,t);Sn(e,t,{pos:dn.pointAdd(o,n)})},jn=(e,t,n)=>{const o=Nn(e),i=Dn(e),l=n;for(const a of t){const t=Pn(e,a);t.pos.x+n.xi.x+i.w&&(l.x=Math.min(i.x+i.w-t.pos.x+o,l.x)),t.pos.y+n.yi.y+i.h&&(l.y=Math.min(i.y+i.h-t.pos.y+o,l.y))}for(const a of t)Vn(e,a,l)},Fn=(e,t,n)=>{for(const o of t)Sn(e,o,{owner:n})};function Ln(e,t){const n=hn[e].puzzle.tiles,o=Be.decodePiece(n[t]),i=[];if(o.group)for(const l of n){const e=Be.decodePiece(l);e.group===o.group&&i.push(e.idx)}else i.push(o.idx);return i}const Wn=(e,t)=>{const n=yn(e,t);return n?n.points:0},qn=e=>hn[e].puzzle.info.table.width,Hn=e=>hn[e].puzzle.info.table.height;var Qn={setGame:function(e,t){hn[e]=t},exists:function(e){return!!hn[e]||!1},playerExists:wn,getActivePlayers:function(e,t){const n=t-30*_;return vn(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return vn(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){wn(e,t)?An(e,t,{ts:n}):fn(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:kn,getPieceCount:bn,getImageUrl:function(e){return hn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){hn[e].puzzle.info.imageUrl=t},get:function(e){return hn[e]||null},getAllGames:function(){return Object.values(hn).sort(((e,t)=>Cn(e.id)===Cn(t.id)?t.puzzle.data.started-e.puzzle.data.started:Cn(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=yn(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=yn(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=yn(e,t);return n?n.name:null},getPlayerIndexById:mn,getPlayerIdByIndex:function(e,t){return hn[e].players.length>t?Be.decodePlayer(hn[e].players[t]).id:null},changePlayer:An,setPlayer:fn,setPiece:function(e,t,n){hn[e].puzzle.tiles[t]=Be.encodePiece(n)},setPuzzleData:function(e,t){hn[e].puzzle.data=t},getTableWidth:qn,getTableHeight:Hn,getPuzzle:e=>hn[e].puzzle,getRng:e=>hn[e].rng.obj,getPuzzleWidth:e=>hn[e].puzzle.info.width,getPuzzleHeight:e=>hn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return hn[e].puzzle.tiles.map(Be.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=On(e,t);return n<0?null:hn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>hn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:Nn,getFinalPiecePos:Tn,getStartTs:e=>hn[e].puzzle.data.started,getFinishTs:e=>hn[e].puzzle.data.finished,handleInput:function(e,t,n,o){const i=hn[e].puzzle,l=function(e,t){return t in hn[e].evtInfos?hn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),a=[],s=()=>{a.push([vt,i.data])},r=t=>{a.push([bt,Be.encodePiece(Pn(e,t))])},c=e=>{for(const t of e)r(t)},d=()=>{const n=yn(e,t);n&&a.push([xt,Be.encodePlayer(n)])},u=n[0];if(u===mt){const i=n[1];An(e,t,{bgcolor:i,ts:o}),d()}else if(u===yt){const i=n[1];An(e,t,{color:i,ts:o}),d()}else if(u===ft){const i=`${n[1]}`.substr(0,16);An(e,t,{name:i,ts:o}),d()}else if(u===dt){const i={x:n[1],y:n[2]};An(e,t,{d:1,ts:o}),d(),l._last_mouse_down=i;const a=((e,t)=>{const n=hn[e].puzzle.info,o=hn[e].puzzle.tiles;let i=-1,l=-1;for(let a=0;ai)&&(i=e.z,l=a)}return l})(e,i);if(a>=0){const n=Gn(e)+1;zn(e,{maxZ:n}),s();const o=Ln(e,a);Rn(e,o,Gn(e)),Fn(e,o,t),c(o)}l._last_mouse=i}else if(u===gt){const i=n[1],a=n[2],s={x:i,y:a};if(null===l._last_mouse_down)An(e,t,{x:i,y:a,ts:o}),d();else{const n=On(e,t);if(n>=0){An(e,t,{x:i,y:a,ts:o}),d();const r=Ln(e,n);let u=dn.pointInBounds(s,Dn(e))&&dn.pointInBounds(l._last_mouse_down,Dn(e));for(const t of r){const n=Bn(e,t);if(dn.pointInBounds(s,n)){u=!0;break}}if(u){const t=i-l._last_mouse_down.x,n=a-l._last_mouse_down.y;jn(e,r,{x:t,y:n}),c(r)}}else An(e,t,{ts:o}),d();l._last_mouse_down=s}l._last_mouse=s}else if(u===ut){const a={x:n[1],y:n[2]},u=0;l._last_mouse_down=null;const g=On(e,t);if(g>=0){const n=Ln(e,g);Fn(e,n,0),c(n);const l=_n(e,g),a=Tn(e,g);if(dn.pointDistance(a,l){for(const n of t)Sn(e,n,{owner:-1,z:1})})(e,n),c(n);let r=Wn(e,t);xn(e)===fe.FINAL?r+=n.length:xn(e)===fe.ANY&&(r+=1),An(e,t,{d:u,ts:o,points:r}),d(),kn(e)===bn(e)&&(zn(e,{finished:o}),s())}else{const n=(e,t,n,o)=>{const i=hn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=In(e,t),i=In(e,n);return!(!o||o!==i)})(e,t,n))return!1;const l=_n(e,t),a=dn.pointAdd(_n(e,n),{x:o[0]*i.tileSize,y:o[1]*i.tileSize});if(dn.pointDistance(l,a){const o=hn[e].puzzle.tiles,i=In(e,t),l=In(e,n);let a;const c=[];i&&c.push(i),l&&c.push(l),i?a=i:l?a=l:(zn(e,{maxGroup:Mn(e)+1}),s(),a=Mn(e));if(Sn(e,t,{group:a}),r(t),Sn(e,n,{group:a}),r(n),c.length>0)for(const s of o){const t=Be.decodePiece(s);c.includes(t.group)&&(Sn(e,t.idx,{group:a}),r(t.idx))}})(e,t,n),i=Ln(e,t);const d=((e,t)=>{let n=0;for(const o of t){const t=En(e,o);t>n&&(n=t)}return n})(e,i);return Rn(e,i,d),c(i),!0}return!1};let i=!1;for(const t of Ln(e,g)){const o=$n(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])){i=!0;break}}if(i&&xn(e)===fe.ANY){const n=Wn(e,t)+1;An(e,t,{d:u,ts:o,points:n}),d()}else An(e,t,{d:u,ts:o}),d()}}else An(e,t,{d:u,ts:o}),d();l._last_mouse=a}else if(u===pt){const i=n[1],a=n[2];An(e,t,{x:i,y:a,ts:o}),d(),l._last_mouse={x:i,y:a}}else if(u===ht){const i=n[1],a=n[2];An(e,t,{x:i,y:a,ts:o}),d(),l._last_mouse={x:i,y:a}}else An(e,t,{ts:o}),d();return function(e,t,n){hn[e].evtInfos[t]=n}(e,t,l),a}};let Yn=-10,Kn=20,Zn=2,Jn=15;class Xn{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Yn+Math.random()*Kn,this.vy=-1*(Zn+Math.random()*Jn),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=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;Zn=t/2,Jn=t-Zn;const n=1/4*this.canvas.width/(t/2);Yn=-n,Kn=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Xn(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Xn(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(!h[t]){const n=e.d?a:s;if(e.color){const o=e.d?r:c;h[t]=await createImageBitmap(tn.colorizedCanvas(n,o,e.color))}else h[t]=n}return h[t]},y=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,lo=!0})),t}(i,tn.createCanvas()),f={final:!1,requesting:!0,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,dataOffset:0,dataSize:1e4};Dt.onConnectionStateChange((e=>{l.setConnectionState(e)}));const w=async e=>{f.requesting=!0;const t=await Dt.requestReplayData(e,f.dataOffset,f.dataSize);return f.dataOffset+=f.dataSize,f.requesting=!1,t};let v=()=>0;const b=async()=>{if("play"===o){const o=await Dt.connect(n,e,t),i=Be.decodeGame(o);Qn.setGame(i.id,i),v=()=>D()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await w(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=Be.decodeGame(t.game);Qn.setGame(n.id,n),f.requesting=!1,f.log=t.log,f.lastRealTs=D(),f.gameStartTs=parseInt(f.log[0][4],10),f.lastGameTs=f.gameStartTs,v=()=>f.lastGameTs}}lo=!0};await b();const x=Qn.getPieceDrawOffset(e),C=Qn.getPieceDrawSize(e),k=Qn.getPuzzleWidth(e),A=Qn.getPuzzleHeight(e),z=Qn.getTableWidth(e),S=Qn.getTableHeight(e),P={x:(z-k)/2,y:(S-A)/2},I={w:k,h:A},T={w:C,h:C},_=await pn.loadPuzzleBitmaps(Qn.getPuzzle(e)),B=new to(y,Qn.getRng(e));B.init();const E=y.getContext("2d");y.classList.add("loaded");const O=Xt();O.move(-(z-y.width)/2,-(S-y.height)/2);const N=function(e,t,n){let o=[],i=!0,l=!1,a=!1,s=!1,r=!1,c=!1,d=!1,u=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{i&&("Shift"===t.key?u=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?s=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?r=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?l=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?a=e:"q"===t.key?d=e:"e"===t.key&&(c=e))};e.addEventListener("mousedown",(e=>{0===e.button&&y([dt,...p(e)])})),e.addEventListener("mouseup",(e=>{0===e.button&&y([ut,...p(e)])})),e.addEventListener("mousemove",(e=>{y([gt,...p(e)])})),e.addEventListener("wheel",(e=>{if(n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?pt:ht;y([t,...p(e)])}})),t.addEventListener("keydown",(e=>m(!0,e))),t.addEventListener("keyup",(e=>m(!1,e))),t.addEventListener("keypress",(e=>{i&&(" "===e.key&&y([wt]),"F"!==e.key&&"f"!==e.key||(oo=!oo,lo=!0),"G"!==e.key&&"g"!==e.key||(io=!io,lo=!0))}));const y=e=>{o.push(e)};return{addEvent:y,consumeAll:()=>{if(0===o.length)return[];const e=o.slice();return o=[],e},createKeyEvents:()=>{const e=u?20:10,t=(l?e:0)-(a?e:0),o=(s?e:0)-(r?e:0);0===t&&0===o||y([ct,t,o]),c&&d||(c?n.canZoom("in")&&y([pt,...h()]):d&&n.canZoom("out")&&y([ht,...h()]))},setHotkeys:e=>{i=e}}}(y,window,O),U=Qn.getImageUrl(e),M=()=>{const t=Qn.getStartTs(e),n=Qn.getFinishTs(e),o=v();l.setFinished(!!n),l.setDuration((n||o)-t)};M(),l.setPiecesDone(Qn.getFinishedPiecesCount(e)),l.setPiecesTotal(Qn.getPieceCount(e));const G=v();l.setActivePlayers(Qn.getActivePlayers(e,G)),l.setIdlePlayers(Qn.getIdlePlayers(e,G));const $=!!Qn.getFinishTs(e);let R=$;const V=()=>R&&!$,j=()=>Qn.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",F=()=>Qn.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let L="",W="",q=!1;const H=e=>{q=e;const[t,n]=e?[L,"grab"]:[W,"default"];y.style.cursor=`url('${t}') ${u} ${p}, ${n}`},Q=e=>{L=tn.colorizedCanvas(a,r,e).toDataURL(),W=tn.colorizedCanvas(s,c,e).toDataURL(),H(q)};Q(F());const Y=()=>{l.setReplaySpeed&&l.setReplaySpeed(f.speeds[f.speedIdx]),l.setReplayPaused&&l.setReplayPaused(f.paused)};if("play"===o?setInterval(M,1e3):"replay"===o&&Y(),"play"===o)Dt.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[i,l]of o)switch(i){case xt:{const n=Be.decodePlayer(l);n.id!==t&&(Qn.setPlayer(e,n.id,n),lo=!0)}break;case bt:{const t=Be.decodePiece(l);Qn.setPiece(e,t.idx,t),lo=!0}break;case vt:Qn.setPuzzleData(e,l),lo=!0}R=!!Qn.getFinishTs(e)}));else if("replay"===o){const t=setInterval((()=>{const n=D();if(f.requesting)return void(f.lastRealTs=n);if(f.logPointer+1>=f.log.length)return f.lastRealTs=n,void(async e=>{const t=await w(e);f.log=f.log.slice(f.logPointer),f.logPointer=0,f.log.push(...t.log),t.log.length=f.log.length){f.final&&clearInterval(t);break}const o=f.log[n],l=f.gameStartTs+o[o.length-1];if(l>i)break;const a=o.slice();if(a[0]===at){const t=a[1];Qn.addPlayer(e,t,l),lo=!0}else if(a[0]===st){const t=Qn.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";Qn.addPlayer(e,t,l),lo=!0}else if(a[0]===rt){const t=Qn.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=a[2];Qn.handleInput(e,t,n,l),lo=!0}f.logPointer=n}f.lastRealTs=n,f.lastGameTs=i,M()}),50)}let K=null;return(e=>{const t=e.fps||60,n=e.slow||1,o=e.update,i=e.render,l=window.requestAnimationFrame,a=1/t,s=n*a;let r,c=0,d=window.performance.now();const u=()=>{for(r=window.performance.now(),c+=Math.min(1,(r-d)/1e3);c>s;)c-=s,o(a);i(c/n),d=r,l(u)};l(u)})({update:()=>{N.createKeyEvents();for(const n of N.consumeAll())if("play"===o){const o=n[0];if(o===ct){const e=n[1],t=n[2];lo=!0,O.move(e,t)}else if(o===gt){if(K&&!Qn.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=O.worldToViewport(e),o=Math.round(t.x-K.x),i=Math.round(t.y-K.y);lo=!0,O.move(o,i),K=t}}else if(o===yt)Q(n[1]);else if(o===dt){const e={x:n[1],y:n[2]};K=O.worldToViewport(e),H(!0)}else if(o===ut)K=null,H(!1);else if(o===pt){const e={x:n[1],y:n[2]};lo=!0,O.zoom("in",O.worldToViewport(e))}else if(o===ht){const e={x:n[1],y:n[2]};lo=!0,O.zoom("out",O.worldToViewport(e))}else o===wt&&l.togglePreview();const i=v();Qn.handleInput(e,t,n,i).length>0&&(lo=!0),Dt.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===ct){const e=n[1],t=n[2];lo=!0,O.move(e,t)}else if(e===gt){if(K){const e={x:n[1],y:n[2]},t=O.worldToViewport(e),o=Math.round(t.x-K.x),i=Math.round(t.y-K.y);lo=!0,O.move(o,i),K=t}}else if(e===dt){const e={x:n[1],y:n[2]};K=O.worldToViewport(e)}else if(e===ut)K=null;else if(e===pt){const e={x:n[1],y:n[2]};lo=!0,O.zoom("in",O.worldToViewport(e))}else if(e===ht){const e={x:n[1],y:n[2]};lo=!0,O.zoom("out",O.worldToViewport(e))}else e===wt&&l.togglePreview()}R=!!Qn.getFinishTs(e),V()&&(B.update(),lo=!0)},render:async()=>{if(!lo)return;const n=v();let i,a,s;window.DEBUG&&an(0),E.fillStyle=j(),E.fillRect(0,0,y.width,y.height),window.DEBUG&&sn("clear done"),i=O.worldToViewportRaw(P),a=O.worldDimToViewportRaw(I),E.fillStyle="rgba(255, 255, 255, .3)",E.fillRect(i.x,i.y,a.w,a.h),window.DEBUG&&sn("board done");const r=Qn.getPiecesSortedByZIndex(e);window.DEBUG&&sn("get tiles done"),a=O.worldDimToViewportRaw(T);for(const e of r)(-1===e.owner?oo:io)&&(s=_[e.idx],i=O.worldToViewportRaw({x:x+e.pos.x,y:x+e.pos.y}),E.drawImage(s,0,0,s.width,s.height,i.x,i.y,a.w,a.h));window.DEBUG&&sn("tiles done");const c=[];for(const l of Qn.getActivePlayers(e,n))d=l,("replay"===o||d.id!==t)&&(s=await m(l),i=O.worldToViewport(l),E.drawImage(s,i.x-u,i.y-p),c.push([`${l.name} (${l.points})`,i.x,i.y+g]));var d;E.fillStyle="white",E.textAlign="center";for(const[e,t,o]of c)E.fillText(e,t,o);window.DEBUG&&sn("players done"),l.setActivePlayers(Qn.getActivePlayers(e,n)),l.setIdlePlayers(Qn.getIdlePlayers(e,n)),l.setPiecesDone(Qn.getFinishedPiecesCount(e)),window.DEBUG&&sn("HUD done"),V()&&B.render(),lo=!1}}),{setHotkeys:e=>{N.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),N.addEvent([mt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),N.addEvent([yt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),N.addEvent([ft,e])},replayOnSpeedUp:()=>{f.speedIdx+1{f.speedIdx>=1&&(f.speedIdx--,Y())},replayOnPauseToggle:()=>{f.paused=!f.paused,Y()},previewImageUrl:U,player:{background:j(),color:F(),name:Qn.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon"},disconnect:Dt.disconnect,connect:b}}var so=e({name:"game",components:{PuzzleStatus:Qe,Scores:Fe,SettingsOverlay:Ke,PreviewOverlay:et,ConnectionOverlay:Bt,HelpOverlay:Gt},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 ao(`${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 ro={id:"game"},co={class:"menu"},uo={class:"tabs"},go=s("🧩 Puzzles");so.render=function(e,i,s,r,c,d){const u=l("settings-overlay"),p=l("preview-overlay"),h=l("help-overlay"),m=l("connection-overlay"),y=l("puzzle-status"),f=l("router-link"),w=l("scores");return a(),t("div",ro,[g(n(u,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(p,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(h,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",co,[n("div",uo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[go])),_:1}),n("div",{class:"opener",onClick:i[5]||(i[5]=t=>e.toggle("preview",!1))},"πŸ–ΌοΈ Preview"),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("settings",!0))},"πŸ› οΈ Settings"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var po=e({name:"replay",components:{PuzzleStatus:Qe,Scores:Fe,SettingsOverlay:Ke,PreviewOverlay:et,HelpOverlay:Gt},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 ao(`${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 ho={id:"replay"},mo={class:"menu"},yo={class:"tabs"},fo=s("🧩 Puzzles");po.render=function(e,i,s,c,d,u){const p=l("settings-overlay"),h=l("preview-overlay"),m=l("help-overlay"),y=l("puzzle-status"),f=l("router-link"),w=l("scores");return a(),t("div",ho,[g(n(p,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(m,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(y,{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:i[5]||(i[5]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",mo,[n("div",yo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[fo])),_:1}),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("preview",!1))},"πŸ–ΌοΈ Preview"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("settings",!0))},"πŸ› οΈ Settings"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=b({history:x(),routes:[{name:"index",path:"/",component:R},{name:"new-game",path:"/new-game",component:Ee},{name:"game",path:"/g/:id",component:so},{name:"replay",path:"/replay/:id",component:po}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=C(k);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=Be.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); diff --git a/build/public/index.html b/build/public/index.html index 9d4f95b..a9cd071 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,7 +4,7 @@ 🧩 jigsaw.hyottoko.club - + diff --git a/build/server/main.js b/build/server/main.js index 7438e73..fd0a8d0 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -10,193 +10,9 @@ 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'; import bsqlite from 'better-sqlite3'; -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, -}; - -/* -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_CLIENT_EVENT = 2; -const EV_CLIENT_INIT = 3; -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_CLIENT_EVENT, - EV_CLIENT_INIT, - 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, -}; - -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, -}; - var PieceEdge; (function (PieceEdge) { PieceEdge[PieceEdge["Flat"] = 0] = "Flat"; @@ -207,706 +23,7 @@ var ScoreMode; (function (ScoreMode) { ScoreMode[ScoreMode["FINAL"] = 0] = "FINAL"; ScoreMode[ScoreMode["ANY"] = 1] = "ANY"; -})(ScoreMode || (ScoreMode = {})); -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) { - const idx = getPlayerIndexById(gameId, playerId); - if (idx === -1) { - return null; - } - return Util.decodePlayer(GAMES[gameId].players[idx]); -} -function setPlayer(gameId, playerId, player) { - const idx = getPlayerIndexById(gameId, playerId); - if (idx === -1) { - GAMES[gameId].players.push(Util.encodePlayer(player)); - } - else { - GAMES[gameId].players[idx] = Util.encodePlayer(player); - } -} -function setPiece(gameId, pieceIdx, piece) { - GAMES[gameId].puzzle.tiles[pieceIdx] = Util.encodePiece(piece); -} -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 getPieceCount(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 || ScoreMode.FINAL; -} -function isFinished(gameId) { - return getFinishedPiecesCount(gameId) === getPieceCount(gameId); -} -function getFinishedPiecesCount(gameId) { - let count = 0; - for (let t of GAMES[gameId].puzzle.tiles) { - if (Util.decodePiece(t).owner === -1) { - count++; - } - } - return count; -} -function getPiecesSortedByZIndex(gameId) { - const pieces = GAMES[gameId].puzzle.tiles.map(Util.decodePiece); - return pieces.sort((t1, t2) => t1.z - t2.z); -} -function changePlayer(gameId, playerId, change) { - const player = getPlayer(gameId, playerId); - if (player === null) { - return; - } - for (let k of Object.keys(change)) { - // @ts-ignore - 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, pieceIdx, change) { - for (let k of Object.keys(change)) { - const piece = Util.decodePiece(GAMES[gameId].puzzle.tiles[pieceIdx]); - // @ts-ignore - piece[k] = change[k]; - GAMES[gameId].puzzle.tiles[pieceIdx] = Util.encodePiece(piece); - } -} -const getPiece = (gameId, pieceIdx) => { - return Util.decodePiece(GAMES[gameId].puzzle.tiles[pieceIdx]); -}; -const getPieceGroup = (gameId, tileIdx) => { - const tile = getPiece(gameId, tileIdx); - return tile.group; -}; -const getFinalPiecePos = (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 getPiecePos = (gameId, tileIdx) => { - const tile = getPiece(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 getPieceBounds = (gameId, tileIdx) => { - const s = getPieceSize(gameId); - const tile = getPiece(gameId, tileIdx); - return { - x: tile.pos.x, - y: tile.pos.y, - w: s, - h: s, - }; -}; -const getTileZIndex = (gameId, tileIdx) => { - const tile = getPiece(gameId, tileIdx); - return tile.z; -}; -const getFirstOwnedPieceIdx = (gameId, playerId) => { - for (let t of GAMES[gameId].puzzle.tiles) { - const tile = Util.decodePiece(t); - if (tile.owner === playerId) { - return tile.idx; - } - } - return -1; -}; -const getFirstOwnedPiece = (gameId, playerId) => { - const idx = getFirstOwnedPieceIdx(gameId, playerId); - return idx < 0 ? null : GAMES[gameId].puzzle.tiles[idx]; -}; -const getPieceDrawOffset = (gameId) => { - return GAMES[gameId].puzzle.info.tileDrawOffset; -}; -const getPieceDrawSize = (gameId) => { - return GAMES[gameId].puzzle.info.tileDrawSize; -}; -const getPieceSize = (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 = getPiecePos(gameId, tileIdx); - const pos = Geometry.pointAdd(oldPos, diff); - changeTile(gameId, tileIdx, { pos }); -}; -const moveTilesDiff = (gameId, tileIdxs, diff) => { - const drawSize = getPieceDrawSize(gameId); - const bounds = getBounds(gameId); - const cappedDiff = diff; - for (let tileIdx of tileIdxs) { - const t = getPiece(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 + drawSize + diff.x > bounds.x + bounds.w) { - cappedDiff.x = Math.min(bounds.x + bounds.w - t.pos.x + drawSize, 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 + drawSize + diff.y > bounds.y + bounds.h) { - cappedDiff.y = Math.min(bounds.y + bounds.h - t.pos.y + drawSize, 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 getGroupedPieceIdxs(gameId, pieceIdx) { - const pieces = GAMES[gameId].puzzle.tiles; - const piece = Util.decodePiece(pieces[pieceIdx]); - const grouped = []; - if (piece.group) { - for (let other of pieces) { - const otherPiece = Util.decodePiece(other); - if (otherPiece.group === piece.group) { - grouped.push(otherPiece.idx); - } - } - } - else { - grouped.push(piece.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 freePieceIdxByPos = (gameId, pos) => { - let info = GAMES[gameId].puzzle.info; - let pieces = GAMES[gameId].puzzle.tiles; - let maxZ = -1; - let pieceIdx = -1; - for (let idx = 0; idx < pieces.length; idx++) { - const piece = Util.decodePiece(pieces[idx]); - if (piece.owner !== 0) { - continue; - } - const collisionRect = { - x: piece.pos.x, - y: piece.pos.y, - w: info.tileSize, - h: info.tileSize, - }; - if (Geometry.pointInBounds(pos, collisionRect)) { - if (maxZ === -1 || piece.z > maxZ) { - maxZ = piece.z; - pieceIdx = idx; - } - } - } - return pieceIdx; -}; -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 : 0; -}; -// determine if two tiles are grouped together -const areGrouped = (gameId, tileIdx1, tileIdx2) => { - const g1 = getPieceGroup(gameId, tileIdx1); - const g2 = getPieceGroup(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.encodePiece(getPiece(gameId, tileIdx)), - ]); - }; - const _tileChanges = (tileIdxs) => { - for (const tileIdx of tileIdxs) { - _tileChange(tileIdx); - } - }; - const _playerChange = () => { - const player = getPlayer(gameId, playerId); - if (!player) { - return; - } - changes.push([ - Protocol.CHANGE_PLAYER, - Util.encodePlayer(player), - ]); - }; - // put both tiles (and their grouped tiles) in the same group - const groupTiles = (gameId, tileIdx1, tileIdx2) => { - const tiles = GAMES[gameId].puzzle.tiles; - const group1 = getPieceGroup(gameId, tileIdx1); - const group2 = getPieceGroup(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 piece = Util.decodePiece(t); - if (searchGroups.includes(piece.group)) { - changeTile(gameId, piece.idx, { group }); - _tileChange(piece.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 = freePieceIdxByPos(gameId, pos); - if (tileIdxAtPos >= 0) { - let maxZ = getMaxZIndex(gameId) + 1; - changeData(gameId, { maxZ }); - _dataChange(); - const tileIdxs = getGroupedPieceIdxs(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 = getFirstOwnedPieceIdx(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 = getGroupedPieceIdxs(gameId, tileIdx); - let anyOk = Geometry.pointInBounds(pos, getBounds(gameId)) - && Geometry.pointInBounds(evtInfo._last_mouse_down, getBounds(gameId)); - for (let idx of tileIdxs) { - const bounds = getPieceBounds(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 = getFirstOwnedPieceIdx(gameId, playerId); - if (tileIdx >= 0) { - // drop the tile(s) - let tileIdxs = getGroupedPieceIdxs(gameId, tileIdx); - setTilesOwner(gameId, tileIdxs, 0); - _tileChanges(tileIdxs); - // Check if the tile was dropped near the final location - let tilePos = getPiecePos(gameId, tileIdx); - let finalPos = getFinalPiecePos(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) === ScoreMode.FINAL) { - points += tileIdxs.length; - } - else if (getScoreMode(gameId) === ScoreMode.ANY) { - points += 1; - } - else ; - changePlayer(gameId, playerId, { d, ts, points }); - _playerChange(); - // check if the puzzle is finished - if (getFinishedPiecesCount(gameId) === getPieceCount(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 = getPiecePos(gameId, tileIdx); - const dstPos = Geometry.pointAdd(getPiecePos(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 = getGroupedPieceIdxs(gameId, tileIdx); - moveTilesDiff(gameId, tileIdxs, diff); - groupTiles(gameId, tileIdx, otherTileIdx); - tileIdxs = getGroupedPieceIdxs(gameId, tileIdx); - const zIndex = getMaxZIndexByTileIdxs(gameId, tileIdxs); - setTilesZIndex(gameId, tileIdxs, zIndex); - _tileChanges(tileIdxs); - return true; - } - return false; - }; - let snapped = false; - for (let tileIdxTmp of getGroupedPieceIdxs(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) === ScoreMode.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 = { - setGame, - exists: exists$1, - playerExists, - getActivePlayers, - getIdlePlayers, - addPlayer: addPlayer$1, - getFinishedPiecesCount, - getPieceCount, - getImageUrl, - setImageUrl, - get: get$1, - getAllGames, - getPlayerBgColor, - getPlayerColor, - getPlayerName, - getPlayerIndexById, - getPlayerIdByIndex, - changePlayer, - setPlayer, - setPiece, - setPuzzleData, - getTableWidth, - getTableHeight, - getPuzzle, - getRng, - getPuzzleWidth, - getPuzzleHeight, - getPiecesSortedByZIndex, - getFirstOwnedPiece, - getPieceDrawOffset, - getPieceDrawSize, - getFinalPiecePos, - getStartTs, - getFinishTs, - handleInput: handleInput$1, -}; +})(ScoreMode || (ScoreMode = {})); class Rng { constructor(seed) { @@ -916,7 +33,7 @@ class Rng { 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; + const n = (this.rand_high >>> 0) / 0xffffffff; return (min + n * (max - min + 1)) | 0; } // get one random item from the given array @@ -976,7 +93,9 @@ const logger = (...pre) => { }; }; // get a unique id -const uniqId = () => Date.now().toString(36) + Math.random().toString(36).substring(2); +const uniqId = () => { + return Date.now().toString(36) + Math.random().toString(36).substring(2); +}; function encodeShape(data) { /* encoded in 1 byte: 00000000 @@ -1063,17 +182,17 @@ function decodeGame(data) { scoreMode: data[6], }; } -function coordByTileIdx(info, tileIdx) { +function coordByPieceIdx(info, pieceIdx) { const wTiles = info.width / info.tileSize; return { - x: tileIdx % wTiles, - y: Math.floor(tileIdx / wTiles), + x: pieceIdx % wTiles, + y: Math.floor(pieceIdx / wTiles), }; } const hash = (str) => { let hash = 0; for (let i = 0; i < str.length; i++) { - let chr = str.charCodeAt(i); + const chr = str.charCodeAt(i); hash = ((hash << 5) - hash) + chr; hash |= 0; // Convert to 32bit integer } @@ -1081,7 +200,7 @@ const hash = (str) => { }; function asQueryArgs(data) { const q = []; - for (let k in data) { + for (const k in data) { const pair = [k, data[k]].map(encodeURIComponent); q.push(pair.join('=')); } @@ -1102,11 +221,11 @@ var Util = { decodePlayer, encodeGame, decodeGame, - coordByTileIdx, + coordByPieceIdx, asQueryArgs, }; -const log$4 = logger('WebSocketServer.js'); +const log$5 = logger('WebSocketServer.js'); /* Example config @@ -1144,12 +263,12 @@ class WebSocketServer { 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); + log$5.log('bad request url: ', request.url); socket.close(); return; } socket.on('message', (data) => { - log$4.log(`ws`, socket.protocol, data); + log$5.log(`ws`, socket.protocol, data); this.evt.dispatch('message', { socket, data }); }); socket.on('close', () => { @@ -1167,6 +286,888 @@ class WebSocketServer { } } +/* +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_CLIENT_EVENT = 2; +const EV_CLIENT_INIT = 3; +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_CLIENT_EVENT, + EV_CLIENT_INIT, + 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 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 (const 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) { + const idx = getPlayerIndexById(gameId, playerId); + if (idx === -1) { + return null; + } + return Util.decodePlayer(GAMES[gameId].players[idx]); +} +function setPlayer(gameId, playerId, player) { + const idx = getPlayerIndexById(gameId, playerId); + if (idx === -1) { + GAMES[gameId].players.push(Util.encodePlayer(player)); + } + else { + GAMES[gameId].players[idx] = Util.encodePlayer(player); + } +} +function setPiece(gameId, pieceIdx, piece) { + GAMES[gameId].puzzle.tiles[pieceIdx] = Util.encodePiece(piece); +} +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] || null; +} +function getPieceCount(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 || ScoreMode.FINAL; +} +function isFinished(gameId) { + return getFinishedPiecesCount(gameId) === getPieceCount(gameId); +} +function getFinishedPiecesCount(gameId) { + let count = 0; + for (const t of GAMES[gameId].puzzle.tiles) { + if (Util.decodePiece(t).owner === -1) { + count++; + } + } + return count; +} +function getPiecesSortedByZIndex(gameId) { + const pieces = GAMES[gameId].puzzle.tiles.map(Util.decodePiece); + return pieces.sort((t1, t2) => t1.z - t2.z); +} +function changePlayer(gameId, playerId, change) { + const player = getPlayer(gameId, playerId); + if (player === null) { + return; + } + for (const k of Object.keys(change)) { + // @ts-ignore + player[k] = change[k]; + } + setPlayer(gameId, playerId, player); +} +function changeData(gameId, change) { + for (const k of Object.keys(change)) { + // @ts-ignore + GAMES[gameId].puzzle.data[k] = change[k]; + } +} +function changePiece(gameId, pieceIdx, change) { + for (const k of Object.keys(change)) { + const piece = Util.decodePiece(GAMES[gameId].puzzle.tiles[pieceIdx]); + // @ts-ignore + piece[k] = change[k]; + GAMES[gameId].puzzle.tiles[pieceIdx] = Util.encodePiece(piece); + } +} +const getPiece = (gameId, pieceIdx) => { + return Util.decodePiece(GAMES[gameId].puzzle.tiles[pieceIdx]); +}; +const getPieceGroup = (gameId, tileIdx) => { + const tile = getPiece(gameId, tileIdx); + return tile.group; +}; +const getFinalPiecePos = (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 getPiecePos = (gameId, tileIdx) => { + const tile = getPiece(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 getPieceBounds = (gameId, tileIdx) => { + const s = getPieceSize(gameId); + const tile = getPiece(gameId, tileIdx); + return { + x: tile.pos.x, + y: tile.pos.y, + w: s, + h: s, + }; +}; +const getPieceZIndex = (gameId, pieceIdx) => { + return getPiece(gameId, pieceIdx).z; +}; +const getFirstOwnedPieceIdx = (gameId, playerId) => { + for (const t of GAMES[gameId].puzzle.tiles) { + const tile = Util.decodePiece(t); + if (tile.owner === playerId) { + return tile.idx; + } + } + return -1; +}; +const getFirstOwnedPiece = (gameId, playerId) => { + const idx = getFirstOwnedPieceIdx(gameId, playerId); + return idx < 0 ? null : GAMES[gameId].puzzle.tiles[idx]; +}; +const getPieceDrawOffset = (gameId) => { + return GAMES[gameId].puzzle.info.tileDrawOffset; +}; +const getPieceDrawSize = (gameId) => { + return GAMES[gameId].puzzle.info.tileDrawSize; +}; +const getPieceSize = (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 getMaxZIndexByPieceIdxs = (gameId, pieceIdxs) => { + let maxZ = 0; + for (const pieceIdx of pieceIdxs) { + const curZ = getPieceZIndex(gameId, pieceIdx); + if (curZ > maxZ) { + maxZ = curZ; + } + } + return maxZ; +}; +function srcPosByTileIdx(gameId, tileIdx) { + const info = GAMES[gameId].puzzle.info; + const c = Util.coordByPieceIdx(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.coordByPieceIdx(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 setPiecesZIndex = (gameId, tileIdxs, zIndex) => { + for (const tilesIdx of tileIdxs) { + changePiece(gameId, tilesIdx, { z: zIndex }); + } +}; +const moveTileDiff = (gameId, tileIdx, diff) => { + const oldPos = getPiecePos(gameId, tileIdx); + const pos = Geometry.pointAdd(oldPos, diff); + changePiece(gameId, tileIdx, { pos }); +}; +const movePiecesDiff = (gameId, pieceIdxs, diff) => { + const drawSize = getPieceDrawSize(gameId); + const bounds = getBounds(gameId); + const cappedDiff = diff; + for (const pieceIdx of pieceIdxs) { + const t = getPiece(gameId, pieceIdx); + if (t.pos.x + diff.x < bounds.x) { + cappedDiff.x = Math.max(bounds.x - t.pos.x, cappedDiff.x); + } + else if (t.pos.x + drawSize + diff.x > bounds.x + bounds.w) { + cappedDiff.x = Math.min(bounds.x + bounds.w - t.pos.x + drawSize, 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 + drawSize + diff.y > bounds.y + bounds.h) { + cappedDiff.y = Math.min(bounds.y + bounds.h - t.pos.y + drawSize, cappedDiff.y); + } + } + for (const pieceIdx of pieceIdxs) { + moveTileDiff(gameId, pieceIdx, cappedDiff); + } +}; +const finishPieces = (gameId, pieceIdxs) => { + for (const pieceIdx of pieceIdxs) { + changePiece(gameId, pieceIdx, { owner: -1, z: 1 }); + } +}; +const setTilesOwner = (gameId, pieceIdxs, owner) => { + for (const pieceIdx of pieceIdxs) { + changePiece(gameId, pieceIdx, { owner }); + } +}; +// get all grouped tiles for a tile +function getGroupedPieceIdxs(gameId, pieceIdx) { + const pieces = GAMES[gameId].puzzle.tiles; + const piece = Util.decodePiece(pieces[pieceIdx]); + const grouped = []; + if (piece.group) { + for (const other of pieces) { + const otherPiece = Util.decodePiece(other); + if (otherPiece.group === piece.group) { + grouped.push(otherPiece.idx); + } + } + } + else { + grouped.push(piece.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 freePieceIdxByPos = (gameId, pos) => { + const info = GAMES[gameId].puzzle.info; + const pieces = GAMES[gameId].puzzle.tiles; + let maxZ = -1; + let pieceIdx = -1; + for (let idx = 0; idx < pieces.length; idx++) { + const piece = Util.decodePiece(pieces[idx]); + if (piece.owner !== 0) { + continue; + } + const collisionRect = { + x: piece.pos.x, + y: piece.pos.y, + w: info.tileSize, + h: info.tileSize, + }; + if (Geometry.pointInBounds(pos, collisionRect)) { + if (maxZ === -1 || piece.z > maxZ) { + maxZ = piece.z; + pieceIdx = idx; + } + } + } + return pieceIdx; +}; +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 : 0; +}; +// determine if two tiles are grouped together +const areGrouped = (gameId, tileIdx1, tileIdx2) => { + const g1 = getPieceGroup(gameId, tileIdx1); + const g2 = getPieceGroup(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 _pieceChange = (pieceIdx) => { + changes.push([ + Protocol.CHANGE_TILE, + Util.encodePiece(getPiece(gameId, pieceIdx)), + ]); + }; + const _pieceChanges = (pieceIdxs) => { + for (const pieceIdx of pieceIdxs) { + _pieceChange(pieceIdx); + } + }; + const _playerChange = () => { + const player = getPlayer(gameId, playerId); + if (!player) { + return; + } + changes.push([ + Protocol.CHANGE_PLAYER, + Util.encodePlayer(player), + ]); + }; + // put both tiles (and their grouped tiles) in the same group + const groupTiles = (gameId, pieceIdx1, pieceIdx2) => { + const pieces = GAMES[gameId].puzzle.tiles; + const group1 = getPieceGroup(gameId, pieceIdx1); + const group2 = getPieceGroup(gameId, pieceIdx2); + 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); + } + changePiece(gameId, pieceIdx1, { group }); + _pieceChange(pieceIdx1); + changePiece(gameId, pieceIdx2, { group }); + _pieceChange(pieceIdx2); + // TODO: strange + if (searchGroups.length > 0) { + for (const p of pieces) { + const piece = Util.decodePiece(p); + if (searchGroups.includes(piece.group)) { + changePiece(gameId, piece.idx, { group }); + _pieceChange(piece.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 = freePieceIdxByPos(gameId, pos); + if (tileIdxAtPos >= 0) { + const maxZ = getMaxZIndex(gameId) + 1; + changeData(gameId, { maxZ }); + _dataChange(); + const tileIdxs = getGroupedPieceIdxs(gameId, tileIdxAtPos); + setPiecesZIndex(gameId, tileIdxs, getMaxZIndex(gameId)); + setTilesOwner(gameId, tileIdxs, playerId); + _pieceChanges(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 { + const pieceIdx = getFirstOwnedPieceIdx(gameId, playerId); + if (pieceIdx >= 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 pieceIdxs = getGroupedPieceIdxs(gameId, pieceIdx); + let anyOk = Geometry.pointInBounds(pos, getBounds(gameId)) + && Geometry.pointInBounds(evtInfo._last_mouse_down, getBounds(gameId)); + for (const idx of pieceIdxs) { + const bounds = getPieceBounds(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 }; + movePiecesDiff(gameId, pieceIdxs, diff); + _pieceChanges(pieceIdxs); + } + } + 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; + const pieceIdx = getFirstOwnedPieceIdx(gameId, playerId); + if (pieceIdx >= 0) { + // drop the tile(s) + const pieceIdxs = getGroupedPieceIdxs(gameId, pieceIdx); + setTilesOwner(gameId, pieceIdxs, 0); + _pieceChanges(pieceIdxs); + // Check if the tile was dropped near the final location + const tilePos = getPiecePos(gameId, pieceIdx); + const finalPos = getFinalPiecePos(gameId, pieceIdx); + if (Geometry.pointDistance(finalPos, tilePos) < puzzle.info.snapDistance) { + const diff = Geometry.pointSub(finalPos, tilePos); + // Snap the tile to the final destination + movePiecesDiff(gameId, pieceIdxs, diff); + finishPieces(gameId, pieceIdxs); + _pieceChanges(pieceIdxs); + let points = getPlayerPoints(gameId, playerId); + if (getScoreMode(gameId) === ScoreMode.FINAL) { + points += pieceIdxs.length; + } + else if (getScoreMode(gameId) === ScoreMode.ANY) { + points += 1; + } + else ; + changePlayer(gameId, playerId, { d, ts, points }); + _playerChange(); + // check if the puzzle is finished + if (getFinishedPiecesCount(gameId) === getPieceCount(gameId)) { + changeData(gameId, { finished: ts }); + _dataChange(); + } + } + else { + // Snap to other tiles + const check = (gameId, tileIdx, otherTileIdx, off) => { + const info = GAMES[gameId].puzzle.info; + if (otherTileIdx < 0) { + return false; + } + if (areGrouped(gameId, tileIdx, otherTileIdx)) { + return false; + } + const tilePos = getPiecePos(gameId, tileIdx); + const dstPos = Geometry.pointAdd(getPiecePos(gameId, otherTileIdx), { x: off[0] * info.tileSize, y: off[1] * info.tileSize }); + if (Geometry.pointDistance(tilePos, dstPos) < info.snapDistance) { + const diff = Geometry.pointSub(dstPos, tilePos); + let pieceIdxs = getGroupedPieceIdxs(gameId, tileIdx); + movePiecesDiff(gameId, pieceIdxs, diff); + groupTiles(gameId, tileIdx, otherTileIdx); + pieceIdxs = getGroupedPieceIdxs(gameId, tileIdx); + const zIndex = getMaxZIndexByPieceIdxs(gameId, pieceIdxs); + setPiecesZIndex(gameId, pieceIdxs, zIndex); + _pieceChanges(pieceIdxs); + return true; + } + return false; + }; + let snapped = false; + for (const pieceIdxTmp of getGroupedPieceIdxs(gameId, pieceIdx)) { + const othersIdxs = getSurroundingTilesByIdx(gameId, pieceIdxTmp); + if (check(gameId, pieceIdxTmp, othersIdxs[0], [0, 1]) // top + || check(gameId, pieceIdxTmp, othersIdxs[1], [-1, 0]) // right + || check(gameId, pieceIdxTmp, othersIdxs[2], [0, -1]) // bottom + || check(gameId, pieceIdxTmp, othersIdxs[3], [1, 0]) // left + ) { + snapped = true; + break; + } + } + if (snapped && getScoreMode(gameId) === ScoreMode.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 = { + setGame, + exists: exists$1, + playerExists, + getActivePlayers, + getIdlePlayers, + addPlayer: addPlayer$1, + getFinishedPiecesCount, + getPieceCount, + getImageUrl, + setImageUrl, + get: get$1, + getAllGames, + getPlayerBgColor, + getPlayerColor, + getPlayerName, + getPlayerIndexById, + getPlayerIdByIndex, + changePlayer, + setPlayer, + setPiece, + setPuzzleData, + getTableWidth, + getTableHeight, + getPuzzle, + getRng, + getPuzzleWidth, + getPuzzleHeight, + getPiecesSortedByZIndex, + getFirstOwnedPiece, + getPieceDrawOffset, + getPieceDrawSize, + getFinalPiecePos, + getStartTs, + getFinishTs, + handleInput: handleInput$1, +}; + const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const BASE_DIR = `${__dirname}/../..`; @@ -1234,6 +1235,7 @@ var GameLog = { get, }; +const log$4 = logger('Images.ts'); const resizeImage = async (filename) => { if (!filename.toLowerCase().match(/\.(jpe?g|webp|png)$/)) { return; @@ -1257,19 +1259,21 @@ const resizeImage = async (filename) => { [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`); + for (const [w, h] of sizes) { + log$4.info(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) { + return new Promise((resolve) => { + new exif.ExifImage({ image: imagePath }, (error, exifData) => { if (error) { resolve(0); } else { - resolve(exifData.image.Orientation); + resolve(exifData.image.Orientation || 0); } }); }); @@ -1279,7 +1283,11 @@ const getTags = (db, imageId) => { select * from categories c inner join image_x_category ixc on c.id = ixc.category_id where ixc.image_id = ?`; - return db._getMany(query, [imageId]); + return db._getMany(query, [imageId]).map(row => ({ + id: parseInt(row.number, 10) || 0, + slug: row.slug, + title: row.tittle, + })); }; const imageFromDb = (db, imageId) => { const i = db.get('images', { id: imageId }); @@ -1293,8 +1301,8 @@ const imageFromDb = (db, imageId) => { created: i.created * 1000, }; }; -const allImagesFromDb = (db, tagSlugs, sort) => { - const sortMap = { +const allImagesFromDb = (db, tagSlugs, orderBy) => { + const orderByMap = { alpha_asc: [{ filename: 1 }], alpha_desc: [{ filename: -1 }], date_asc: [{ created: 1 }], @@ -1319,7 +1327,7 @@ inner join images i on i.id = ixc.image_id ${where.sql}; } wheresRaw['id'] = { '$in': ids }; } - const images = db.getMany('images', wheresRaw, sortMap[sort]); + const images = db.getMany('images', wheresRaw, orderByMap[orderBy]); return images.map(i => ({ id: i.id, filename: i.filename, @@ -1368,7 +1376,7 @@ const allImagesFromDisk = (tags, sort) => { return images; }; async function getDimensions(imagePath) { - let dimensions = sizeOf(imagePath); + const 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/ @@ -1403,15 +1411,15 @@ async function createPuzzle(rng, targetTiles, image, ts) { throw `[ 2021-05-16 invalid dimension for path ${imagePath} ]`; } const info = determinePuzzleInfo(dim, targetTiles); - let tiles = new Array(info.tiles); - for (let i = 0; i < tiles.length; i++) { - tiles[i] = { idx: i }; + const rawPieces = new Array(info.tiles); + for (let i = 0; i < rawPieces.length; i++) { + rawPieces[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] = { + for (const piece of rawPieces) { + const coord = Util.coordByPieceIdx(info, piece.idx); + positions[piece.idx] = { // instead of info.tileSize, we use info.tileDrawSize // to spread the tiles a bit x: coord.x * info.tileSize * 1.5, @@ -1421,7 +1429,7 @@ async function createPuzzle(rng, targetTiles, image, ts) { const tableWidth = info.width * 3; const tableHeight = info.height * 3; const off = info.tileSize * 1.5; - let last = { + const last = { x: info.width - (1 * off), y: info.height - (2 * off), }; @@ -1430,7 +1438,7 @@ async function createPuzzle(rng, targetTiles, image, ts) { let diffX = off; let diffY = 0; let index = 0; - for (let pos of positions) { + for (const pos of positions) { pos.x = last.x; pos.y = last.y; last.x += diffX; @@ -1456,9 +1464,9 @@ async function createPuzzle(rng, targetTiles, image, ts) { } // then shuffle the positions positions = rng.shuffle(positions); - const pieces = tiles.map(tile => { + const pieces = rawPieces.map(piece => { return Util.encodePiece({ - idx: tile.idx, + idx: piece.idx, group: 0, z: 0, // who owns the tile @@ -1469,7 +1477,7 @@ async function createPuzzle(rng, targetTiles, image, ts) { // 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], + pos: positions[piece.idx], }); }); // Complete puzzle object @@ -1520,7 +1528,7 @@ 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); + const coord = Util.coordByPieceIdx(info, i); shapes[i] = { top: coord.y === 0 ? 0 : shapes[i - info.tilesX].bottom * -1, right: coord.x === info.tilesX - 1 ? 0 : rng.choice(tabs), @@ -1566,12 +1574,12 @@ const determinePuzzleInfo = (dim, targetTiles) => { }; const log$3 = logger('GameStorage.js'); -const DIRTY_GAMES = {}; +const dirtyGames = {}; function setDirty(gameId) { - DIRTY_GAMES[gameId] = true; + dirtyGames[gameId] = true; } function setClean(gameId) { - delete DIRTY_GAMES[gameId]; + delete dirtyGames[gameId]; } function loadGames() { const files = fs.readdirSync(DATA_DIR); @@ -1620,13 +1628,17 @@ function loadGame(gameId) { GameCommon.setGame(gameObject.id, gameObject); } function persistGames() { - for (const gameId of Object.keys(DIRTY_GAMES)) { + for (const gameId of Object.keys(dirtyGames)) { persistGame(gameId); } } function persistGame(gameId) { const game = GameCommon.get(gameId); - if (game.id in DIRTY_GAMES) { + if (!game) { + log$3.error(`[ERROR] unable to persist non existing game ${gameId}`); + return; + } + if (game.id in dirtyGames) { setClean(game.id); } fs.writeFileSync(`${DATA_DIR}/${game.id}.json`, JSON.stringify({ @@ -1790,7 +1802,7 @@ class Db { let prop = '$nin'; if (where[k][prop]) { if (where[k][prop].length > 0) { - wheres.push(k + ' NOT IN (' + where[k][prop].map((_) => '?') + ')'); + wheres.push(k + ' NOT IN (' + where[k][prop].map(() => '?') + ')'); values.push(...where[k][prop]); } continue; @@ -1798,7 +1810,7 @@ class Db { prop = '$in'; if (where[k][prop]) { if (where[k][prop].length > 0) { - wheres.push(k + ' IN (' + where[k][prop].map((_) => '?') + ')'); + wheres.push(k + ' IN (' + where[k][prop].map(() => '?') + ')'); values.push(...where[k][prop]); } continue; @@ -1866,7 +1878,7 @@ class Db { const values = keys.map(k => data[k]); const sql = 'INSERT INTO ' + table + ' (' + keys.join(',') + ')' - + ' VALUES (' + keys.map(k => '?').join(',') + ')'; + + ' VALUES (' + keys.map(() => '?').join(',') + ')'; return this.run(sql, values).lastInsertRowid; } update(table, data, whereRaw = {}) { @@ -1977,7 +1989,7 @@ const setImageTags = (db, imageId, tags) => { } }); }; -app.post('/api/save-image', bodyParser.json(), (req, res) => { +app.post('/api/save-image', express.json(), (req, res) => { const data = req.body; db.update('images', { title: data.title, @@ -2015,7 +2027,7 @@ app.post('/api/upload', (req, res) => { res.send(Images.imageFromDb(db, imageId)); }); }); -app.post('/newgame', bodyParser.json(), async (req, res) => { +app.post('/newgame', express.json(), async (req, res) => { const gameSettings = req.body; log.log(gameSettings); const gameId = Util.uniqId(); @@ -2029,15 +2041,14 @@ 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) { + for (const socket of sockets) { wss.notifyOne(data, socket); } }; wss.on('close', async ({ socket }) => { try { const proto = socket.protocol.split('|'); - const clientId = proto[0]; + // const clientId = proto[0] const gameId = proto[1]; GameSockets.removeSocket(gameId, socket); } @@ -2062,6 +2073,9 @@ wss.on('message', async ({ socket, data }) => { Game.addPlayer(gameId, clientId, ts); GameSockets.addSocket(gameId, socket); const game = GameCommon.get(gameId); + if (!game) { + throw `[game ${gameId} does not exist (anymore)... ]`; + } notify([Protocol.EV_SERVER_INIT, Util.encodeGame(game)], [socket]); } break; @@ -2084,6 +2098,9 @@ wss.on('message', async ({ socket, data }) => { } if (sendGame) { const game = GameCommon.get(gameId); + if (!game) { + throw `[game ${gameId} does not exist (anymore)... ]`; + } notify([Protocol.EV_SERVER_INIT, Util.encodeGame(game)], [socket]); } const changes = Game.handleInput(gameId, clientId, clientEvtData, ts); @@ -2101,7 +2118,7 @@ const server = app.listen(port, hostname, () => log.log(`server running on http: wss.listen(); const memoryUsageHuman = () => { const totalHeapSize = v8.getHeapStatistics().total_available_size; - let totalHeapSizeInGB = (totalHeapSize / 1024 / 1024 / 1024).toFixed(2); + const 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`); @@ -2130,9 +2147,9 @@ const gracefulShutdown = (signal) => { process.once('SIGUSR2', function () { gracefulShutdown('SIGUSR2'); }); -process.once('SIGINT', function (code) { +process.once('SIGINT', function () { gracefulShutdown('SIGINT'); }); -process.once('SIGTERM', function (code) { +process.once('SIGTERM', function () { gracefulShutdown('SIGTERM'); }); diff --git a/package-lock.json b/package-lock.json index 7246c08..199e26a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,6 @@ "": { "dependencies": { "better-sqlite3": "^7.4.0", - "body-parser": "^1.19.0", "exif": "^0.6.0", "express": "^4.17.1", "image-size": "^0.9.3", @@ -24,15 +23,18 @@ "@types/multer": "^1.4.5", "@types/sharp": "^0.28.1", "@types/ws": "^7.4.4", + "@typescript-eslint/eslint-plugin": "^4.25.0", + "@typescript-eslint/parser": "^4.25.0", "@vitejs/plugin-vue": "^1.2.2", "@vuedx/typescript-plugin-vue": "^0.6.3", "compression": "^1.7.4", + "eslint": "^7.27.0", "jest": "^26.6.3", "rollup": "^2.48.0", "rollup-plugin-typescript2": "^0.30.0", "rollup-plugin-vue": "^6.0.0-beta.10", "ts-node": "^9.1.1", - "typescript": "^4.2.4", + "typescript": "^4.3.2", "vite": "^2.3.2" }, "engines": { @@ -539,6 +541,62 @@ "node": ">=0.1.95" } }, + "node_modules/@eslint/eslintrc": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.1.tgz", + "integrity": "sha512-5v7TDE9plVhvxQeWLXDTvFvJBdH6pEsdnl2g/dAptmuFEPedQ4Erq5rsDsX+mvAM610IhNaO2W5V1dOOnDKxkQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "dependencies": { + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/@intlify/core": { "version": "9.1.6", "resolved": "https://registry.npmjs.org/@intlify/core/-/core-9.1.6.tgz", @@ -1568,6 +1626,12 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/json-schema": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", + "dev": true + }, "node_modules/@types/micromatch": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.1.tgz", @@ -1671,6 +1735,229 @@ "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", "dev": true }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.25.0.tgz", + "integrity": "sha512-Qfs3dWkTMKkKwt78xp2O/KZQB8MPS1UQ5D3YW2s6LQWBE1074BE+Rym+b1pXZIX3M3fSvPUDaCvZLKV2ylVYYQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/experimental-utils": "4.25.0", + "@typescript-eslint/scope-manager": "4.25.0", + "debug": "^4.1.1", + "functional-red-black-tree": "^1.0.1", + "lodash": "^4.17.15", + "regexpp": "^3.0.0", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^4.0.0", + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.25.0.tgz", + "integrity": "sha512-f0doRE76vq7NEEU0tw+ajv6CrmPelw5wLoaghEHkA2dNLFb3T/zJQqGPQ0OYt5XlZaS13MtnN+GTPCuUVg338w==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.25.0", + "@typescript-eslint/types": "4.25.0", + "@typescript-eslint/typescript-estree": "4.25.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.25.0.tgz", + "integrity": "sha512-OZFa1SKyEJpAhDx8FcbWyX+vLwh7OEtzoo2iQaeWwxucyfbi0mT4DijbOSsTgPKzGHr6GrF2V5p/CEpUH/VBxg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "4.25.0", + "@typescript-eslint/types": "4.25.0", + "@typescript-eslint/typescript-estree": "4.25.0", + "debug": "^4.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.25.0.tgz", + "integrity": "sha512-2NElKxMb/0rya+NJG1U71BuNnp1TBd1JgzYsldsdA83h/20Tvnf/HrwhiSlNmuq6Vqa0EzidsvkTArwoq+tH6w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.25.0", + "@typescript-eslint/visitor-keys": "4.25.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.25.0.tgz", + "integrity": "sha512-+CNINNvl00OkW6wEsi32wU5MhHti2J25TJsJJqgQmJu3B3dYDBcmOxcE5w9cgoM13TrdE/5ND2HoEnBohasxRQ==", + "dev": true, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.25.0.tgz", + "integrity": "sha512-1B8U07TGNAFMxZbSpF6jqiDs1cVGO0izVkf18Q/SPcUAc9LhHxzvSowXDTvkHMWUVuPpagupaW63gB6ahTXVlg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.25.0", + "@typescript-eslint/visitor-keys": "4.25.0", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.25.0.tgz", + "integrity": "sha512-AmkqV9dDJVKP/TcZrbf6s6i1zYXt5Hl8qOLrRDTFfRNae4+LB8A4N3i+FLZPW85zIxRy39BgeWOfMS3HoH5ngg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.25.0", + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@vitejs/plugin-vue": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.2.2.tgz", @@ -2025,6 +2312,15 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/acorn-walk": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", @@ -2062,6 +2358,15 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -2210,6 +2515,15 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -2246,6 +2560,15 @@ "node": ">=0.10.0" } }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -3610,6 +3933,30 @@ "node": ">= 10.14.2" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/domexception": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", @@ -3696,6 +4043,18 @@ "once": "^1.4.0" } }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3760,6 +4119,366 @@ "source-map": "~0.6.1" } }, + "node_modules/eslint": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.27.0.tgz", + "integrity": "sha512-JZuR6La2ZF0UD384lcbnd0Cgg6QJjiCwhMD6eU4h/VGPcVGwawNNzKU41tgokGXnfjOOyI6QIffthhJTPzzuRA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.1", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", + "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -3773,6 +4492,30 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/estraverse": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", @@ -4252,6 +4995,18 @@ "bser": "2.1.1" } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -4329,6 +5084,25 @@ "node": ">=8" } }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", + "dev": true + }, "node_modules/for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -4445,6 +5219,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "node_modules/gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -4595,6 +5375,35 @@ "node": ">=4" } }, + "node_modules/globby": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", + "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", @@ -4861,6 +5670,15 @@ } ] }, + "node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/image-size": { "version": "0.9.7", "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.9.7.tgz", @@ -4875,6 +5693,31 @@ "node": ">=10.18.0" } }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/import-local": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", @@ -7113,6 +7956,12 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -7257,6 +8106,24 @@ "dev": true, "peer": true }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, "node_modules/lru_map": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", @@ -8042,6 +8909,18 @@ "node": ">=6" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -8136,6 +9015,15 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -8398,6 +9286,15 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/prompts": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", @@ -8611,6 +9508,18 @@ "node": ">=0.10.0" } }, + "node_modules/regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, "node_modules/remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -8753,6 +9662,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -9509,6 +10427,65 @@ "node": ">=8" } }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -10127,6 +11104,68 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/table": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.5.0.tgz", + "integrity": "sha512-Y2l399Tt1AguU3BPRP9Fn4eN+Or+StUGWCUpbnFyXSo8NZ9S4uj+AG2pjs5apK+ZMOwYOz1+a+VKvKH7CudXgQ==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/table/node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tar": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", @@ -10260,6 +11299,12 @@ "node": ">=8" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -10418,6 +11463,21 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -10495,9 +11555,9 @@ } }, "node_modules/typescript": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", - "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", + "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -10649,6 +11709,12 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, "node_modules/v8-to-istanbul": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", @@ -11522,6 +12588,46 @@ "minimist": "^1.2.0" } }, + "@eslint/eslintrc": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.1.tgz", + "integrity": "sha512-5v7TDE9plVhvxQeWLXDTvFvJBdH6pEsdnl2g/dAptmuFEPedQ4Erq5rsDsX+mvAM610IhNaO2W5V1dOOnDKxkQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, "@intlify/core": { "version": "9.1.6", "resolved": "https://registry.npmjs.org/@intlify/core/-/core-9.1.6.tgz", @@ -12355,6 +13461,12 @@ "@types/istanbul-lib-report": "*" } }, + "@types/json-schema": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", + "dev": true + }, "@types/micromatch": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.1.tgz", @@ -12458,6 +13570,141 @@ "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", "dev": true }, + "@typescript-eslint/eslint-plugin": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.25.0.tgz", + "integrity": "sha512-Qfs3dWkTMKkKwt78xp2O/KZQB8MPS1UQ5D3YW2s6LQWBE1074BE+Rym+b1pXZIX3M3fSvPUDaCvZLKV2ylVYYQ==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "4.25.0", + "@typescript-eslint/scope-manager": "4.25.0", + "debug": "^4.1.1", + "functional-red-black-tree": "^1.0.1", + "lodash": "^4.17.15", + "regexpp": "^3.0.0", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@typescript-eslint/experimental-utils": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.25.0.tgz", + "integrity": "sha512-f0doRE76vq7NEEU0tw+ajv6CrmPelw5wLoaghEHkA2dNLFb3T/zJQqGPQ0OYt5XlZaS13MtnN+GTPCuUVg338w==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.25.0", + "@typescript-eslint/types": "4.25.0", + "@typescript-eslint/typescript-estree": "4.25.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.25.0.tgz", + "integrity": "sha512-OZFa1SKyEJpAhDx8FcbWyX+vLwh7OEtzoo2iQaeWwxucyfbi0mT4DijbOSsTgPKzGHr6GrF2V5p/CEpUH/VBxg==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "4.25.0", + "@typescript-eslint/types": "4.25.0", + "@typescript-eslint/typescript-estree": "4.25.0", + "debug": "^4.1.1" + } + }, + "@typescript-eslint/scope-manager": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.25.0.tgz", + "integrity": "sha512-2NElKxMb/0rya+NJG1U71BuNnp1TBd1JgzYsldsdA83h/20Tvnf/HrwhiSlNmuq6Vqa0EzidsvkTArwoq+tH6w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.25.0", + "@typescript-eslint/visitor-keys": "4.25.0" + } + }, + "@typescript-eslint/types": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.25.0.tgz", + "integrity": "sha512-+CNINNvl00OkW6wEsi32wU5MhHti2J25TJsJJqgQmJu3B3dYDBcmOxcE5w9cgoM13TrdE/5ND2HoEnBohasxRQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.25.0.tgz", + "integrity": "sha512-1B8U07TGNAFMxZbSpF6jqiDs1cVGO0izVkf18Q/SPcUAc9LhHxzvSowXDTvkHMWUVuPpagupaW63gB6ahTXVlg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.25.0", + "@typescript-eslint/visitor-keys": "4.25.0", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.25.0.tgz", + "integrity": "sha512-AmkqV9dDJVKP/TcZrbf6s6i1zYXt5Hl8qOLrRDTFfRNae4+LB8A4N3i+FLZPW85zIxRy39BgeWOfMS3HoH5ngg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.25.0", + "eslint-visitor-keys": "^2.0.0" + } + }, "@vitejs/plugin-vue": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.2.2.tgz", @@ -12757,6 +14004,13 @@ } } }, + "acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true, + "requires": {} + }, "acorn-walk": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", @@ -12784,6 +14038,12 @@ "uri-js": "^4.2.2" } }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, "ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -12910,6 +14170,12 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -12937,6 +14203,12 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -14003,6 +15275,24 @@ "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", "dev": true }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "domexception": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", @@ -14073,6 +15363,15 @@ "once": "^1.4.0" } }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -14118,12 +15417,293 @@ "source-map": "~0.6.1" } }, + "eslint": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.27.0.tgz", + "integrity": "sha512-JZuR6La2ZF0UD384lcbnd0Cgg6QJjiCwhMD6eU4h/VGPcVGwawNNzKU41tgokGXnfjOOyI6QIffthhJTPzzuRA==", + "dev": true, + "requires": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.1", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "globals": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", + "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "dependencies": { + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + } + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, "estraverse": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", @@ -14527,6 +16107,15 @@ "bser": "2.1.1" } }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -14591,6 +16180,22 @@ "path-exists": "^4.0.0" } }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", + "dev": true + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -14676,6 +16281,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -14792,6 +16403,28 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, + "globby": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", + "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + } + } + }, "graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", @@ -14993,6 +16626,12 @@ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, "image-size": { "version": "0.9.7", "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.9.7.tgz", @@ -15001,6 +16640,24 @@ "queue": "6.0.2" } }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, "import-local": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", @@ -16708,6 +18365,12 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -16824,6 +18487,24 @@ "dev": true, "peer": true }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, "lru_map": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", @@ -17451,6 +19132,15 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -17526,6 +19216,12 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -17718,6 +19414,12 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, "prompts": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", @@ -17876,6 +19578,12 @@ "safe-regex": "^1.1.0" } }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -17984,6 +19692,12 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -18583,6 +20297,49 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + } + } + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -19088,6 +20845,57 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "table": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.5.0.tgz", + "integrity": "sha512-Y2l399Tt1AguU3BPRP9Fn4eN+Or+StUGWCUpbnFyXSo8NZ9S4uj+AG2pjs5apK+ZMOwYOz1+a+VKvKH7CudXgQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + } + } + }, "tar": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", @@ -19189,6 +20997,12 @@ "minimatch": "^3.0.4" } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, "thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -19310,6 +21124,15 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -19369,9 +21192,9 @@ } }, "typescript": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", - "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", + "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==", "dev": true }, "union-value": { @@ -19488,6 +21311,12 @@ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, "v8-to-istanbul": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", diff --git a/package.json b/package.json index d36af29..11aaf70 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "type": "module", "dependencies": { "better-sqlite3": "^7.4.0", - "body-parser": "^1.19.0", "exif": "^0.6.0", "express": "^4.17.1", "image-size": "^0.9.3", @@ -20,15 +19,18 @@ "@types/multer": "^1.4.5", "@types/sharp": "^0.28.1", "@types/ws": "^7.4.4", + "@typescript-eslint/eslint-plugin": "^4.25.0", + "@typescript-eslint/parser": "^4.25.0", "@vitejs/plugin-vue": "^1.2.2", "@vuedx/typescript-plugin-vue": "^0.6.3", "compression": "^1.7.4", + "eslint": "^7.27.0", "jest": "^26.6.3", "rollup": "^2.48.0", "rollup-plugin-typescript2": "^0.30.0", "rollup-plugin-vue": "^6.0.0-beta.10", "ts-node": "^9.1.1", - "typescript": "^4.2.4", + "typescript": "^4.3.2", "vite": "^2.3.2" }, "engines": { @@ -37,6 +39,7 @@ }, "scripts": { "rollup": "rollup", - "vite": "vite" + "vite": "vite", + "eslint": "eslint" } } diff --git a/rollup.server.config.js b/rollup.server.config.js index 117fee1..091ff79 100644 --- a/rollup.server.config.js +++ b/rollup.server.config.js @@ -8,17 +8,20 @@ export default { format: 'es', }, external: [ - "express", - "multer", - "body-parser", - "v8", - "fs", - "ws", - "image-size", + "better-sqlite3", + "compression", "exif", - "sharp", - "url", + "express", + "fs", + "image-size", + "multer", "path", + "readline", + "sharp", + "stream", + "url", + "v8", + "ws", ], plugins: [typescript()], }; diff --git a/scripts/lint b/scripts/lint new file mode 100755 index 0000000..055d373 --- /dev/null +++ b/scripts/lint @@ -0,0 +1,5 @@ +#!/bin/sh -e + +cd "$RUN_DIR" + +npm run eslint src diff --git a/scripts/tests b/scripts/tests index a047361..ac15ff6 100755 --- a/scripts/tests +++ b/scripts/tests @@ -1,3 +1,3 @@ -#!/bin/sh +#!/bin/sh -e node --experimental-vm-modules node_modules/.bin/jest diff --git a/src/common/GameCommon.ts b/src/common/GameCommon.ts index e9e2d30..629248a 100644 --- a/src/common/GameCommon.ts +++ b/src/common/GameCommon.ts @@ -1,172 +1,31 @@ import Geometry, { Point, Rect } from './Geometry' import Protocol from './Protocol' -import { Rng, RngSerialized } from './Rng' +import { Rng } from './Rng' import Time from './Time' -import { FixedLengthArray } from './Types' -import Util from './Util' - -export type Timestamp = number - -export type EncodedPlayer = FixedLengthArray<[ - string, - number, - number, - 0|1, - string|null, - string|null, - string|null, - number, - Timestamp, -]> - -export type EncodedPiece = FixedLengthArray<[ - number, - number, - number, - number, - string|number, - number, -]> - -export type EncodedPieceShape = number - -export type EncodedGame = FixedLengthArray<[ - string, - string, - RngSerialized, +import { + Change, + EncodedPiece, + EvtInfo, + Game, + Input, + Piece, + PieceChange, + Player, + PlayerChange, Puzzle, - Array, - Record, + PuzzleData, + PuzzleDataChange, ScoreMode, -]> - -export interface ReplayData { - log: any[], - game: EncodedGame|null -} - -export interface Tag { - id: number - slug: string - title: string -} - -interface GameRng { - obj: Rng - type?: string -} - -export interface Game { - id: string - players: Array - puzzle: Puzzle - evtInfos: Record - scoreMode?: ScoreMode - rng: GameRng -} - -export interface Image { - id: number - filename: string - file: string - url: string - title: string - tags: Array - created: number -} - -export interface GameSettings { - tiles: number - image: Image - scoreMode: ScoreMode -} - -export interface Puzzle { - tiles: Array - data: PuzzleData - info: PuzzleInfo -} - -interface PuzzleData { - started: number - finished: number - maxGroup: number - maxZ: number -} - -interface PuzzleTable { - width: number - height: number -} - -enum PieceEdge { - Flat = 0, - Out = 1, - In = -1, -} -export interface PieceShape { - top: PieceEdge - bottom: PieceEdge - left: PieceEdge - right: PieceEdge -} - -export interface Piece { - owner: string|number - idx: number - pos: Point - z: number - group: number -} - -export interface PuzzleInfo { - table: PuzzleTable - targetTiles: number, - imageUrl: string - - width: number - height: number - tileSize: number - tileDrawSize: number - tileMarginWidth: number - tileDrawOffset: number - snapDistance: number - - tiles: number - tilesX: number - tilesY: number - - shapes: Array -} - -export interface Player { - id: string - x: number - y: number - d: 0|1 - name: string|null - color: string|null - bgcolor: string|null - points: number - ts: Timestamp -} - -interface EvtInfo { - _last_mouse: Point|null - _last_mouse_down: Point|null -} - -export enum ScoreMode { - FINAL = 0, - ANY = 1, -} + Timestamp +} from './Types' +import Util from './Util' const IDLE_TIMEOUT_SEC = 30 // Map const GAMES: Record = {} -function exists(gameId: string) { +function exists(gameId: string): boolean { return (!!GAMES[gameId]) || false } @@ -190,7 +49,7 @@ function setGame(gameId: string, game: Game): void { function getPlayerIndexById(gameId: string, playerId: string): number { let i = 0; - for (let player of GAMES[gameId].players) { + for (const player of GAMES[gameId].players) { if (Util.decodePlayer(player).id === playerId) { return i } @@ -293,8 +152,8 @@ function getAllPlayers(gameId: string): Array { : [] } -function get(gameId: string) { - return GAMES[gameId] +function get(gameId: string): Game|null { + return GAMES[gameId] || null } function getPieceCount(gameId: string): number { @@ -319,7 +178,7 @@ function isFinished(gameId: string): boolean { function getFinishedPiecesCount(gameId: string): number { let count = 0 - for (let t of GAMES[gameId].puzzle.tiles) { + for (const t of GAMES[gameId].puzzle.tiles) { if (Util.decodePiece(t).owner === -1) { count++ } @@ -335,29 +194,33 @@ function getPiecesSortedByZIndex(gameId: string): Piece[] { function changePlayer( gameId: string, playerId: string, - change: any + change: PlayerChange ): void { const player = getPlayer(gameId, playerId) if (player === null) { return } - for (let k of Object.keys(change)) { + for (const k of Object.keys(change)) { // @ts-ignore player[k] = change[k] } setPlayer(gameId, playerId, player) } -function changeData(gameId: string, change: any): void { - for (let k of Object.keys(change)) { +function changeData(gameId: string, change: PuzzleDataChange): void { + for (const k of Object.keys(change)) { // @ts-ignore GAMES[gameId].puzzle.data[k] = change[k] } } -function changeTile(gameId: string, pieceIdx: number, change: any): void { - for (let k of Object.keys(change)) { +function changePiece( + gameId: string, + pieceIdx: number, + change: PieceChange +): void { + for (const k of Object.keys(change)) { const piece = Util.decodePiece(GAMES[gameId].puzzle.tiles[pieceIdx]) // @ts-ignore piece[k] = change[k] @@ -415,13 +278,12 @@ const getPieceBounds = (gameId: string, tileIdx: number): Rect => { } } -const getTileZIndex = (gameId: string, tileIdx: number): number => { - const tile = getPiece(gameId, tileIdx) - return tile.z +const getPieceZIndex = (gameId: string, pieceIdx: number): number => { + return getPiece(gameId, pieceIdx).z } const getFirstOwnedPieceIdx = (gameId: string, playerId: string): number => { - for (let t of GAMES[gameId].puzzle.tiles) { + for (const t of GAMES[gameId].puzzle.tiles) { const tile = Util.decodePiece(t) if (tile.owner === playerId) { return tile.idx @@ -430,7 +292,10 @@ const getFirstOwnedPieceIdx = (gameId: string, playerId: string): number => { return -1 } -const getFirstOwnedPiece = (gameId: string, playerId: string): EncodedPiece|null => { +const getFirstOwnedPiece = ( + gameId: string, + playerId: string +): EncodedPiece|null => { const idx = getFirstOwnedPieceIdx(gameId, playerId) return idx < 0 ? null : GAMES[gameId].puzzle.tiles[idx] } @@ -463,12 +328,12 @@ const getMaxZIndex = (gameId: string): number => { return GAMES[gameId].puzzle.data.maxZ } -const getMaxZIndexByTileIdxs = (gameId: string, tileIdxs: Array): number => { +const getMaxZIndexByPieceIdxs = (gameId: string, pieceIdxs: Array): number => { let maxZ = 0 - for (let tileIdx of tileIdxs) { - let tileZIndex = getTileZIndex(gameId, tileIdx) - if (tileZIndex > maxZ) { - maxZ = tileZIndex + for (const pieceIdx of pieceIdxs) { + const curZ = getPieceZIndex(gameId, pieceIdx) + if (curZ > maxZ) { + maxZ = curZ } } return maxZ @@ -477,7 +342,7 @@ const getMaxZIndexByTileIdxs = (gameId: string, tileIdxs: Array): number function srcPosByTileIdx(gameId: string, tileIdx: number): Point { const info = GAMES[gameId].puzzle.info - const c = Util.coordByTileIdx(info, tileIdx) + const c = Util.coordByPieceIdx(info, tileIdx) const cx = c.x * info.tileSize const cy = c.y * info.tileSize @@ -487,7 +352,7 @@ function srcPosByTileIdx(gameId: string, tileIdx: number): Point { function getSurroundingTilesByIdx(gameId: string, tileIdx: number) { const info = GAMES[gameId].puzzle.info - const c = Util.coordByTileIdx(info, tileIdx) + const c = Util.coordByPieceIdx(info, tileIdx) return [ // top @@ -501,29 +366,29 @@ function getSurroundingTilesByIdx(gameId: string, tileIdx: number) { ] } -const setTilesZIndex = (gameId: string, tileIdxs: Array, zIndex: number): void => { - for (let tilesIdx of tileIdxs) { - changeTile(gameId, tilesIdx, { z: zIndex }) +const setPiecesZIndex = (gameId: string, tileIdxs: Array, zIndex: number): void => { + for (const tilesIdx of tileIdxs) { + changePiece(gameId, tilesIdx, { z: zIndex }) } } const moveTileDiff = (gameId: string, tileIdx: number, diff: Point): void => { const oldPos = getPiecePos(gameId, tileIdx) const pos = Geometry.pointAdd(oldPos, diff) - changeTile(gameId, tileIdx, { pos }) + changePiece(gameId, tileIdx, { pos }) } -const moveTilesDiff = ( +const movePiecesDiff = ( gameId: string, - tileIdxs: Array, + pieceIdxs: Array, diff: Point ): void => { const drawSize = getPieceDrawSize(gameId) const bounds = getBounds(gameId) const cappedDiff = diff - for (let tileIdx of tileIdxs) { - const t = getPiece(gameId, tileIdx) + for (const pieceIdx of pieceIdxs) { + const t = getPiece(gameId, pieceIdx) if (t.pos.x + diff.x < bounds.x) { cappedDiff.x = Math.max(bounds.x - t.pos.x, cappedDiff.x) } else if (t.pos.x + drawSize + diff.x > bounds.x + bounds.w) { @@ -536,24 +401,24 @@ const moveTilesDiff = ( } } - for (let tileIdx of tileIdxs) { - moveTileDiff(gameId, tileIdx, cappedDiff) + for (const pieceIdx of pieceIdxs) { + moveTileDiff(gameId, pieceIdx, cappedDiff) } } -const finishTiles = (gameId: string, tileIdxs: Array): void => { - for (let tileIdx of tileIdxs) { - changeTile(gameId, tileIdx, { owner: -1, z: 1 }) +const finishPieces = (gameId: string, pieceIdxs: Array): void => { + for (const pieceIdx of pieceIdxs) { + changePiece(gameId, pieceIdx, { owner: -1, z: 1 }) } } const setTilesOwner = ( gameId: string, - tileIdxs: Array, + pieceIdxs: Array, owner: string|number ): void => { - for (let tileIdx of tileIdxs) { - changeTile(gameId, tileIdx, { owner }) + for (const pieceIdx of pieceIdxs) { + changePiece(gameId, pieceIdx, { owner }) } } @@ -564,7 +429,7 @@ function getGroupedPieceIdxs(gameId: string, pieceIdx: number): number[] { const grouped = [] if (piece.group) { - for (let other of pieces) { + for (const other of pieces) { const otherPiece = Util.decodePiece(other) if (otherPiece.group === piece.group) { grouped.push(otherPiece.idx) @@ -579,8 +444,8 @@ function getGroupedPieceIdxs(gameId: string, pieceIdx: number): number[] { // Returns the index of the puzzle tile with the highest z index // that is not finished yet and that matches the position const freePieceIdxByPos = (gameId: string, pos: Point): number => { - let info = GAMES[gameId].puzzle.info - let pieces = GAMES[gameId].puzzle.tiles + const info = GAMES[gameId].puzzle.info + const pieces = GAMES[gameId].puzzle.tiles let maxZ = -1 let pieceIdx = -1 @@ -664,28 +529,28 @@ const getPuzzleHeight = (gameId: string): number => { function handleInput( gameId: string, playerId: string, - input: any, - ts: number -): Array> { + input: Input, + ts: Timestamp +): Array { const puzzle = GAMES[gameId].puzzle const evtInfo = getEvtInfo(gameId, playerId) - const changes = [] as Array> + const changes: Array = [] const _dataChange = (): void => { changes.push([Protocol.CHANGE_DATA, puzzle.data]) } - const _tileChange = (tileIdx: number): void => { + const _pieceChange = (pieceIdx: number): void => { changes.push([ Protocol.CHANGE_TILE, - Util.encodePiece(getPiece(gameId, tileIdx)), + Util.encodePiece(getPiece(gameId, pieceIdx)), ]) } - const _tileChanges = (tileIdxs: Array): void => { - for (const tileIdx of tileIdxs) { - _tileChange(tileIdx) + const _pieceChanges = (pieceIdxs: Array): void => { + for (const pieceIdx of pieceIdxs) { + _pieceChange(pieceIdx) } } @@ -703,12 +568,12 @@ function handleInput( // put both tiles (and their grouped tiles) in the same group const groupTiles = ( gameId: string, - tileIdx1: number, - tileIdx2: number + pieceIdx1: number, + pieceIdx2: number ): void => { - const tiles = GAMES[gameId].puzzle.tiles - const group1 = getPieceGroup(gameId, tileIdx1) - const group2 = getPieceGroup(gameId, tileIdx2) + const pieces = GAMES[gameId].puzzle.tiles + const group1 = getPieceGroup(gameId, pieceIdx1) + const group2 = getPieceGroup(gameId, pieceIdx2) let group const searchGroups = [] @@ -729,18 +594,18 @@ function handleInput( group = getMaxGroup(gameId) } - changeTile(gameId, tileIdx1, { group }) - _tileChange(tileIdx1) - changeTile(gameId, tileIdx2, { group }) - _tileChange(tileIdx2) + changePiece(gameId, pieceIdx1, { group }) + _pieceChange(pieceIdx1) + changePiece(gameId, pieceIdx2, { group }) + _pieceChange(pieceIdx2) // TODO: strange if (searchGroups.length > 0) { - for (const t of tiles) { - const piece = Util.decodePiece(t) + for (const p of pieces) { + const piece = Util.decodePiece(p) if (searchGroups.includes(piece.group)) { - changeTile(gameId, piece.idx, { group }) - _tileChange(piece.idx) + changePiece(gameId, piece.idx, { group }) + _pieceChange(piece.idx) } } } @@ -770,13 +635,13 @@ function handleInput( const tileIdxAtPos = freePieceIdxByPos(gameId, pos) if (tileIdxAtPos >= 0) { - let maxZ = getMaxZIndex(gameId) + 1 + const maxZ = getMaxZIndex(gameId) + 1 changeData(gameId, { maxZ }) _dataChange() const tileIdxs = getGroupedPieceIdxs(gameId, tileIdxAtPos) - setTilesZIndex(gameId, tileIdxs, getMaxZIndex(gameId)) + setPiecesZIndex(gameId, tileIdxs, getMaxZIndex(gameId)) setTilesOwner(gameId, tileIdxs, playerId) - _tileChanges(tileIdxs) + _pieceChanges(tileIdxs) } evtInfo._last_mouse = pos @@ -790,18 +655,18 @@ function handleInput( changePlayer(gameId, playerId, {x, y, ts}) _playerChange() } else { - let tileIdx = getFirstOwnedPieceIdx(gameId, playerId) - if (tileIdx >= 0) { + const pieceIdx = getFirstOwnedPieceIdx(gameId, playerId) + if (pieceIdx >= 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 = getGroupedPieceIdxs(gameId, tileIdx) + const pieceIdxs = getGroupedPieceIdxs(gameId, pieceIdx) let anyOk = Geometry.pointInBounds(pos, getBounds(gameId)) && Geometry.pointInBounds(evtInfo._last_mouse_down, getBounds(gameId)) - for (let idx of tileIdxs) { + for (const idx of pieceIdxs) { const bounds = getPieceBounds(gameId, idx) if (Geometry.pointInBounds(pos, bounds)) { anyOk = true @@ -813,9 +678,9 @@ function handleInput( const diffY = y - evtInfo._last_mouse_down.y const diff = { x: diffX, y: diffY } - moveTilesDiff(gameId, tileIdxs, diff) + movePiecesDiff(gameId, pieceIdxs, diff) - _tileChanges(tileIdxs) + _pieceChanges(pieceIdxs) } } else { // player is just moving map, so no change in position! @@ -835,26 +700,26 @@ function handleInput( evtInfo._last_mouse_down = null - let tileIdx = getFirstOwnedPieceIdx(gameId, playerId) - if (tileIdx >= 0) { + const pieceIdx = getFirstOwnedPieceIdx(gameId, playerId) + if (pieceIdx >= 0) { // drop the tile(s) - let tileIdxs = getGroupedPieceIdxs(gameId, tileIdx) - setTilesOwner(gameId, tileIdxs, 0) - _tileChanges(tileIdxs) + const pieceIdxs = getGroupedPieceIdxs(gameId, pieceIdx) + setTilesOwner(gameId, pieceIdxs, 0) + _pieceChanges(pieceIdxs) // Check if the tile was dropped near the final location - let tilePos = getPiecePos(gameId, tileIdx) - let finalPos = getFinalPiecePos(gameId, tileIdx) + const tilePos = getPiecePos(gameId, pieceIdx) + const finalPos = getFinalPiecePos(gameId, pieceIdx) if (Geometry.pointDistance(finalPos, tilePos) < puzzle.info.snapDistance) { - let diff = Geometry.pointSub(finalPos, tilePos) + const diff = Geometry.pointSub(finalPos, tilePos) // Snap the tile to the final destination - moveTilesDiff(gameId, tileIdxs, diff) - finishTiles(gameId, tileIdxs) - _tileChanges(tileIdxs) + movePiecesDiff(gameId, pieceIdxs, diff) + finishPieces(gameId, pieceIdxs) + _pieceChanges(pieceIdxs) let points = getPlayerPoints(gameId, playerId) if (getScoreMode(gameId) === ScoreMode.FINAL) { - points += tileIdxs.length + points += pieceIdxs.length } else if (getScoreMode(gameId) === ScoreMode.ANY) { points += 1 } else { @@ -877,7 +742,7 @@ function handleInput( otherTileIdx: number, off: Array ): boolean => { - let info = GAMES[gameId].puzzle.info + const info = GAMES[gameId].puzzle.info if (otherTileIdx < 0) { return false } @@ -890,27 +755,27 @@ function handleInput( {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 = getGroupedPieceIdxs(gameId, tileIdx) - moveTilesDiff(gameId, tileIdxs, diff) + const diff = Geometry.pointSub(dstPos, tilePos) + let pieceIdxs = getGroupedPieceIdxs(gameId, tileIdx) + movePiecesDiff(gameId, pieceIdxs, diff) groupTiles(gameId, tileIdx, otherTileIdx) - tileIdxs = getGroupedPieceIdxs(gameId, tileIdx) - const zIndex = getMaxZIndexByTileIdxs(gameId, tileIdxs) - setTilesZIndex(gameId, tileIdxs, zIndex) - _tileChanges(tileIdxs) + pieceIdxs = getGroupedPieceIdxs(gameId, tileIdx) + const zIndex = getMaxZIndexByPieceIdxs(gameId, pieceIdxs) + setPiecesZIndex(gameId, pieceIdxs, zIndex) + _pieceChanges(pieceIdxs) return true } return false } let snapped = false - for (let tileIdxTmp of getGroupedPieceIdxs(gameId, tileIdx)) { - let othersIdxs = getSurroundingTilesByIdx(gameId, tileIdxTmp) + for (const pieceIdxTmp of getGroupedPieceIdxs(gameId, pieceIdx)) { + const othersIdxs = getSurroundingTilesByIdx(gameId, pieceIdxTmp) 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 + check(gameId, pieceIdxTmp, othersIdxs[0], [0, 1]) // top + || check(gameId, pieceIdxTmp, othersIdxs[1], [-1, 0]) // right + || check(gameId, pieceIdxTmp, othersIdxs[2], [0, -1]) // bottom + || check(gameId, pieceIdxTmp, othersIdxs[3], [1, 0]) // left ) { snapped = true break diff --git a/src/common/Rng.ts b/src/common/Rng.ts index af72dfc..349e698 100644 --- a/src/common/Rng.ts +++ b/src/common/Rng.ts @@ -15,7 +15,7 @@ export class Rng { random (min: number, max: number): number { 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; + const n = (this.rand_high >>> 0) / 0xffffffff; return (min + n * (max-min+1))|0; } diff --git a/src/common/Types.ts b/src/common/Types.ts index 279f5f7..f00c3e2 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -1,6 +1,199 @@ +import { Point } from "./Geometry" +import { Rng, RngSerialized } from "./Rng" + // @see https://stackoverflow.com/a/59906630/392905 type ArrayLengthMutationKeys = 'splice' | 'push' | 'pop' | 'shift' | 'unshift' | number type ArrayItems> = T extends Array ? TItems : never export type FixedLengthArray = Pick> & { [Symbol.iterator]: () => IterableIterator< ArrayItems > } + +export type Timestamp = number + +export type Input = any +export type Change = Array + +export type GameEvent = Array + +export type ClientEvent = Array + +export type EncodedPlayer = FixedLengthArray<[ + string, + number, + number, + 0|1, + string|null, + string|null, + string|null, + number, + Timestamp, +]> + +export type EncodedPiece = FixedLengthArray<[ + number, + number, + number, + number, + string|number, + number, +]> + +export type EncodedPieceShape = number + +export type EncodedGame = FixedLengthArray<[ + string, + string, + RngSerialized, + Puzzle, + Array, + Record, + ScoreMode, +]> + +export interface ReplayData { + log: any[], + game: EncodedGame|null +} + +export interface Tag { + id: number + slug: string + title: string +} + +interface GameRng { + obj: Rng + type?: string +} + +export interface Game { + id: string + players: Array + puzzle: Puzzle + evtInfos: Record + scoreMode?: ScoreMode + rng: GameRng +} + +export interface Image { + id: number + filename: string + file: string + url: string + title: string + tags: Array + created: number +} + +export interface GameSettings { + tiles: number + image: Image + scoreMode: ScoreMode +} + +export interface Puzzle { + tiles: Array + data: PuzzleData + info: PuzzleInfo +} + +export interface PuzzleData { + started: number + finished: number + maxGroup: number + maxZ: number +} + +export interface PuzzleDataChange { + started?: number + finished?: number + maxGroup?: number + maxZ?: number +} + +interface PuzzleTable { + width: number + height: number +} + +enum PieceEdge { + Flat = 0, + Out = 1, + In = -1, +} +export interface PieceShape { + top: PieceEdge + bottom: PieceEdge + left: PieceEdge + right: PieceEdge +} + +export interface Piece { + owner: string|number + idx: number + pos: Point + z: number + group: number +} + +export interface PieceChange { + owner?: string|number + idx?: number + pos?: Point + z?: number + group?: number +} + +export interface PuzzleInfo { + table: PuzzleTable + targetTiles: number, + imageUrl: string + + width: number + height: number + tileSize: number + tileDrawSize: number + tileMarginWidth: number + tileDrawOffset: number + snapDistance: number + + tiles: number + tilesX: number + tilesY: number + + shapes: Array +} + +export interface Player { + id: string + x: number + y: number + d: 0|1 + name: string|null + color: string|null + bgcolor: string|null + points: number + ts: Timestamp +} + +export interface PlayerChange { + id?: string + x?: number + y?: number + d?: 0|1 + name?: string|null + color?: string|null + bgcolor?: string|null + points?: number + ts?: Timestamp +} + +export interface EvtInfo { + _last_mouse: Point|null + _last_mouse_down: Point|null +} + +export enum ScoreMode { + FINAL = 0, + ANY = 1, +} diff --git a/src/common/Util.ts b/src/common/Util.ts index c988b9d..020c568 100644 --- a/src/common/Util.ts +++ b/src/common/Util.ts @@ -1,3 +1,4 @@ +import { PuzzleCreationInfo } from '../server/Puzzle' import { EncodedGame, EncodedPiece, @@ -7,19 +8,20 @@ import { Piece, PieceShape, Player, + PuzzleInfo, ScoreMode -} from './GameCommon' +} from './Types' import { Point } from './Geometry' import { Rng } from './Rng' -const slug = (str: string) => { +const slug = (str: string): string => { let tmp = str.toLowerCase() tmp = tmp.replace(/[^a-z0-9]+/g, '-') tmp = tmp.replace(/^-|-$/, '') return tmp } -const pad = (x: any, pad: string) => { +const pad = (x: number, pad: string): string => { const str = `${x}` if (str.length >= pad.length) { return str @@ -43,7 +45,9 @@ export const logger = (...pre: Array) => { } // get a unique id -export const uniqId = () => Date.now().toString(36) + Math.random().toString(36).substring(2) +export const uniqId = (): string => { + return Date.now().toString(36) + Math.random().toString(36).substring(2) +} function encodeShape(data: PieceShape): EncodedPieceShape { /* encoded in 1 byte: @@ -139,11 +143,11 @@ function decodeGame(data: EncodedGame): Game { } } -function coordByTileIdx(info: any, tileIdx: number): Point { +function coordByPieceIdx(info: PuzzleInfo|PuzzleCreationInfo, pieceIdx: number): Point { const wTiles = info.width / info.tileSize return { - x: tileIdx % wTiles, - y: Math.floor(tileIdx / wTiles), + x: pieceIdx % wTiles, + y: Math.floor(pieceIdx / wTiles), } } @@ -151,16 +155,16 @@ const hash = (str: string): number => { let hash = 0 for (let i = 0; i < str.length; i++) { - let chr = str.charCodeAt(i); + const chr = str.charCodeAt(i); hash = ((hash << 5) - hash) + chr; hash |= 0; // Convert to 32bit integer } return hash; } -function asQueryArgs(data: any) { +function asQueryArgs(data: Record): string { const q = [] - for (let k in data) { + for (const k in data) { const pair = [k, data[k]].map(encodeURIComponent) q.push(pair.join('=')) } @@ -187,7 +191,7 @@ export default { encodeGame, decodeGame, - coordByTileIdx, + coordByPieceIdx, asQueryArgs, } diff --git a/src/frontend/Communication.ts b/src/frontend/Communication.ts index d84e629..2b52b77 100644 --- a/src/frontend/Communication.ts +++ b/src/frontend/Communication.ts @@ -1,6 +1,6 @@ "use strict" -import { EncodedGame, ReplayData } from '../common/GameCommon' +import { ClientEvent, EncodedGame, GameEvent, ReplayData } from '../common/Types' import Util, { logger } from '../common/Util' import Protocol from './../common/Protocol' @@ -16,25 +16,29 @@ const CONN_STATE_CONNECTING = 3 // connecting const CONN_STATE_CLOSED = 4 // not connected (closed on purpose) let ws: WebSocket -let changesCallback = (msg: Array) => {} -let connectionStateChangeCallback = (state: number) => {} +let changesCallback = (msg: Array) => { + // empty +} +let connectionStateChangeCallback = (state: number) => { + // empty +} // TODO: change these to something like on(EVT, cb) -function onServerChange(callback: (msg: Array) => void) { +function onServerChange(callback: (msg: Array) => void): void { changesCallback = callback } -function onConnectionStateChange(callback: (state: number) => void) { +function onConnectionStateChange(callback: (state: number) => void): void { connectionStateChangeCallback = callback } let connectionState = CONN_STATE_NOT_CONNECTED -const setConnectionState = (state: number) => { +const setConnectionState = (state: number): void => { if (connectionState !== state) { connectionState = state connectionStateChangeCallback(state) } } -function send(message: Array): void { +function send(message: ClientEvent): void { if (connectionState === CONN_STATE_CONNECTED) { try { ws.send(JSON.stringify(message)) @@ -46,7 +50,7 @@ function send(message: Array): void { let clientSeq: number -let events: Record +let events: Record function connect( address: string, @@ -58,7 +62,7 @@ function connect( setConnectionState(CONN_STATE_CONNECTING) return new Promise(resolve => { ws = new WebSocket(address, clientId + '|' + gameId) - ws.onopen = (e) => { + ws.onopen = () => { setConnectionState(CONN_STATE_CONNECTED) send([Protocol.EV_CLIENT_INIT]) } @@ -82,7 +86,7 @@ function connect( } } - ws.onerror = (e) => { + ws.onerror = () => { setConnectionState(CONN_STATE_DISCONNECTED) throw `[ 2021-05-15 onerror ]` } @@ -116,7 +120,7 @@ function disconnect(): void { events = {} } -function sendClientEvent(evt: any): void { +function sendClientEvent(evt: GameEvent): void { // when sending event, increase number of sent events // and add the event locally clientSeq++; diff --git a/src/frontend/Debug.ts b/src/frontend/Debug.ts index 5580b9a..f8b9ac7 100644 --- a/src/frontend/Debug.ts +++ b/src/frontend/Debug.ts @@ -7,12 +7,12 @@ const log = logger('Debug.js') let _pt = 0 let _mindiff = 0 -const checkpoint_start = (mindiff: number) => { +const checkpoint_start = (mindiff: number): void => { _pt = performance.now() _mindiff = mindiff } -const checkpoint = (label: string) => { +const checkpoint = (label: string): void => { const now = performance.now() const diff = now - _pt if (diff > _mindiff) { diff --git a/src/frontend/Fireworks.ts b/src/frontend/Fireworks.ts index 7dae028..2b84891 100644 --- a/src/frontend/Fireworks.ts +++ b/src/frontend/Fireworks.ts @@ -1,7 +1,6 @@ "use strict" import { Rng } from '../common/Rng' -import Util from '../common/Util' let minVx = -10 let deltaVx = 20 @@ -108,11 +107,11 @@ class Bomb { } class Particle { - px: any - py: any + px: number + py: number vx: number vy: number - color: any + color: string duration: number alive: boolean radius: number @@ -171,7 +170,7 @@ class Controller { }) } - setSpeedParams() { + setSpeedParams(): void { let heightReached = 0 let vy = 0 @@ -188,11 +187,11 @@ class Controller { deltaVx = 2 * vx } - resize() { + resize(): void { this.setSpeedParams() } - init() { + init(): void { this.readyBombs = [] this.explodedBombs = [] this.particles = [] @@ -202,7 +201,7 @@ class Controller { } } - update() { + update(): void { if (Math.random() * 100 < percentChanceNewBomb) { this.readyBombs.push(new Bomb(this.rng)) } @@ -250,7 +249,7 @@ class Controller { this.particles = aliveParticles } - render() { + render(): void { this.ctx.beginPath() this.ctx.fillStyle = 'rgba(0, 0, 0, 0.1)' // Ghostly effect this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height) diff --git a/src/frontend/PuzzleGraphics.ts b/src/frontend/PuzzleGraphics.ts index c4c7716..6debdbe 100644 --- a/src/frontend/PuzzleGraphics.ts +++ b/src/frontend/PuzzleGraphics.ts @@ -3,22 +3,22 @@ import Geometry, { Rect } from '../common/Geometry' import Graphics from './Graphics' import Util, { logger } from './../common/Util' -import { Puzzle, PuzzleInfo, PieceShape } from './../common/GameCommon' +import { Puzzle, PuzzleInfo, PieceShape, EncodedPiece } from './../common/GameCommon' const log = logger('PuzzleGraphics.js') async function createPuzzleTileBitmaps( img: ImageBitmap, - tiles: Array, + pieces: EncodedPiece[], info: PuzzleInfo ): Promise> { log.log('start createPuzzleTileBitmaps') - var tileSize = info.tileSize - var tileMarginWidth = info.tileMarginWidth - var tileDrawSize = info.tileDrawSize - var tileRatio = tileSize / 100.0 + const tileSize = info.tileSize + const tileMarginWidth = info.tileMarginWidth + const tileDrawSize = info.tileDrawSize + const tileRatio = tileSize / 100.0 - var curvyCoords = [ + const curvyCoords = [ 0, 0, 40, 15, 37, 5, 37, 5, 40, 0, 38, -5, 38, -5, 20, -20, 50, -20, @@ -27,7 +27,7 @@ async function createPuzzleTileBitmaps( 63, 5, 65, 15, 100, 0 ]; - const bitmaps: Array = new Array(tiles.length) + const bitmaps: Array = new Array(pieces.length) const paths: Record = {} function pathForShape(shape: PieceShape) { @@ -65,9 +65,9 @@ async function createPuzzleTileBitmaps( } if (shape.bottom !== 0) { for (let i = 0; i < curvyCoords.length / 6; i++) { - let p1 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 0] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 1] * tileRatio }) - let p2 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 2] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 3] * tileRatio }) - let p3 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 4] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 5] * tileRatio }) + const p1 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 0] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 1] * tileRatio }) + const p2 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 2] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 3] * tileRatio }) + const p3 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 4] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 5] * tileRatio }) path.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); } } else { @@ -75,9 +75,9 @@ async function createPuzzleTileBitmaps( } if (shape.left !== 0) { for (let i = 0; i < curvyCoords.length / 6; i++) { - let p1 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 1] * tileRatio, y: curvyCoords[i * 6 + 0] * tileRatio }) - let p2 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 3] * tileRatio, y: curvyCoords[i * 6 + 2] * tileRatio }) - let p3 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 5] * tileRatio, y: curvyCoords[i * 6 + 4] * tileRatio }) + const p1 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 1] * tileRatio, y: curvyCoords[i * 6 + 0] * tileRatio }) + const p2 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 3] * tileRatio, y: curvyCoords[i * 6 + 2] * tileRatio }) + const p3 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 5] * tileRatio, y: curvyCoords[i * 6 + 4] * tileRatio }) path.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); } } else { @@ -93,10 +93,10 @@ async function createPuzzleTileBitmaps( const c2 = Graphics.createCanvas(tileDrawSize, tileDrawSize) const ctx2 = c2.getContext('2d') as CanvasRenderingContext2D - for (const t of tiles) { - const tile = Util.decodePiece(t) - const srcRect = srcRectByIdx(info, tile.idx) - const path = pathForShape(Util.decodeShape(info.shapes[tile.idx])) + for (const p of pieces) { + const piece = Util.decodePiece(p) + const srcRect = srcRectByIdx(info, piece.idx) + const path = pathForShape(Util.decodeShape(info.shapes[piece.idx])) ctx.clearRect(0, 0, tileDrawSize, tileDrawSize) @@ -195,7 +195,7 @@ async function createPuzzleTileBitmaps( ctx2.restore() ctx.drawImage(c2, 0, 0) - bitmaps[tile.idx] = await createImageBitmap(c) + bitmaps[piece.idx] = await createImageBitmap(c) } log.log('end createPuzzleTileBitmaps') @@ -203,7 +203,7 @@ async function createPuzzleTileBitmaps( } function srcRectByIdx(puzzleInfo: PuzzleInfo, idx: number): Rect { - const c = Util.coordByTileIdx(puzzleInfo, idx) + const c = Util.coordByPieceIdx(puzzleInfo, idx) return { x: c.x * puzzleInfo.tileSize, y: c.y * puzzleInfo.tileSize, diff --git a/src/frontend/components/NewGameDialog.vue b/src/frontend/components/NewGameDialog.vue index 0eff20a..04a7eff 100644 --- a/src/frontend/components/NewGameDialog.vue +++ b/src/frontend/components/NewGameDialog.vue @@ -36,7 +36,7 @@