diff --git a/build/public/assets/index.2f18bf13.js b/build/public/assets/index.2f18bf13.js deleted file mode 100644 index 1d09f6b..0000000 --- a/build/public/assets/index.2f18bf13.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as n,w as o,b as l,r as i,o as s,e as a,t as r,F as d,f as c,g as u,v as p,h,i as g,j as y,k as m,l as f,m as w,n as v}from"./vendor.00b608ff.js";var x=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const b={id:"app"},A={key:0,class:"nav"},z=a("Index"),T=a("New game");x.render=function(e,a,r,d,c,u){const p=i("router-link"),h=i("router-view");return s(),t("div",b,[e.showNav?(s(),t("ul",A,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[z])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[T])),_:1})])])):l("",!0),n(h)])};const S=864e5,C=e=>{const t=Math.floor(e/S);e%=S;const n=Math.floor(e/36e5);e%=36e5;const o=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${o}m ${Math.floor(e/1e3)}s`};var k=1e3,I=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},P=(e,t)=>C(t-e),_=C,D=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",o=e,l=t||I();return`${n} ${P(o,l)}`}}});const B={class:"game-info-text"},O=n("br",null,null,-1),E=n("br",null,null,-1),M=n("br",null,null,-1);D.render=function(e,d,c,u,p,h){const g=i("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(g,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",B,[a(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),O,a(" πŸ‘₯ "+r(e.game.players),1),E,a(" "+r(e.time(e.game.started,e.game.finished)),1),M])])),_:1},8,["to"]),l("",!0)],4)};var N=e({components:{GameTeaser:D},data:()=>({gamesRunning:[],gamesFinished:[]}),async created(){const e=await fetch("/api/index-data"),t=await e.json();this.gamesRunning=t.gamesRunning,this.gamesFinished=t.gamesFinished}});const U=n("h1",null,"Running games",-1),R=n("h1",null,"Finished games",-1);function $(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function G(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}N.render=function(e,o,l,a,r,u){const p=i("game-teaser");return s(),t("div",null,[U,(s(!0),t(d,null,c(e.gamesRunning,((e,o)=>(s(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128)),R,(s(!0),t(d,null,c(e.gamesFinished,((e,o)=>(s(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128))])};var V={pointSub:function(e,t){return{x:e.x-t.x,y:e.y-t.y}},pointAdd:function(e,t){return{x:e.x+t.x,y:e.y+t.y}},pointDistance:$,pointInBounds:function(e,t){return e.x>=t.x&&e.x<=t.x+t.w&&e.y>=t.y&&e.y<=t.y+t.h},rectCenter:G,rectMoved:function(e,t,n){return{x:e.x+t,y:e.y+n,w:e.w,h:e.h}},rectCenterDistance:function(e,t){return $(G(e),G(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};var j=1,W=4,F=5,H=2,L=3,Q=6,Y=2,q=4,Z=3,K=9,J=1,X=2,ee=3,te=4,ne=5,oe=6,le=7,ie=8,se=10,ae=1,re=2,de=3;class ce{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){return this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295,e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}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 ce(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const ue=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},pe=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=ue(o.getHours(),"00"),i=ue(o.getMinutes(),"00"),s=ue(o.getSeconds(),"00");console[t](`${l}:${i}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var he={hash:e=>{let t=0;for(let n=0;nDate.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return"number"==typeof e?e:e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return"number"!=typeof e?e:{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodeTile:function(e){return Array.isArray(e)?e:[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodeTile:function(e){return Array.isArray(e)?{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}:e},encodePlayer:function(e){return Array.isArray(e)?e:[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return Array.isArray(e)?{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}:e},encodeGame:function(e){return Array.isArray(e)?e:[e.id,e.rng.type,ce.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode]},decodeGame:function(e){return Array.isArray(e)?{id:e[0],rng:{type:e[1],obj:ce.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}:e},coordByTileIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}}};const ge={};function ye(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}function me(e,t){let n=0;for(let o of ge[e].players){if(he.decodePlayer(o).id===t)return n;n++}return-1}function fe(e,t){let n=me(e,t);return he.decodePlayer(ge[e].players[n])}function we(e,t,n){let o=me(e,t);-1===o?ge[e].players.push(he.encodePlayer(n)):ge[e].players[o]=he.encodePlayer(n)}function ve(e,t){return-1!==me(e,t)}function xe(e){return ge[e]?ge[e].players.map(he.decodePlayer):[]}function be(e){return ge[e].puzzle.tiles.length}function Ae(e){return ge[e].scoreMode||0}function ze(e){return Te(e)===be(e)}function Te(e){let t=0;for(let n of ge[e].puzzle.tiles)-1===he.decodeTile(n).owner&&t++;return t}function Se(e,t,n){const o=fe(e,t);for(let l of Object.keys(n))o[l]=n[l];we(e,t,o)}function Ce(e,t){for(let n of Object.keys(t))ge[e].puzzle.data[n]=t[n]}function ke(e,t,n){for(let o of Object.keys(n)){const l=he.decodeTile(ge[e].puzzle.tiles[t]);l[o]=n[o],ge[e].puzzle.tiles[t]=he.encodeTile(l)}}const Ie=(e,t)=>he.decodeTile(ge[e].puzzle.tiles[t]),Pe=(e,t)=>Ie(e,t).group,_e=(e,t)=>{const n=ge[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=ge[e].puzzle.info,o=he.coordByTileIdx(n,t),l=o.x*n.tileSize,i=o.y*n.tileSize;return{x:l,y:i}}(e,t);return V.pointAdd(o,l)},De=(e,t)=>Ie(e,t).pos,Be=e=>{const t=Qe(e),n=Ye(e),o=Math.round(t/4),l=Math.round(n/4);return{x:0-o,y:0-l,w:t+2*o,h:n+2*l}},Oe=(e,t)=>{const n=Ue(e),o=Ie(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Ee=(e,t)=>Ie(e,t).z,Me=(e,t)=>{for(let n of ge[e].puzzle.tiles){const e=he.decodeTile(n);if(e.owner===t)return e.idx}return-1},Ne=e=>ge[e].puzzle.info.tileDrawSize,Ue=e=>ge[e].puzzle.info.tileSize,Re=e=>ge[e].puzzle.data.maxGroup,$e=e=>ge[e].puzzle.data.maxZ;function Ge(e,t){const n=ge[e].puzzle.info,o=he.coordByTileIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Ve=(e,t,n)=>{for(let o of t)ke(e,o,{z:n})},je=(e,t,n)=>{const o=De(e,t);ke(e,t,{pos:V.pointAdd(o,n)})},We=(e,t,n)=>{const o=Ne(e),l=Be(e),i=n;for(let s of t){const t=Ie(e,s);t.pos.x+n.xl.x+l.w&&(i.x=Math.min(l.x+l.w-t.pos.x+o,i.x)),t.pos.y+n.yl.y+l.h&&(i.y=Math.min(l.y+l.h-t.pos.y+o,i.y))}for(let s of t)je(e,s,i)},Fe=(e,t,n)=>{for(let o of t)ke(e,o,{owner:n})};function He(e,t){const n=ge[e].puzzle.tiles,o=he.decodeTile(n[t]),l=[];if(o.group)for(let i of n){const e=he.decodeTile(i);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const Le=(e,t)=>{const n=fe(e,t);return n?n.points:null},Qe=e=>ge[e].puzzle.info.table.width,Ye=e=>ge[e].puzzle.info.table.height;var qe={__createPlayerObject:ye,setGame:function(e,t){ge[e]=t},exists:function(e){return!!ge[e]||!1},playerExists:ve,getActivePlayers:function(e,t){const n=t-30*k;return xe(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*k;return xe(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){ve(e,t)?Se(e,t,{ts:n}):we(e,t,ye(t,n))},getFinishedTileCount:Te,getTileCount:be,getImageUrl:function(e){return ge[e].puzzle.info.imageUrl},setImageUrl:function(e,t){ge[e].puzzle.info.imageUrl=t},get:function(e){return ge[e]},getAllGames:function(){return Object.values(ge).sort(((e,t)=>ze(e.id)===ze(t.id)?t.puzzle.data.started-e.puzzle.data.started:ze(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=fe(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=fe(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=fe(e,t);return n?n.name:null},getPlayerIndexById:me,getPlayerIdByIndex:function(e,t){return ge[e].players.length>t?he.decodePlayer(ge[e].players[t]).id:null},changePlayer:Se,setPlayer:we,setTile:function(e,t,n){ge[e].puzzle.tiles[t]=he.encodeTile(n)},setPuzzleData:function(e,t){ge[e].puzzle.data=t},getTableWidth:Qe,getTableHeight:Ye,getPuzzle:e=>ge[e].puzzle,getRng:e=>ge[e].rng.obj,getPuzzleWidth:e=>ge[e].puzzle.info.width,getPuzzleHeight:e=>ge[e].puzzle.info.height,getTilesSortedByZIndex:function(e){return ge[e].puzzle.tiles.map(he.decodeTile).sort(((e,t)=>e.z-t.z))},getFirstOwnedTile:(e,t)=>{const n=Me(e,t);return n<0?null:ge[e].puzzle.tiles[n]},getTileDrawOffset:e=>ge[e].puzzle.info.tileDrawOffset,getTileDrawSize:Ne,getFinalTilePos:_e,getStartTs:e=>ge[e].puzzle.data.started,getFinishTs:e=>ge[e].puzzle.data.finished,handleInput:function(e,t,n,o){const l=ge[e].puzzle,i=function(e,t){return t in ge[e].evtInfos?ge[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],a=()=>{s.push([ae,l.data])},r=t=>{s.push([re,he.encodeTile(Ie(e,t))])},d=e=>{for(const t of e)r(t)},c=()=>{s.push([de,he.encodePlayer(fe(e,t))])},u=n[0];if(u===oe){const l=n[1];Se(e,t,{bgcolor:l,ts:o}),c()}else if(u===le){const l=n[1];Se(e,t,{color:l,ts:o}),c()}else if(u===ie){const l=`${n[1]}`.substr(0,16);Se(e,t,{name:l,ts:o}),c()}else if(u===J){const l={x:n[1],y:n[2]};Se(e,t,{d:1,ts:o}),c(),i._last_mouse_down=l;const s=((e,t)=>{let n=ge[e].puzzle.info,o=ge[e].puzzle.tiles,l=-1,i=-1;for(let s=0;sl)&&(l=e.z,i=s)}return i})(e,l);if(s>=0){let n=$e(e)+1;Ce(e,{maxZ:n}),a();const o=He(e,s);Ve(e,o,$e(e)),Fe(e,o,t),d(o)}i._last_mouse=l}else if(u===ee){const l=n[1],s=n[2],a={x:l,y:s};if(null===i._last_mouse_down)Se(e,t,{x:l,y:s,ts:o}),c();else{let n=Me(e,t);if(n>=0){Se(e,t,{x:l,y:s,ts:o}),c();const r=He(e,n);let u=V.pointInBounds(a,Be(e))&&V.pointInBounds(i._last_mouse_down,Be(e));for(let t of r){const n=Oe(e,t);if(V.pointInBounds(a,n)){u=!0;break}}if(u){const t=l-i._last_mouse_down.x,n=s-i._last_mouse_down.y;We(e,r,{x:t,y:n}),d(r)}}else Se(e,t,{ts:o}),c();i._last_mouse_down=a}i._last_mouse=a}else if(u===X){const s={x:n[1],y:n[2]},u=0;i._last_mouse_down=null;let p=Me(e,t);if(p>=0){let n=He(e,p);Fe(e,n,0),d(n);let i=De(e,p),s=_e(e,p);if(V.pointDistance(s,i){for(let n of t)ke(e,n,{owner:-1,z:1})})(e,n),d(n);let r=Le(e,t);0===Ae(e)?r+=n.length:1===Ae(e)&&(r+=1),Se(e,t,{d:u,ts:o,points:r}),c(),Te(e)===be(e)&&(Ce(e,{finished:o}),a())}else{const n=(e,t,n,o)=>{let l=ge[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=Pe(e,t),l=Pe(e,n);return o&&o===l})(e,t,n))return!1;const i=De(e,t),s=V.pointAdd(De(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(V.pointDistance(i,s){const o=ge[e].puzzle.tiles,l=Pe(e,t),i=Pe(e,n);let s;const d=[];l&&d.push(l),i&&d.push(i),l?s=l:i?s=i:(Ce(e,{maxGroup:Re(e)+1}),a(),s=Re(e));if(ke(e,t,{group:s}),r(t),ke(e,n,{group:s}),r(n),d.length>0)for(const a of o){const t=he.decodeTile(a);d.includes(t.group)&&(ke(e,t.idx,{group:s}),r(t.idx))}})(e,t,n),l=He(e,t);const c=((e,t)=>{let n=0;for(let o of t){let t=Ee(e,o);t>n&&(n=t)}return n})(e,l);return Ve(e,l,c),d(l),!0}return!1};let l=!1;for(let t of He(e,p)){let o=Ge(e,t);if(n(e,t,o[0],[0,1])||n(e,t,o[1],[-1,0])||n(e,t,o[2],[0,-1])||n(e,t,o[3],[1,0])){l=!0;break}}if(l&&1===Ae(e)){const n=Le(e,t)+1;Se(e,t,{d:u,ts:o,points:n}),c()}else Se(e,t,{d:u,ts:o}),c()}}else Se(e,t,{d:u,ts:o}),c();i._last_mouse=s}else if(u===te){const l=n[1],s=n[2];Se(e,t,{x:l,y:s,ts:o}),c(),i._last_mouse={x:l,y:s}}else if(u===ne){const l=n[1],s=n[2];Se(e,t,{x:l,y:s,ts:o}),c(),i._last_mouse={x:l,y:s}}else Se(e,t,{ts:o}),c();return function(e,t,n){ge[e].evtInfos[t]=n}(e,t,i),s},SCORE_MODE_FINAL:0,SCORE_MODE_ANY:1},Ze=e({name:"upload",props:{accept:String,label:String},methods:{async upload(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const o=new FormData;o.append("file",n,n.name);const l=await fetch("/upload",{method:"post",body:o}),i=await l.json();this.$emit("uploaded",i)}}});const Ke={class:"btn"};Ze.render=function(e,o,l,i,a,d){return s(),t("label",null,[n("input",{type:"file",style:{display:"none"},onChange:o[1]||(o[1]=(...t)=>e.upload&&e.upload(...t)),accept:e.accept},null,40,["accept"]),n("span",Ke,r(e.label||"Upload File"),1)])};var Je=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},methods:{onClick(){this.$emit("click")}}});Je.render=function(e,n,o,l,i,a){return s(),t("div",{class:"imageteaser",style:e.style,onClick:n[1]||(n[1]=(...t)=>e.onClick&&e.onClick(...t))},null,4)};var Xe=e({name:"new-game-dialog",components:{Upload:Ze,ImageTeaser:Je},props:{images:Array},emits:{newGame:null},data:()=>({tiles:1e3,image:"",scoreMode:qe.SCORE_MODE_ANY}),methods:{mediaImgUploaded(e){this.image=e.image},canStartNewGame(){return!!(this.tilesInt&&this.image&&[0,1].includes(this.scoreModeInt))},onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const et=n("h1",null,"New game",-1),tt=n("td",null,[n("label",null,"Pieces: ")],-1),nt=n("td",null,[n("label",null,"Scoring: ")],-1),ot=a(" Any (Score when pieces are connected to each other or on final location)"),lt=n("br",null,null,-1),it=a(" Final (Score when pieces are put to their final location)"),st=n("td",null,[n("label",null,"Image: ")],-1),at={key:0},rt=a(" or "),dt={key:1},ct=a(" (or select from below) "),ut={colspan:"2"},pt=n("h1",null,"Image lib",-1);Xe.render=function(e,o,l,a,r,g){const y=i("upload"),m=i("image-teaser");return s(),t("div",null,[et,n("table",null,[n("tr",null,[tt,n("td",null,[u(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[nt,n("td",null,[n("label",null,[u(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[h,e.scoreMode]]),ot]),lt,n("label",null,[u(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[h,e.scoreMode]]),it])])]),n("tr",null,[st,n("td",null,[e.image?(s(),t("span",at,[n("img",{src:e.image.url,style:{width:"150px"}},null,8,["src"]),rt,n(y,{onUploaded:o[4]||(o[4]=t=>e.mediaImgUploaded(t)),accept:"image/*",label:"Upload an image"})])):(s(),t("span",dt,[n(y,{onUploaded:o[5]||(o[5]=t=>e.mediaImgUploaded(t)),accept:"image/*",label:"Upload an image"}),ct]))])]),n("tr",null,[n("td",ut,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[6]||(o[6]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))},"Start new game",8,["disabled"])])])]),pt,n("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(m,{image:n,onClick:t=>e.image=n,key:o},null,8,["image","onClick"])))),128))])])};var ht=e({components:{NewGameDialog:Xe},data:()=>({images:[]}),async created(){const e=await fetch("/api/newgame-data"),t=await e.json();this.images=t.images},methods:{async onNewGame(e){const t=await fetch("/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});ht.render=function(e,o,l,a,r,d){const c=i("new-game-dialog");return s(),t("div",null,[n(c,{images:e.images,onNewGame:e.onNewGame},null,8,["images","onNewGame"])])};var gt=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 yt={class:"scores"},mt=n("div",null,"Scores",-1),ft=n("td",null,"⚑",-1),wt=n("td",null,"πŸ’€",-1);gt.render=function(e,o,l,i,a,u){return s(),t("div",yt,[mt,n("table",null,[(s(!0),t(d,null,c(e.actives,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[ft,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(s(!0),t(d,null,c(e.idles,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[wt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var vt=e({name:"puzzle-status",props:{finished:{type:Boolean,required:!0},duration:{type:Number,required:!0},piecesDone:{type:Number,required:!0},piecesTotal:{type:Number,required:!0}},computed:{icon(){return this.finished?"🏁":"⏳"},durationStr(){return _(this.duration)}}});const xt={class:"timer"};vt.render=function(e,o,l,i,a,d){return s(),t("div",xt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),g(e.$slots,"default")])};var bt=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 At=n("td",null,[n("label",null,"Background: ")],-1),zt=n("td",null,[n("label",null,"Color: ")],-1),Tt=n("td",null,[n("label",null,"Name: ")],-1);bt.render=function(e,o,l,i,a,r){return s(),t("div",{class:"overlay transparent",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[4]||(o[4]=y((()=>{}),["stop"]))},[n("tr",null,[At,n("td",null,[u(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[zt,n("td",null,[u(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[Tt,n("td",null,[u(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])])])])};var St=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const Ct={class:"preview"};St.render=function(e,o,l,i,a,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",Ct,[n("div",{class:"img",style:e.previewStyle},null,4)])])};const kt=pe("Communication.js");let It,Pt=e=>{},_t=e=>{};let Dt=0;const Bt=e=>{Dt!==e&&(Dt=e,_t(e))};function Ot(e){if(2===Dt)try{It.send(JSON.stringify(e))}catch(t){kt.info("unable to send message.. maybe because ws is invalid?")}}let Et,Mt;var Nt={connect:function(e,t,n){return Et=0,Mt={},Bt(3),new Promise((o=>{It=new WebSocket(e,n+"|"+t),It.onopen=e=>{Bt(2),Ot([L])},It.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===W){const e=t[1];o(e)}else{if(l!==j)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&Mt[o])return void delete Mt[o];Pt(t)}}},It.onerror=e=>{throw Bt(1),"[ 2021-05-15 onerror ]"},It.onclose=e=>{4e3===e.code||1001===e.code?Bt(4):Bt(1)}}))},connectReplay:function(e,t,n){return Et=0,Mt={},Bt(3),new Promise((o=>{It=new WebSocket(e,n+"|"+t),It.onopen=e=>{Bt(2),Ot([Q])},It.onmessage=e=>{const t=JSON.parse(e.data),n=t[0];if(n!==F)throw`[ 2021-05-09 invalid connectReplay msgType ${n} ]`;{const e=t[1],n=t[2];o({game:e,log:n})}},It.onerror=e=>{throw Bt(1),"[ 2021-05-15 onerror ]"},It.onclose=e=>{4e3===e.code||1001===e.code?Bt(4):Bt(1)}}))},disconnect:function(){It&&It.close(4e3),Et=0,Mt={}},sendClientEvent:function(e){Et++,Mt[Et]=e,Ot([H,Et,Mt[Et]])},onServerChange:function(e){Pt=e},onConnectionStateChange:function(e){_t=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},Ut=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Nt.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Nt.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const Rt={key:0,class:"overlay connection-lost"},$t={key:0,class:"overlay-content"},Gt=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Vt={key:1,class:"overlay-content"},jt=n("div",null,"Connecting...",-1);Ut.render=function(e,o,i,a,r,d){return e.show?(s(),t("div",Rt,[e.lostConnection?(s(),t("div",$t,[Gt,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",Vt,[jt])):l("",!0)])):l("",!0)};var Wt=e({name:"help-overlay",emits:{bgclick:null}});const Ft=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),a("/"),n("kbd",null,"↑"),a("/πŸ–±οΈ")])])],-1),Ht=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),a("/"),n("kbd",null,"↓"),a("/πŸ–±οΈ")])])],-1),Lt=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),a("/"),n("kbd",null,"←"),a("/πŸ–±οΈ")])])],-1),Qt=n("tr",null,[n("td",null,"➑️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),a("/"),n("kbd",null,"β†’"),a("/πŸ–±οΈ")])])],-1),Yt=n("tr",null,[n("td"),n("td",null,[n("div",null,[a("Move faster by holding "),n("kbd",null,"Shift")])])],-1),qt=n("tr",null,[n("td",null,"πŸ”+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),a("/πŸ–±οΈ-Wheel")])])],-1),Zt=n("tr",null,[n("td",null,"πŸ”- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),a("/πŸ–±οΈ-Wheel")])])],-1),Kt=n("tr",null,[n("td",null,"πŸ–ΌοΈ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Jt=n("tr",null,[n("td",null,"πŸ§©βœ”οΈ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Xt=n("tr",null,[n("td",null,"πŸ§©β“ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1);Wt.render=function(e,o,l,i,a,r){return s(),t("div",{class:"overlay transparent",onClick:o[2]||(o[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:o[1]||(o[1]=y((()=>{}),["stop"]))},[Ft,Ht,Lt,Qt,Yt,qt,Zt,Kt,Jt,Xt])])};var en=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),tn=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="}),on=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function ln(){let e=0,t=0,n=1;const o=(o,l)=>{e+=o/n,t+=l/n},l=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},i=o=>({x:o.x/n-e,y:o.y/n-t}),s=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),a=e=>({w:e.w*n,h:e.h*n});return{move:o,canZoom:e=>n!=l(e),zoom:(e,t)=>((e,t)=>{if(n==e)return!1;const l=1-n/e;return o(-t.x*l,-t.y*l),n=e,!0})(l(e),t),worldToViewport:e=>{const{x:t,y:n}=s(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:s,worldDimToViewport:e=>{const{w:t,h:n}=a(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:a,viewportToWorld:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:i}}function sn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var an={createCanvas:sn,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=sn(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorize:async function(e,t,n){const o=sn(e.width,e.height),l=o.getContext("2d");return l.save(),l.drawImage(t,0,0),l.fillStyle=n,l.globalCompositeOperation="source-in",l.fillRect(0,0,t.width,t.height),l.restore(),l.save(),l.globalCompositeOperation="destination-over",l.drawImage(e,0,0),l.restore(),await createImageBitmap(o)}};const rn=pe("Debug.js");let dn=0,cn=0;var un=e=>{dn=performance.now(),cn=e},pn=e=>{const t=performance.now(),n=t-dn;n>cn&&rn.log(e+": "+n),dn=t};const hn=pe("PuzzleGraphics.js");function gn(e,t){const n=he.coordByTileIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var yn={loadPuzzleBitmaps:async function(e){const t=await an.loadImageToBitmap(e.info.imageUrl),n=await an.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){hn.log("start createPuzzleTileBitmaps");var o=n.tileSize,l=n.tileMarginWidth,i=n.tileDrawSize,s=o/100,a=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0];const r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,i={x:l,y:l},r=V.pointAdd(i,{x:o,y:0}),c=V.pointAdd(r,{x:0,y:o}),u=V.pointSub(c,{x:o,y:0});if(n.moveTo(i.x,i.y),0!==e.top)for(let o=0;o=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;wn=t/2,vn=t-wn;const n=1/4*this.canvas.width/(t/2);mn=-n,fn=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(!g[t]){const n=e.d?s:a;if(e.color){const o=e.d?r:d;g[t]=await an.colorize(n,o,e.color)}else g[t]=n}return g[t]},m=function(e,t){return t.width=window.innerWidth,t.height=window.innerHeight,e.appendChild(t),window.addEventListener("resize",(()=>{t.width=window.innerWidth,t.height=window.innerHeight,Cn=!0})),t}(l,an.createCanvas()),f={log:[],logIdx:0,speeds:[.5,1,2,5,10,20,50],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0};Nt.onConnectionStateChange((e=>{i.setConnectionState(e)}));let w=()=>0;const v=async()=>{if("play"===o){const o=await Nt.connect(n,e,t),l=he.decodeGame(o);qe.setGame(l.id,l),w=()=>I()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const o=await Nt.connectReplay(n,e,t),l=he.decodeGame(o.game);qe.setGame(l.id,l),f.log=o.log,f.lastRealTs=I(),f.gameStartTs=parseInt(f.log[0][f.log[0].length-2],10),f.lastGameTs=f.gameStartTs,w=()=>f.lastGameTs}}Cn=!0};await v();const x=qe.getTileDrawOffset(e),b=qe.getTileDrawSize(e),A=qe.getPuzzleWidth(e),z=qe.getPuzzleHeight(e),T=qe.getTableWidth(e),S=qe.getTableHeight(e),C={x:(T-A)/2,y:(S-z)/2},k={w:A,h:z},P={w:b,h:b},_=await yn.loadPuzzleBitmaps(qe.getPuzzle(e)),D=new An(m,qe.getRng(e));D.init();const B=m.getContext("2d");m.classList.add("loaded");const O=ln();O.move(-(T-m.width)/2,-(S-m.height)/2);const E=function(e,t,n){let o=[],l=!0,i=!1,s=!1,a=!1,r=!1,d=!1,c=!1,u=!1;const p=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>p(e.offsetX,e.offsetY),g=()=>p(e.width/2,e.height/2),y=(e,t)=>{l&&("Shift"===t.key?u=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?a=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?r=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?i=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?s=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};e.addEventListener("mousedown",(e=>{0===e.button&&m([J,...h(e)])})),e.addEventListener("mouseup",(e=>{0===e.button&&m([X,...h(e)])})),e.addEventListener("mousemove",(e=>{m([ee,...h(e)])})),e.addEventListener("wheel",(e=>{if(n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?te:ne;m([t,...h(e)])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{l&&(" "===e.key&&m([se]),"F"!==e.key&&"f"!==e.key||(Tn=!Tn,Cn=!0),"G"!==e.key&&"g"!==e.key||(Sn=!Sn,Cn=!0))}));const m=e=>{o.push(e)};return{addEvent:m,consumeAll:()=>{if(0===o.length)return[];const e=o.slice();return o=[],e},createKeyEvents:()=>{const e=u?20:10,t=(i?e:0)-(s?e:0),o=(a?e:0)-(r?e:0);0===t&&0===o||m([K,t,o]),d&&c||(d?n.canZoom("in")&&m([te,...g()]):c&&n.canZoom("out")&&m([ne,...g()]))},setHotkeys:e=>{l=e}}}(m,window,O),M=qe.getImageUrl(e),N=()=>{const t=qe.getStartTs(e),n=qe.getFinishTs(e),o=w();i.setFinished(!!n),i.setDuration((n||o)-t)};N(),i.setPiecesDone(qe.getFinishedTileCount(e)),i.setPiecesTotal(qe.getTileCount(e));const U=w();i.setActivePlayers(qe.getActivePlayers(e,U)),i.setIdlePlayers(qe.getIdlePlayers(e,U));const R=!!qe.getFinishTs(e);let $=R;const G=()=>$&&!R,V=()=>qe.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",j=()=>{i.setReplaySpeed&&i.setReplaySpeed(f.speeds[f.speedIdx]),i.setReplayPaused&&i.setReplayPaused(f.paused)};if("play"===o?setInterval(N,1e3):"replay"===o&&j(),"play"===o)Nt.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,i]of o)switch(l){case de:{const n=he.decodePlayer(i);n.id!==t&&(qe.setPlayer(e,n.id,n),Cn=!0)}break;case re:{const t=he.decodeTile(i);qe.setTile(e,t.idx,t),Cn=!0}break;case ae:qe.setPuzzleData(e,i),Cn=!0}$=!!qe.getFinishTs(e)}));else if("replay"===o){let t=setInterval((()=>{const n=I();if(f.paused)return void(f.lastRealTs=n);const o=(n-f.lastRealTs)*f.speeds[f.speedIdx],l=f.lastGameTs+o;for(;;){if(f.paused)break;const n=f.logIdx+1;if(n>=f.log.length){clearInterval(t);break}const o=f.log[n],i=f.gameStartTs+o[o.length-1];if(i>l)break;const s=o.slice();if(s[0]===Y){const t=s[1];qe.addPlayer(e,t,i),Cn=!0}else if(s[0]===q){const t=qe.getPlayerIdByIndex(e,s[1]);qe.addPlayer(e,t,i),Cn=!0}else if(s[0]===Z){const t=qe.getPlayerIdByIndex(e,s[1]),n=s[2];qe.handleInput(e,t,n,i),Cn=!0}f.logIdx=n}f.lastRealTs=n,f.lastGameTs=l,N()}),50)}let W=null;return(e=>{const t=e.fps||60,n=e.slow||1,o=e.update,l=e.render,i=window.requestAnimationFrame,s=1/t,a=n*s;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>a;)d-=a,o(s);l(d/n),c=r,i(u)};i(u)})({update:()=>{E.createKeyEvents();for(const n of E.consumeAll())if("play"===o){const o=n[0];if(o===K){const e=n[1],t=n[2];Cn=!0,O.move(e,t)}else if(o===ee){if(W&&!qe.getFirstOwnedTile(e,t)){const e={x:n[1],y:n[2]},t=O.worldToViewport(e),o=Math.round(t.x-W.x),l=Math.round(t.y-W.y);Cn=!0,O.move(o,l),W=t}}else if(o===J){const e={x:n[1],y:n[2]};W=O.worldToViewport(e)}else if(o===X)W=null;else if(o===te){const e={x:n[1],y:n[2]};Cn=!0,O.zoom("in",O.worldToViewport(e))}else if(o===ne){const e={x:n[1],y:n[2]};Cn=!0,O.zoom("out",O.worldToViewport(e))}else o===se&&i.togglePreview();const l=w();qe.handleInput(e,t,n,l).length>0&&(Cn=!0),Nt.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===K){const e=n[1],t=n[2];Cn=!0,O.move(e,t)}else if(e===ee){if(W){const e={x:n[1],y:n[2]},t=O.worldToViewport(e),o=Math.round(t.x-W.x),l=Math.round(t.y-W.y);Cn=!0,O.move(o,l),W=t}}else if(e===J){const e={x:n[1],y:n[2]};W=O.worldToViewport(e)}else if(e===X)W=null;else if(e===te){const e={x:n[1],y:n[2]};Cn=!0,O.zoom("in",O.worldToViewport(e))}else if(e===ne){const e={x:n[1],y:n[2]};Cn=!0,O.zoom("out",O.worldToViewport(e))}else e===se&&i.togglePreview()}$=!!qe.getFinishTs(e),G()&&(D.update(),Cn=!0)},render:async()=>{if(!Cn)return;const n=w();let l,s,a;window.DEBUG&&un(0),B.fillStyle=V(),B.fillRect(0,0,m.width,m.height),window.DEBUG&&pn("clear done"),l=O.worldToViewportRaw(C),s=O.worldDimToViewportRaw(k),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&pn("board done");const r=qe.getTilesSortedByZIndex(e);window.DEBUG&&pn("get tiles done"),s=O.worldDimToViewportRaw(P);for(const e of r)(-1===e.owner?Tn:Sn)&&(a=_[e.idx],l=O.worldToViewportRaw({x:x+e.pos.x,y:x+e.pos.y}),B.drawImage(a,0,0,a.width,a.height,l.x,l.y,s.w,s.h));window.DEBUG&&pn("tiles done");const d=[];for(const i of qe.getActivePlayers(e,n))a=await y(i),l=O.worldToViewport(i),B.drawImage(a,l.x-u,l.y-h),c=i,("replay"===o||c.id!==t)&&d.push([`${i.name} (${i.points})`,l.x,l.y+p]);var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,o]of d)B.fillText(e,t,o);window.DEBUG&&pn("players done"),i.setActivePlayers(qe.getActivePlayers(e,n)),i.setIdlePlayers(qe.getIdlePlayers(e,n)),i.setPiecesDone(qe.getFinishedTileCount(e)),window.DEBUG&&pn("HUD done"),G()&&D.render(),Cn=!1}}),{setHotkeys:e=>{E.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),E.addEvent([oe,e])},onColorChange:e=>{localStorage.setItem("player_color",e),E.addEvent([le,e])},onNameChange:e=>{localStorage.setItem("player_name",e),E.addEvent([ie,e])},replayOnSpeedUp:()=>{f.speedIdx+1{f.speedIdx>=1&&(f.speedIdx--,j())},replayOnPauseToggle:()=>{f.paused=!f.paused,j()},previewImageUrl:M,player:{background:V(),color:qe.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff",name:qe.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon"},disconnect:Nt.disconnect,connect:v}}var In=e({name:"game",components:{PuzzleStatus:vt,Scores:gt,SettingsOverlay:bt,PreviewOverlay:St,ConnectionOverlay:Ut,HelpOverlay:Wt},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 kn(`${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 Pn={id:"game"},_n={class:"menu"},Dn={class:"tabs"},Bn=a("🧩 Puzzles");In.render=function(e,l,a,r,d,c){const p=i("settings-overlay"),h=i("preview-overlay"),g=i("help-overlay"),y=i("connection-overlay"),f=i("puzzle-status"),w=i("router-link"),v=i("scores");return s(),t("div",Pn,[u(n(p,{onBgclick:l[1]||(l[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":l[2]||(l[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[m,"settings"===e.overlay]]),u(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[m,"preview"===e.overlay]]),u(n(g,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[m,"help"===e.overlay]]),n(y,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(f,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",_n,[n("div",Dn,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Bn])),_:1}),n("div",{class:"opener",onClick:l[5]||(l[5]=t=>e.toggle("preview",!1))},"πŸ–ΌοΈ Preview"),n("div",{class:"opener",onClick:l[6]||(l[6]=t=>e.toggle("settings",!0))},"πŸ› οΈ Settings"),n("div",{class:"opener",onClick:l[7]||(l[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var On=e({name:"replay",components:{PuzzleStatus:vt,Scores:gt,SettingsOverlay:bt,PreviewOverlay:St,HelpOverlay:Wt},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 kn(`${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 En={id:"replay"},Mn={class:"menu"},Nn={class:"tabs"},Un=a("🧩 Puzzles");On.render=function(e,l,a,d,c,p){const h=i("settings-overlay"),g=i("preview-overlay"),y=i("help-overlay"),f=i("puzzle-status"),w=i("router-link"),v=i("scores");return s(),t("div",En,[u(n(h,{onBgclick:l[1]||(l[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":l[2]||(l[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[m,"settings"===e.overlay]]),u(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[m,"preview"===e.overlay]]),u(n(y,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[m,"help"===e.overlay]]),n(f,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:l[5]||(l[5]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[6]||(l[6]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",Mn,[n("div",Nn,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Un])),_:1}),n("div",{class:"opener",onClick:l[8]||(l[8]=t=>e.toggle("preview",!1))},"πŸ–ΌοΈ Preview"),n("div",{class:"opener",onClick:l[9]||(l[9]=t=>e.toggle("settings",!0))},"πŸ› οΈ Settings"),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=f({history:w(),routes:[{name:"index",path:"/",component:N},{name:"new-game",path:"/new-game",component:ht},{name:"game",path:"/g/:id",component:In},{name:"replay",path:"/replay/:id",component:On}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=v(x);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=he.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); diff --git a/build/public/assets/index.85898c1b.js b/build/public/assets/index.85898c1b.js new file mode 100644 index 0000000..57b1c41 --- /dev/null +++ b/build/public/assets/index.85898c1b.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as o,b as l,r as i,o as s,e as a,t as r,F as d,f as c,g as u,v as p,h,i as g,j as y,k as m,l as f,m as w,n as v}from"./vendor.00b608ff.js";var x=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const b={id:"app"},A={key:0,class:"nav"},z=a("Index"),T=a("New game");x.render=function(e,a,r,d,c,u){const p=i("router-link"),h=i("router-view");return s(),t("div",b,[e.showNav?(s(),t("ul",A,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[z])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[T])),_:1})])])):l("",!0),n(h)])};const S=864e5,C=e=>{const t=Math.floor(e/S);e%=S;const n=Math.floor(e/36e5);e%=36e5;const o=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${o}m ${Math.floor(e/1e3)}s`};var k=1e3,I=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},P=(e,t)=>C(t-e),_=C,D=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",o=e,l=t||I();return`${n} ${P(o,l)}`}}});const B={class:"game-info-text"},O=n("br",null,null,-1),E=n("br",null,null,-1),M=n("br",null,null,-1);D.render=function(e,d,c,u,p,h){const g=i("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(g,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",B,[a(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),O,a(" πŸ‘₯ "+r(e.game.players),1),E,a(" "+r(e.time(e.game.started,e.game.finished)),1),M])])),_:1},8,["to"]),l("",!0)],4)};var N=e({components:{GameTeaser:D},data:()=>({gamesRunning:[],gamesFinished:[]}),async created(){const e=await fetch("/api/index-data"),t=await e.json();this.gamesRunning=t.gamesRunning,this.gamesFinished=t.gamesFinished}});const U=n("h1",null,"Running games",-1),R=n("h1",null,"Finished games",-1);function $(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function G(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}N.render=function(e,o,l,a,r,u){const p=i("game-teaser");return s(),t("div",null,[U,(s(!0),t(d,null,c(e.gamesRunning,((e,o)=>(s(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128)),R,(s(!0),t(d,null,c(e.gamesFinished,((e,o)=>(s(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128))])};var V={pointSub:function(e,t){return{x:e.x-t.x,y:e.y-t.y}},pointAdd:function(e,t){return{x:e.x+t.x,y:e.y+t.y}},pointDistance:$,pointInBounds:function(e,t){return e.x>=t.x&&e.x<=t.x+t.w&&e.y>=t.y&&e.y<=t.y+t.h},rectCenter:G,rectMoved:function(e,t,n){return{x:e.x+t,y:e.y+n,w:e.w,h:e.h}},rectCenterDistance:function(e,t){return $(G(e),G(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};var j=1,F=4,W=5,H=2,L=3,Y=6,Q=2,q=4,Z=3,K=9,J=1,X=2,ee=3,te=4,ne=5,oe=6,le=7,ie=8,se=10,ae=1,re=2,de=3;class ce{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){return this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295,e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}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 ce(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const ue=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},pe=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=ue(o.getHours(),"00"),i=ue(o.getMinutes(),"00"),s=ue(o.getSeconds(),"00");console[t](`${l}:${i}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var he,ge,ye,me,fe={hash:e=>{let t=0;for(let n=0;nDate.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}},encodeTile:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodeTile: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 Array.isArray(e)?e:[e.id,e.rng.type,ce.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode]},decodeGame:function(e){return Array.isArray(e)?{id:e[0],rng:{type:e[1],obj:ce.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}:e},coordByTileIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}}};(ge=he||(he={}))[ge.Flat=0]="Flat",ge[ge.Out=1]="Out",ge[ge.In=-1]="In",(me=ye||(ye={}))[me.FINAL=0]="FINAL",me[me.ANY=1]="ANY";const we={};function ve(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}function xe(e,t){let n=0;for(let o of we[e].players){if(fe.decodePlayer(o).id===t)return n;n++}return-1}function be(e,t){const n=xe(e,t);return fe.decodePlayer(we[e].players[n])}function Ae(e,t,n){const o=xe(e,t);-1===o?we[e].players.push(fe.encodePlayer(n)):we[e].players[o]=fe.encodePlayer(n)}function ze(e,t){return-1!==xe(e,t)}function Te(e){return we[e]?we[e].players.map(fe.decodePlayer):[]}function Se(e){return we[e].puzzle.tiles.length}function Ce(e){return we[e].scoreMode||0}function ke(e){return Ie(e)===Se(e)}function Ie(e){let t=0;for(let n of we[e].puzzle.tiles)-1===fe.decodeTile(n).owner&&t++;return t}function Pe(e,t,n){const o=be(e,t);for(let l of Object.keys(n))o[l]=n[l];Ae(e,t,o)}function _e(e,t){for(let n of Object.keys(t))we[e].puzzle.data[n]=t[n]}function De(e,t,n){for(let o of Object.keys(n)){const l=fe.decodeTile(we[e].puzzle.tiles[t]);l[o]=n[o],we[e].puzzle.tiles[t]=fe.encodeTile(l)}}const Be=(e,t)=>fe.decodeTile(we[e].puzzle.tiles[t]),Oe=(e,t)=>Be(e,t).group,Ee=(e,t)=>{const n=we[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=we[e].puzzle.info,o=fe.coordByTileIdx(n,t),l=o.x*n.tileSize,i=o.y*n.tileSize;return{x:l,y:i}}(e,t);return V.pointAdd(o,l)},Me=(e,t)=>Be(e,t).pos,Ne=e=>{const t=Ke(e),n=Je(e),o=Math.round(t/4),l=Math.round(n/4);return{x:0-o,y:0-l,w:t+2*o,h:n+2*l}},Ue=(e,t)=>{const n=Ve(e),o=Be(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Re=(e,t)=>Be(e,t).z,$e=(e,t)=>{for(let n of we[e].puzzle.tiles){const e=fe.decodeTile(n);if(e.owner===t)return e.idx}return-1},Ge=e=>we[e].puzzle.info.tileDrawSize,Ve=e=>we[e].puzzle.info.tileSize,je=e=>we[e].puzzle.data.maxGroup,Fe=e=>we[e].puzzle.data.maxZ;function We(e,t){const n=we[e].puzzle.info,o=fe.coordByTileIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const He=(e,t,n)=>{for(let o of t)De(e,o,{z:n})},Le=(e,t,n)=>{const o=Me(e,t);De(e,t,{pos:V.pointAdd(o,n)})},Ye=(e,t,n)=>{const o=Ge(e),l=Ne(e),i=n;for(let s of t){const t=Be(e,s);t.pos.x+n.xl.x+l.w&&(i.x=Math.min(l.x+l.w-t.pos.x+o,i.x)),t.pos.y+n.yl.y+l.h&&(i.y=Math.min(l.y+l.h-t.pos.y+o,i.y))}for(let s of t)Le(e,s,i)},Qe=(e,t,n)=>{for(let o of t)De(e,o,{owner:n})};function qe(e,t){const n=we[e].puzzle.tiles,o=fe.decodeTile(n[t]),l=[];if(o.group)for(let i of n){const e=fe.decodeTile(i);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const Ze=(e,t)=>{const n=be(e,t);return n?n.points:0},Ke=e=>we[e].puzzle.info.table.width,Je=e=>we[e].puzzle.info.table.height;var Xe={__createPlayerObject:ve,setGame:function(e,t){we[e]=t},exists:function(e){return!!we[e]||!1},playerExists:ze,getActivePlayers:function(e,t){const n=t-30*k;return Te(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*k;return Te(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){ze(e,t)?Pe(e,t,{ts:n}):Ae(e,t,ve(t,n))},getFinishedTileCount:Ie,getTileCount:Se,getImageUrl:function(e){return we[e].puzzle.info.imageUrl},setImageUrl:function(e,t){we[e].puzzle.info.imageUrl=t},get:function(e){return we[e]},getAllGames:function(){return Object.values(we).sort(((e,t)=>ke(e.id)===ke(t.id)?t.puzzle.data.started-e.puzzle.data.started:ke(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=be(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=be(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=be(e,t);return n?n.name:null},getPlayerIndexById:xe,getPlayerIdByIndex:function(e,t){return we[e].players.length>t?fe.decodePlayer(we[e].players[t]).id:null},changePlayer:Pe,setPlayer:Ae,setTile:function(e,t,n){we[e].puzzle.tiles[t]=fe.encodeTile(n)},setPuzzleData:function(e,t){we[e].puzzle.data=t},getTableWidth:Ke,getTableHeight:Je,getPuzzle:e=>we[e].puzzle,getRng:e=>we[e].rng.obj,getPuzzleWidth:e=>we[e].puzzle.info.width,getPuzzleHeight:e=>we[e].puzzle.info.height,getTilesSortedByZIndex:function(e){return we[e].puzzle.tiles.map(fe.decodeTile).sort(((e,t)=>e.z-t.z))},getFirstOwnedTile:(e,t)=>{const n=$e(e,t);return n<0?null:we[e].puzzle.tiles[n]},getTileDrawOffset:e=>we[e].puzzle.info.tileDrawOffset,getTileDrawSize:Ge,getFinalTilePos:Ee,getStartTs:e=>we[e].puzzle.data.started,getFinishTs:e=>we[e].puzzle.data.finished,handleInput:function(e,t,n,o){const l=we[e].puzzle,i=function(e,t){return t in we[e].evtInfos?we[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],a=()=>{s.push([ae,l.data])},r=t=>{s.push([re,fe.encodeTile(Be(e,t))])},d=e=>{for(const t of e)r(t)},c=()=>{s.push([de,fe.encodePlayer(be(e,t))])},u=n[0];if(u===oe){const l=n[1];Pe(e,t,{bgcolor:l,ts:o}),c()}else if(u===le){const l=n[1];Pe(e,t,{color:l,ts:o}),c()}else if(u===ie){const l=`${n[1]}`.substr(0,16);Pe(e,t,{name:l,ts:o}),c()}else if(u===J){const l={x:n[1],y:n[2]};Pe(e,t,{d:1,ts:o}),c(),i._last_mouse_down=l;const s=((e,t)=>{let n=we[e].puzzle.info,o=we[e].puzzle.tiles,l=-1,i=-1;for(let s=0;sl)&&(l=e.z,i=s)}return i})(e,l);if(s>=0){let n=Fe(e)+1;_e(e,{maxZ:n}),a();const o=qe(e,s);He(e,o,Fe(e)),Qe(e,o,t),d(o)}i._last_mouse=l}else if(u===ee){const l=n[1],s=n[2],a={x:l,y:s};if(null===i._last_mouse_down)Pe(e,t,{x:l,y:s,ts:o}),c();else{let n=$e(e,t);if(n>=0){Pe(e,t,{x:l,y:s,ts:o}),c();const r=qe(e,n);let u=V.pointInBounds(a,Ne(e))&&V.pointInBounds(i._last_mouse_down,Ne(e));for(let t of r){const n=Ue(e,t);if(V.pointInBounds(a,n)){u=!0;break}}if(u){const t=l-i._last_mouse_down.x,n=s-i._last_mouse_down.y;Ye(e,r,{x:t,y:n}),d(r)}}else Pe(e,t,{ts:o}),c();i._last_mouse_down=a}i._last_mouse=a}else if(u===X){const s={x:n[1],y:n[2]},u=0;i._last_mouse_down=null;let p=$e(e,t);if(p>=0){let n=qe(e,p);Qe(e,n,0),d(n);let i=Me(e,p),s=Ee(e,p);if(V.pointDistance(s,i){for(let n of t)De(e,n,{owner:-1,z:1})})(e,n),d(n);let r=Ze(e,t);0===Ce(e)?r+=n.length:1===Ce(e)&&(r+=1),Pe(e,t,{d:u,ts:o,points:r}),c(),Ie(e)===Se(e)&&(_e(e,{finished:o}),a())}else{const n=(e,t,n,o)=>{let l=we[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=Oe(e,t),l=Oe(e,n);return o&&o===l})(e,t,n))return!1;const i=Me(e,t),s=V.pointAdd(Me(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(V.pointDistance(i,s){const o=we[e].puzzle.tiles,l=Oe(e,t),i=Oe(e,n);let s;const d=[];l&&d.push(l),i&&d.push(i),l?s=l:i?s=i:(_e(e,{maxGroup:je(e)+1}),a(),s=je(e));if(De(e,t,{group:s}),r(t),De(e,n,{group:s}),r(n),d.length>0)for(const a of o){const t=fe.decodeTile(a);d.includes(t.group)&&(De(e,t.idx,{group:s}),r(t.idx))}})(e,t,n),l=qe(e,t);const c=((e,t)=>{let n=0;for(let o of t){let t=Re(e,o);t>n&&(n=t)}return n})(e,l);return He(e,l,c),d(l),!0}return!1};let l=!1;for(let t of qe(e,p)){let o=We(e,t);if(n(e,t,o[0],[0,1])||n(e,t,o[1],[-1,0])||n(e,t,o[2],[0,-1])||n(e,t,o[3],[1,0])){l=!0;break}}if(l&&1===Ce(e)){const n=Ze(e,t)+1;Pe(e,t,{d:u,ts:o,points:n}),c()}else Pe(e,t,{d:u,ts:o}),c()}}else Pe(e,t,{d:u,ts:o}),c();i._last_mouse=s}else if(u===te){const l=n[1],s=n[2];Pe(e,t,{x:l,y:s,ts:o}),c(),i._last_mouse={x:l,y:s}}else if(u===ne){const l=n[1],s=n[2];Pe(e,t,{x:l,y:s,ts:o}),c(),i._last_mouse={x:l,y:s}}else Pe(e,t,{ts:o}),c();return function(e,t,n){we[e].evtInfos[t]=n}(e,t,i),s}},et=e({name:"upload",props:{accept:String,label:String},methods:{async upload(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const o=new FormData;o.append("file",n,n.name);const l=await fetch("/upload",{method:"post",body:o}),i=await l.json();this.$emit("uploaded",i)}}});const tt={class:"btn"};et.render=function(e,o,l,i,a,d){return s(),t("label",null,[n("input",{type:"file",style:{display:"none"},onChange:o[1]||(o[1]=(...t)=>e.upload&&e.upload(...t)),accept:e.accept},null,40,["accept"]),n("span",tt,r(e.label||"Upload File"),1)])};var nt=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},methods:{onClick(){this.$emit("click")}}});nt.render=function(e,n,o,l,i,a){return s(),t("div",{class:"imageteaser",style:e.style,onClick:n[1]||(n[1]=(...t)=>e.onClick&&e.onClick(...t))},null,4)};var ot=e({name:"new-game-dialog",components:{Upload:et,ImageTeaser:nt},props:{images:Array},emits:{newGame:null},data:()=>({tiles:1e3,image:"",scoreMode:ye.ANY}),methods:{mediaImgUploaded(e){this.image=e.image},canStartNewGame(){return!!(this.tilesInt&&this.image&&[0,1].includes(this.scoreModeInt))},onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const lt=n("h1",null,"New game",-1),it=n("td",null,[n("label",null,"Pieces: ")],-1),st=n("td",null,[n("label",null,"Scoring: ")],-1),at=a(" Any (Score when pieces are connected to each other or on final location)"),rt=n("br",null,null,-1),dt=a(" Final (Score when pieces are put to their final location)"),ct=n("td",null,[n("label",null,"Image: ")],-1),ut={key:0},pt=a(" or "),ht={key:1},gt=a(" (or select from below) "),yt={colspan:"2"},mt=n("h1",null,"Image lib",-1);ot.render=function(e,o,l,a,r,g){const y=i("upload"),m=i("image-teaser");return s(),t("div",null,[lt,n("table",null,[n("tr",null,[it,n("td",null,[u(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[st,n("td",null,[n("label",null,[u(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[h,e.scoreMode]]),at]),rt,n("label",null,[u(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[h,e.scoreMode]]),dt])])]),n("tr",null,[ct,n("td",null,[e.image?(s(),t("span",ut,[n("img",{src:e.image.url,style:{width:"150px"}},null,8,["src"]),pt,n(y,{onUploaded:o[4]||(o[4]=t=>e.mediaImgUploaded(t)),accept:"image/*",label:"Upload an image"})])):(s(),t("span",ht,[n(y,{onUploaded:o[5]||(o[5]=t=>e.mediaImgUploaded(t)),accept:"image/*",label:"Upload an image"}),gt]))])]),n("tr",null,[n("td",yt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[6]||(o[6]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))},"Start new game",8,["disabled"])])])]),mt,n("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(m,{image:n,onClick:t=>e.image=n,key:o},null,8,["image","onClick"])))),128))])])};var ft=e({components:{NewGameDialog:ot},data:()=>({images:[]}),async created(){const e=await fetch("/api/newgame-data"),t=await e.json();this.images=t.images},methods:{async onNewGame(e){const t=await fetch("/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});ft.render=function(e,o,l,a,r,d){const c=i("new-game-dialog");return s(),t("div",null,[n(c,{images:e.images,onNewGame:e.onNewGame},null,8,["images","onNewGame"])])};var wt=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 vt={class:"scores"},xt=n("div",null,"Scores",-1),bt=n("td",null,"⚑",-1),At=n("td",null,"πŸ’€",-1);wt.render=function(e,o,l,i,a,u){return s(),t("div",vt,[xt,n("table",null,[(s(!0),t(d,null,c(e.actives,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[bt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(s(!0),t(d,null,c(e.idles,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[At,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var zt=e({name:"puzzle-status",props:{finished:{type:Boolean,required:!0},duration:{type:Number,required:!0},piecesDone:{type:Number,required:!0},piecesTotal:{type:Number,required:!0}},computed:{icon(){return this.finished?"🏁":"⏳"},durationStr(){return _(this.duration)}}});const Tt={class:"timer"};zt.render=function(e,o,l,i,a,d){return s(),t("div",Tt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),g(e.$slots,"default")])};var St=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 Ct=n("td",null,[n("label",null,"Background: ")],-1),kt=n("td",null,[n("label",null,"Color: ")],-1),It=n("td",null,[n("label",null,"Name: ")],-1);St.render=function(e,o,l,i,a,r){return s(),t("div",{class:"overlay transparent",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[4]||(o[4]=y((()=>{}),["stop"]))},[n("tr",null,[Ct,n("td",null,[u(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[kt,n("td",null,[u(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[It,n("td",null,[u(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])])])])};var Pt=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const _t={class:"preview"};Pt.render=function(e,o,l,i,a,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",_t,[n("div",{class:"img",style:e.previewStyle},null,4)])])};const Dt=pe("Communication.js");let Bt,Ot=e=>{},Et=e=>{};let Mt=0;const Nt=e=>{Mt!==e&&(Mt=e,Et(e))};function Ut(e){if(2===Mt)try{Bt.send(JSON.stringify(e))}catch(t){Dt.info("unable to send message.. maybe because ws is invalid?")}}let Rt,$t;var Gt={connect:function(e,t,n){return Rt=0,$t={},Nt(3),new Promise((o=>{Bt=new WebSocket(e,n+"|"+t),Bt.onopen=e=>{Nt(2),Ut([L])},Bt.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===F){const e=t[1];o(e)}else{if(l!==j)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&$t[o])return void delete $t[o];Ot(t)}}},Bt.onerror=e=>{throw Nt(1),"[ 2021-05-15 onerror ]"},Bt.onclose=e=>{4e3===e.code||1001===e.code?Nt(4):Nt(1)}}))},connectReplay:function(e,t,n){return Rt=0,$t={},Nt(3),new Promise((o=>{Bt=new WebSocket(e,n+"|"+t),Bt.onopen=e=>{Nt(2),Ut([Y])},Bt.onmessage=e=>{const t=JSON.parse(e.data),n=t[0];if(n!==W)throw`[ 2021-05-09 invalid connectReplay msgType ${n} ]`;{const e=t[1],n=t[2];o({game:e,log:n})}},Bt.onerror=e=>{throw Nt(1),"[ 2021-05-15 onerror ]"},Bt.onclose=e=>{4e3===e.code||1001===e.code?Nt(4):Nt(1)}}))},disconnect:function(){Bt&&Bt.close(4e3),Rt=0,$t={}},sendClientEvent:function(e){Rt++,$t[Rt]=e,Ut([H,Rt,$t[Rt]])},onServerChange:function(e){Ot=e},onConnectionStateChange:function(e){Et=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},Vt=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Gt.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Gt.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const jt={key:0,class:"overlay connection-lost"},Ft={key:0,class:"overlay-content"},Wt=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Ht={key:1,class:"overlay-content"},Lt=n("div",null,"Connecting...",-1);Vt.render=function(e,o,i,a,r,d){return e.show?(s(),t("div",jt,[e.lostConnection?(s(),t("div",Ft,[Wt,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",Ht,[Lt])):l("",!0)])):l("",!0)};var Yt=e({name:"help-overlay",emits:{bgclick:null}});const Qt=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),a("/"),n("kbd",null,"↑"),a("/πŸ–±οΈ")])])],-1),qt=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),a("/"),n("kbd",null,"↓"),a("/πŸ–±οΈ")])])],-1),Zt=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),a("/"),n("kbd",null,"←"),a("/πŸ–±οΈ")])])],-1),Kt=n("tr",null,[n("td",null,"➑️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),a("/"),n("kbd",null,"β†’"),a("/πŸ–±οΈ")])])],-1),Jt=n("tr",null,[n("td"),n("td",null,[n("div",null,[a("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Xt=n("tr",null,[n("td",null,"πŸ”+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),a("/πŸ–±οΈ-Wheel")])])],-1),en=n("tr",null,[n("td",null,"πŸ”- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),a("/πŸ–±οΈ-Wheel")])])],-1),tn=n("tr",null,[n("td",null,"πŸ–ΌοΈ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),nn=n("tr",null,[n("td",null,"πŸ§©βœ”οΈ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),on=n("tr",null,[n("td",null,"πŸ§©β“ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1);Yt.render=function(e,o,l,i,a,r){return s(),t("div",{class:"overlay transparent",onClick:o[2]||(o[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:o[1]||(o[1]=y((()=>{}),["stop"]))},[Qt,qt,Zt,Kt,Jt,Xt,en,tn,nn,on])])};var ln=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),sn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),an=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),rn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function dn(){let e=0,t=0,n=1;const o=(o,l)=>{e+=o/n,t+=l/n},l=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},i=o=>({x:o.x/n-e,y:o.y/n-t}),s=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),a=e=>({w:e.w*n,h:e.h*n});return{move:o,canZoom:e=>n!=l(e),zoom:(e,t)=>((e,t)=>{if(n==e)return!1;const l=1-n/e;return o(-t.x*l,-t.y*l),n=e,!0})(l(e),t),worldToViewport:e=>{const{x:t,y:n}=s(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:s,worldDimToViewport:e=>{const{w:t,h:n}=a(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:a,viewportToWorld:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:i}}function cn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var un={createCanvas:cn,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=cn(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorize:async function(e,t,n){const o=cn(e.width,e.height),l=o.getContext("2d");return l.save(),l.drawImage(t,0,0),l.fillStyle=n,l.globalCompositeOperation="source-in",l.fillRect(0,0,t.width,t.height),l.restore(),l.save(),l.globalCompositeOperation="destination-over",l.drawImage(e,0,0),l.restore(),await createImageBitmap(o)}};const pn=pe("Debug.js");let hn=0,gn=0;var yn=e=>{hn=performance.now(),gn=e},mn=e=>{const t=performance.now(),n=t-hn;n>gn&&pn.log(e+": "+n),hn=t};const fn=pe("PuzzleGraphics.js");function wn(e,t){const n=fe.coordByTileIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var vn={loadPuzzleBitmaps:async function(e){const t=await un.loadImageToBitmap(e.info.imageUrl),n=await un.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){fn.log("start createPuzzleTileBitmaps");var o=n.tileSize,l=n.tileMarginWidth,i=n.tileDrawSize,s=o/100,a=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0];const r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,i={x:l,y:l},r=V.pointAdd(i,{x:o,y:0}),c=V.pointAdd(r,{x:0,y:o}),u=V.pointSub(c,{x:o,y:0});if(n.moveTo(i.x,i.y),0!==e.top)for(let o=0;o=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;An=t/2,zn=t-An;const n=1/4*this.canvas.width/(t/2);xn=-n,bn=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Tn(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Tn(this.rng));const e=[];for(;this.explodedBombs.length>0;){const t=this.explodedBombs.shift();if(!t)break;t.update(),t.alive&&e.push(t)}this.explodedBombs=e;const t=[];for(;this.readyBombs.length>0;){const e=this.readyBombs.shift();if(!e)break;e.update(this.particles),e.hasExploded?this.explodedBombs.push(e):t.push(e)}this.readyBombs=t;const n=[];for(;this.particles.length>0;){const e=this.particles.shift();if(!e)break;e.update(),e.alive&&n.push(e)}this.particles=n}render(){this.ctx.beginPath(),this.ctx.fillStyle="rgba(0, 0, 0, 0.1)",this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height);for(let e=0;e{const t=e.color+" "+e.d;if(!g[t]){const n=e.d?s:a;if(e.color){const o=e.d?r:d;g[t]=await un.colorize(n,o,e.color)}else g[t]=n}return g[t]},m=function(e,t){return t.width=window.innerWidth,t.height=window.innerHeight,e.appendChild(t),window.addEventListener("resize",(()=>{t.width=window.innerWidth,t.height=window.innerHeight,_n=!0})),t}(l,un.createCanvas()),f={log:[],logIdx:0,speeds:[.5,1,2,5,10,20,50],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0};Gt.onConnectionStateChange((e=>{i.setConnectionState(e)}));let w=()=>0;const v=async()=>{if("play"===o){const o=await Gt.connect(n,e,t),l=fe.decodeGame(o);Xe.setGame(l.id,l),w=()=>I()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const o=await Gt.connectReplay(n,e,t),l=fe.decodeGame(o.game);Xe.setGame(l.id,l),f.log=o.log,f.lastRealTs=I(),f.gameStartTs=parseInt(f.log[0][f.log[0].length-2],10),f.lastGameTs=f.gameStartTs,w=()=>f.lastGameTs}}_n=!0};await v();const x=Xe.getTileDrawOffset(e),b=Xe.getTileDrawSize(e),A=Xe.getPuzzleWidth(e),z=Xe.getPuzzleHeight(e),T=Xe.getTableWidth(e),S=Xe.getTableHeight(e),C={x:(T-A)/2,y:(S-z)/2},k={w:A,h:z},P={w:b,h:b},_=await vn.loadPuzzleBitmaps(Xe.getPuzzle(e)),D=new Cn(m,Xe.getRng(e));D.init();const B=m.getContext("2d");m.classList.add("loaded");const O=dn();O.move(-(T-m.width)/2,-(S-m.height)/2);const E=function(e,t,n){let o=[],l=!0,i=!1,s=!1,a=!1,r=!1,d=!1,c=!1,u=!1;const p=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>p(e.offsetX,e.offsetY),g=()=>p(e.width/2,e.height/2),y=(e,t)=>{l&&("Shift"===t.key?u=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?a=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?r=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?i=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?s=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};e.addEventListener("mousedown",(e=>{0===e.button&&m([J,...h(e)])})),e.addEventListener("mouseup",(e=>{0===e.button&&m([X,...h(e)])})),e.addEventListener("mousemove",(e=>{m([ee,...h(e)])})),e.addEventListener("wheel",(e=>{if(n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?te:ne;m([t,...h(e)])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{l&&(" "===e.key&&m([se]),"F"!==e.key&&"f"!==e.key||(In=!In,_n=!0),"G"!==e.key&&"g"!==e.key||(Pn=!Pn,_n=!0))}));const m=e=>{o.push(e)};return{addEvent:m,consumeAll:()=>{if(0===o.length)return[];const e=o.slice();return o=[],e},createKeyEvents:()=>{const e=u?20:10,t=(i?e:0)-(s?e:0),o=(a?e:0)-(r?e:0);0===t&&0===o||m([K,t,o]),d&&c||(d?n.canZoom("in")&&m([te,...g()]):c&&n.canZoom("out")&&m([ne,...g()]))},setHotkeys:e=>{l=e}}}(m,window,O),M=Xe.getImageUrl(e),N=()=>{const t=Xe.getStartTs(e),n=Xe.getFinishTs(e),o=w();i.setFinished(!!n),i.setDuration((n||o)-t)};N(),i.setPiecesDone(Xe.getFinishedTileCount(e)),i.setPiecesTotal(Xe.getTileCount(e));const U=w();i.setActivePlayers(Xe.getActivePlayers(e,U)),i.setIdlePlayers(Xe.getIdlePlayers(e,U));const R=!!Xe.getFinishTs(e);let $=R;const G=()=>$&&!R,V=()=>Xe.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",j=()=>{i.setReplaySpeed&&i.setReplaySpeed(f.speeds[f.speedIdx]),i.setReplayPaused&&i.setReplayPaused(f.paused)};if("play"===o?setInterval(N,1e3):"replay"===o&&j(),"play"===o)Gt.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,i]of o)switch(l){case de:{const n=fe.decodePlayer(i);n.id!==t&&(Xe.setPlayer(e,n.id,n),_n=!0)}break;case re:{const t=fe.decodeTile(i);Xe.setTile(e,t.idx,t),_n=!0}break;case ae:Xe.setPuzzleData(e,i),_n=!0}$=!!Xe.getFinishTs(e)}));else if("replay"===o){let t=setInterval((()=>{const n=I();if(f.paused)return void(f.lastRealTs=n);const o=(n-f.lastRealTs)*f.speeds[f.speedIdx],l=f.lastGameTs+o;for(;;){if(f.paused)break;const n=f.logIdx+1;if(n>=f.log.length){clearInterval(t);break}const o=f.log[n],i=f.gameStartTs+o[o.length-1];if(i>l)break;const s=o.slice();if(s[0]===Q){const t=s[1];Xe.addPlayer(e,t,i),_n=!0}else if(s[0]===q){const t=Xe.getPlayerIdByIndex(e,s[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";Xe.addPlayer(e,t,i),_n=!0}else if(s[0]===Z){const t=Xe.getPlayerIdByIndex(e,s[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=s[2];Xe.handleInput(e,t,n,i),_n=!0}f.logIdx=n}f.lastRealTs=n,f.lastGameTs=l,N()}),50)}let F=null;return(e=>{const t=e.fps||60,n=e.slow||1,o=e.update,l=e.render,i=window.requestAnimationFrame,s=1/t,a=n*s;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>a;)d-=a,o(s);l(d/n),c=r,i(u)};i(u)})({update:()=>{E.createKeyEvents();for(const n of E.consumeAll())if("play"===o){const o=n[0];if(o===K){const e=n[1],t=n[2];_n=!0,O.move(e,t)}else if(o===ee){if(F&&!Xe.getFirstOwnedTile(e,t)){const e={x:n[1],y:n[2]},t=O.worldToViewport(e),o=Math.round(t.x-F.x),l=Math.round(t.y-F.y);_n=!0,O.move(o,l),F=t}}else if(o===J){const e={x:n[1],y:n[2]};F=O.worldToViewport(e)}else if(o===X)F=null;else if(o===te){const e={x:n[1],y:n[2]};_n=!0,O.zoom("in",O.worldToViewport(e))}else if(o===ne){const e={x:n[1],y:n[2]};_n=!0,O.zoom("out",O.worldToViewport(e))}else o===se&&i.togglePreview();const l=w();Xe.handleInput(e,t,n,l).length>0&&(_n=!0),Gt.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===K){const e=n[1],t=n[2];_n=!0,O.move(e,t)}else if(e===ee){if(F){const e={x:n[1],y:n[2]},t=O.worldToViewport(e),o=Math.round(t.x-F.x),l=Math.round(t.y-F.y);_n=!0,O.move(o,l),F=t}}else if(e===J){const e={x:n[1],y:n[2]};F=O.worldToViewport(e)}else if(e===X)F=null;else if(e===te){const e={x:n[1],y:n[2]};_n=!0,O.zoom("in",O.worldToViewport(e))}else if(e===ne){const e={x:n[1],y:n[2]};_n=!0,O.zoom("out",O.worldToViewport(e))}else e===se&&i.togglePreview()}$=!!Xe.getFinishTs(e),G()&&(D.update(),_n=!0)},render:async()=>{if(!_n)return;const n=w();let l,s,a;window.DEBUG&&yn(0),B.fillStyle=V(),B.fillRect(0,0,m.width,m.height),window.DEBUG&&mn("clear done"),l=O.worldToViewportRaw(C),s=O.worldDimToViewportRaw(k),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&mn("board done");const r=Xe.getTilesSortedByZIndex(e);window.DEBUG&&mn("get tiles done"),s=O.worldDimToViewportRaw(P);for(const e of r)(-1===e.owner?In:Pn)&&(a=_[e.idx],l=O.worldToViewportRaw({x:x+e.pos.x,y:x+e.pos.y}),B.drawImage(a,0,0,a.width,a.height,l.x,l.y,s.w,s.h));window.DEBUG&&mn("tiles done");const d=[];for(const i of Xe.getActivePlayers(e,n))a=await y(i),l=O.worldToViewport(i),B.drawImage(a,l.x-u,l.y-h),c=i,("replay"===o||c.id!==t)&&d.push([`${i.name} (${i.points})`,l.x,l.y+p]);var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,o]of d)B.fillText(e,t,o);window.DEBUG&&mn("players done"),i.setActivePlayers(Xe.getActivePlayers(e,n)),i.setIdlePlayers(Xe.getIdlePlayers(e,n)),i.setPiecesDone(Xe.getFinishedTileCount(e)),window.DEBUG&&mn("HUD done"),G()&&D.render(),_n=!1}}),{setHotkeys:e=>{E.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),E.addEvent([oe,e])},onColorChange:e=>{localStorage.setItem("player_color",e),E.addEvent([le,e])},onNameChange:e=>{localStorage.setItem("player_name",e),E.addEvent([ie,e])},replayOnSpeedUp:()=>{f.speedIdx+1{f.speedIdx>=1&&(f.speedIdx--,j())},replayOnPauseToggle:()=>{f.paused=!f.paused,j()},previewImageUrl:M,player:{background:V(),color:Xe.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff",name:Xe.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon"},disconnect:Gt.disconnect,connect:v}}var Bn=e({name:"game",components:{PuzzleStatus:zt,Scores:wt,SettingsOverlay:St,PreviewOverlay:Pt,ConnectionOverlay:Vt,HelpOverlay:Yt},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 Dn(`${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 On={id:"game"},En={class:"menu"},Mn={class:"tabs"},Nn=a("🧩 Puzzles");Bn.render=function(e,l,a,r,d,c){const p=i("settings-overlay"),h=i("preview-overlay"),g=i("help-overlay"),y=i("connection-overlay"),f=i("puzzle-status"),w=i("router-link"),v=i("scores");return s(),t("div",On,[u(n(p,{onBgclick:l[1]||(l[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":l[2]||(l[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[m,"settings"===e.overlay]]),u(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[m,"preview"===e.overlay]]),u(n(g,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[m,"help"===e.overlay]]),n(y,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(f,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",En,[n("div",Mn,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Nn])),_:1}),n("div",{class:"opener",onClick:l[5]||(l[5]=t=>e.toggle("preview",!1))},"πŸ–ΌοΈ Preview"),n("div",{class:"opener",onClick:l[6]||(l[6]=t=>e.toggle("settings",!0))},"πŸ› οΈ Settings"),n("div",{class:"opener",onClick:l[7]||(l[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var Un=e({name:"replay",components:{PuzzleStatus:zt,Scores:wt,SettingsOverlay:St,PreviewOverlay:Pt,HelpOverlay:Yt},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 Dn(`${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 Rn={id:"replay"},$n={class:"menu"},Gn={class:"tabs"},Vn=a("🧩 Puzzles");Un.render=function(e,l,a,d,c,p){const h=i("settings-overlay"),g=i("preview-overlay"),y=i("help-overlay"),f=i("puzzle-status"),w=i("router-link"),v=i("scores");return s(),t("div",Rn,[u(n(h,{onBgclick:l[1]||(l[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":l[2]||(l[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[m,"settings"===e.overlay]]),u(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[m,"preview"===e.overlay]]),u(n(y,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[m,"help"===e.overlay]]),n(f,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:l[5]||(l[5]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[6]||(l[6]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",$n,[n("div",Gn,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Vn])),_:1}),n("div",{class:"opener",onClick:l[8]||(l[8]=t=>e.toggle("preview",!1))},"πŸ–ΌοΈ Preview"),n("div",{class:"opener",onClick:l[9]||(l[9]=t=>e.toggle("settings",!0))},"πŸ› οΈ Settings"),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=f({history:w(),routes:[{name:"index",path:"/",component:N},{name:"new-game",path:"/new-game",component:ft},{name:"game",path:"/g/:id",component:Bn},{name:"replay",path:"/replay/:id",component:Un}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=v(x);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=fe.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 3429ad3..0f3745a 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -2,7 +2,7 @@ 🧩 jigsaw.hyottoko.club - + diff --git a/build/server/main.js b/build/server/main.js index 43df728..d9067f2 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -74,9 +74,6 @@ const logger = (...pre) => { // get a unique id const uniqId = () => Date.now().toString(36) + Math.random().toString(36).substring(2); function encodeShape(data) { - if (typeof data === 'number') { - return data; - } /* encoded in 1 byte: 00000000 ^^ top @@ -90,9 +87,6 @@ function encodeShape(data) { | ((data.left + 1) << 6); } function decodeShape(data) { - if (typeof data !== 'number') { - return data; - } return { top: (data >> 0 & 0b11) - 1, right: (data >> 2 & 0b11) - 1, @@ -101,15 +95,9 @@ function decodeShape(data) { }; } function encodeTile(data) { - if (Array.isArray(data)) { - return data; - } return [data.idx, data.pos.x, data.pos.y, data.z, data.owner, data.group]; } function decodeTile(data) { - if (!Array.isArray(data)) { - return data; - } return { idx: data[0], pos: { @@ -122,9 +110,6 @@ function decodeTile(data) { }; } function encodePlayer(data) { - if (Array.isArray(data)) { - return data; - } return [ data.id, data.x, @@ -138,9 +123,6 @@ function encodePlayer(data) { ]; } function decodePlayer(data) { - if (!Array.isArray(data)) { - return data; - } return { id: data[0], x: data[1], @@ -461,8 +443,17 @@ var Time = { durationStr, }; -const SCORE_MODE_FINAL = 0; -const SCORE_MODE_ANY = 1; +var PieceEdge; +(function (PieceEdge) { + PieceEdge[PieceEdge["Flat"] = 0] = "Flat"; + PieceEdge[PieceEdge["Out"] = 1] = "Out"; + PieceEdge[PieceEdge["In"] = -1] = "In"; +})(PieceEdge || (PieceEdge = {})); +var ScoreMode; +(function (ScoreMode) { + ScoreMode[ScoreMode["FINAL"] = 0] = "FINAL"; + ScoreMode[ScoreMode["ANY"] = 1] = "ANY"; +})(ScoreMode || (ScoreMode = {})); const IDLE_TIMEOUT_SEC = 30; // Map const GAMES = {}; @@ -502,11 +493,11 @@ function getPlayerIdByIndex(gameId, playerIndex) { return null; } function getPlayer(gameId, playerId) { - let idx = getPlayerIndexById(gameId, playerId); + const idx = getPlayerIndexById(gameId, playerId); return Util.decodePlayer(GAMES[gameId].players[idx]); } function setPlayer(gameId, playerId, player) { - let idx = getPlayerIndexById(gameId, playerId); + const idx = getPlayerIndexById(gameId, playerId); if (idx === -1) { GAMES[gameId].players.push(Util.encodePlayer(player)); } @@ -580,7 +571,7 @@ function setImageUrl(gameId, imageUrl) { GAMES[gameId].puzzle.info.imageUrl = imageUrl; } function getScoreMode(gameId) { - return GAMES[gameId].scoreMode || SCORE_MODE_FINAL; + return GAMES[gameId].scoreMode || ScoreMode.FINAL; } function isFinished(gameId) { return getFinishedTileCount(gameId) === getTileCount(gameId); @@ -601,6 +592,7 @@ function getTilesSortedByZIndex(gameId) { function changePlayer(gameId, playerId, change) { const player = getPlayer(gameId, playerId); for (let k of Object.keys(change)) { + // @ts-ignore player[k] = change[k]; } setPlayer(gameId, playerId, player); @@ -614,6 +606,7 @@ function changeData(gameId, change) { function changeTile(gameId, tileIdx, change) { for (let k of Object.keys(change)) { const tile = Util.decodeTile(GAMES[gameId].puzzle.tiles[tileIdx]); + // @ts-ignore tile[k] = change[k]; GAMES[gameId].puzzle.tiles[tileIdx] = Util.encodeTile(tile); } @@ -832,7 +825,7 @@ const getPlayerName = (gameId, playerId) => { }; const getPlayerPoints = (gameId, playerId) => { const p = getPlayer(gameId, playerId); - return p ? p.points : null; + return p ? p.points : 0; }; // determine if two tiles are grouped together const areGrouped = (gameId, tileIdx1, tileIdx2) => { @@ -1023,10 +1016,10 @@ function handleInput$1(gameId, playerId, input, ts) { finishTiles(gameId, tileIdxs); _tileChanges(tileIdxs); let points = getPlayerPoints(gameId, playerId); - if (getScoreMode(gameId) === SCORE_MODE_FINAL) { + if (getScoreMode(gameId) === ScoreMode.FINAL) { points += tileIdxs.length; } - else if (getScoreMode(gameId) === SCORE_MODE_ANY) { + else if (getScoreMode(gameId) === ScoreMode.ANY) { points += 1; } else ; @@ -1075,7 +1068,7 @@ function handleInput$1(gameId, playerId, input, ts) { break; } } - if (snapped && getScoreMode(gameId) === SCORE_MODE_ANY) { + if (snapped && getScoreMode(gameId) === ScoreMode.ANY) { const points = getPlayerPoints(gameId, playerId) + 1; changePlayer(gameId, playerId, { d, ts, points }); _playerChange(); @@ -1150,8 +1143,6 @@ var GameCommon = { getStartTs, getFinishTs, handleInput: handleInput$1, - SCORE_MODE_FINAL, - SCORE_MODE_ANY, }; const __filename = fileURLToPath(import.meta.url); @@ -1343,7 +1334,7 @@ async function createPuzzle(rng, targetTiles, image, ts) { } // then shuffle the positions positions = rng.shuffle(positions); - tiles = tiles.map(tile => { + const pieces = tiles.map(tile => { return Util.encodeTile({ idx: tile.idx, group: 0, @@ -1362,7 +1353,7 @@ async function createPuzzle(rng, targetTiles, image, ts) { // Complete puzzle object return { // tiles array - tiles, + tiles: pieces, // game data for puzzle, data changes during the game data: { // TODO: maybe calculate this each time? @@ -1500,7 +1491,7 @@ function loadGame(gameId) { puzzle: game.puzzle, players: game.players, evtInfos: {}, - scoreMode: game.scoreMode || GameCommon.SCORE_MODE_FINAL, + scoreMode: game.scoreMode || ScoreMode.FINAL, }; GameCommon.setGame(gameObject.id, gameObject); } @@ -1746,7 +1737,7 @@ wss.on('message', async ({ socket, data }) => { throw `[gamelog ${gameId} does not exist... ]`; } const log = GameLog.get(gameId); - const game = await Game.createGameObject(gameId, log[0][2], log[0][3], log[0][4], log[0][5] || GameCommon.SCORE_MODE_FINAL); + const game = await Game.createGameObject(gameId, log[0][2], log[0][3], log[0][4], log[0][5] || ScoreMode.FINAL); notify([Protocol.EV_SERVER_INIT_REPLAY, Util.encodeGame(game), log], [socket]); } break; diff --git a/src/common/GameCommon.ts b/src/common/GameCommon.ts index 493a923..b73bca5 100644 --- a/src/common/GameCommon.ts +++ b/src/common/GameCommon.ts @@ -1,16 +1,12 @@ -import Geometry from './Geometry' +import Geometry, { Point, Rect } from './Geometry' import Protocol from './Protocol' import { Rng } from './Rng' import Time from './Time' import Util from './Util' -interface EncodedPlayer extends Array {} -interface EncodedPiece extends Array {} - -interface Point { - x: number - y: number -} +export type EncodedPlayer = Array +export type EncodedPiece = Array +export type EncodedPieceShape = number interface GameRng { obj: Rng @@ -22,7 +18,7 @@ interface Game { players: Array puzzle: Puzzle evtInfos: Record - scoreMode?: number + scoreMode?: ScoreMode rng: GameRng } @@ -44,15 +40,24 @@ interface PuzzleTable { height: number } +enum PieceEdge { + Flat = 0, + Out = 1, + In = -1, +} export interface PieceShape { - top: 0|1|-1 - bottom: 0|1|-1 - left: 0|1|-1 - right: 0|1|-1 + 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 { @@ -72,8 +77,7 @@ export interface PuzzleInfo { tilesX: number tilesY: number - // TODO: ts type Array - shapes: Array + shapes: Array } export interface Player { @@ -93,8 +97,10 @@ interface EvtInfo { _last_mouse_down: Point|null } -const SCORE_MODE_FINAL = 0 -const SCORE_MODE_ANY = 1 +export enum ScoreMode { + FINAL = 0, + ANY = 1, +} const IDLE_TIMEOUT_SEC = 30 @@ -134,20 +140,20 @@ function getPlayerIndexById(gameId: string, playerId: string): number { return -1 } -function getPlayerIdByIndex(gameId: string, playerIndex: number) { +function getPlayerIdByIndex(gameId: string, playerIndex: number): string|null { if (GAMES[gameId].players.length > playerIndex) { return Util.decodePlayer(GAMES[gameId].players[playerIndex]).id } return null } -function getPlayer(gameId: string, playerId: string) { - let idx = getPlayerIndexById(gameId, playerId) +function getPlayer(gameId: string, playerId: string): Player { + const idx = getPlayerIndexById(gameId, playerId) return Util.decodePlayer(GAMES[gameId].players[idx]) } function setPlayer(gameId: string, playerId: string, player: Player) { - let idx = getPlayerIndexById(gameId, playerId) + const idx = getPlayerIndexById(gameId, playerId) if (idx === -1) { GAMES[gameId].players.push(Util.encodePlayer(player)) } else { @@ -168,17 +174,17 @@ function playerExists(gameId: string, playerId: string) { return idx !== -1 } -function getActivePlayers(gameId: string, ts: number) { +function getActivePlayers(gameId: string, ts: number): Array { const minTs = ts - IDLE_TIMEOUT_SEC * Time.SEC return getAllPlayers(gameId).filter((p: Player) => p.ts >= minTs) } -function getIdlePlayers(gameId: string, ts: number) { +function getIdlePlayers(gameId: string, ts: number): Array { const minTs = ts - IDLE_TIMEOUT_SEC * Time.SEC return getAllPlayers(gameId).filter((p: Player) => p.ts < minTs && p.points > 0) } -function addPlayer(gameId: string, playerId: string, ts: number) { +function addPlayer(gameId: string, playerId: string, ts: number): void { if (!playerExists(gameId, playerId)) { setPlayer(gameId, playerId, __createPlayerObject(playerId, ts)) } else { @@ -186,7 +192,7 @@ function addPlayer(gameId: string, playerId: string, ts: number) { } } -function getEvtInfo(gameId: string, playerId: string) { +function getEvtInfo(gameId: string, playerId: string): EvtInfo { if (playerId in GAMES[gameId].evtInfos) { return GAMES[gameId].evtInfos[playerId] } @@ -211,7 +217,7 @@ function getAllGames(): Array { }) } -function getAllPlayers(gameId: string) { +function getAllPlayers(gameId: string): Array { return GAMES[gameId] ? GAMES[gameId].players.map(Util.decodePlayer) : [] @@ -221,11 +227,11 @@ function get(gameId: string) { return GAMES[gameId] } -function getTileCount(gameId: string) { +function getTileCount(gameId: string): number { return GAMES[gameId].puzzle.tiles.length } -function getImageUrl(gameId: string) { +function getImageUrl(gameId: string): string { return GAMES[gameId].puzzle.info.imageUrl } @@ -233,8 +239,8 @@ function setImageUrl(gameId: string, imageUrl: string) { GAMES[gameId].puzzle.info.imageUrl = imageUrl } -function getScoreMode(gameId: string) { - return GAMES[gameId].scoreMode || SCORE_MODE_FINAL +function getScoreMode(gameId: string): ScoreMode { + return GAMES[gameId].scoreMode || ScoreMode.FINAL } function isFinished(gameId: string) { @@ -259,6 +265,7 @@ function getTilesSortedByZIndex(gameId: string) { function changePlayer(gameId: string, playerId: string, change: any) { const player = getPlayer(gameId, playerId) for (let k of Object.keys(change)) { + // @ts-ignore player[k] = change[k] } setPlayer(gameId, playerId, player) @@ -274,12 +281,13 @@ function changeData(gameId: string, change: any) { function changeTile(gameId: string, tileIdx: number, change: any) { for (let k of Object.keys(change)) { const tile = Util.decodeTile(GAMES[gameId].puzzle.tiles[tileIdx]) + // @ts-ignore tile[k] = change[k] GAMES[gameId].puzzle.tiles[tileIdx] = Util.encodeTile(tile) } } -const getTile = (gameId: string, tileIdx: number) => { +const getTile = (gameId: string, tileIdx: number): Piece => { return Util.decodeTile(GAMES[gameId].puzzle.tiles[tileIdx]) } @@ -500,7 +508,7 @@ const freeTileIdxByPos = (gameId: string, pos: Point) => { continue } - const collisionRect = { + const collisionRect: Rect = { x: tile.pos.x, y: tile.pos.y, w: info.tileSize, @@ -531,9 +539,9 @@ const getPlayerName = (gameId: string, playerId: string) => { return p ? p.name : null } -const getPlayerPoints = (gameId: string, playerId: string) => { +const getPlayerPoints = (gameId: string, playerId: string): number => { const p = getPlayer(gameId, playerId) - return p ? p.points : null + return p ? p.points : 0 } // determine if two tiles are grouped together @@ -746,13 +754,13 @@ function handleInput(gameId: string, playerId: string, input: any, ts: number) { _tileChanges(tileIdxs) let points = getPlayerPoints(gameId, playerId) - if (getScoreMode(gameId) === SCORE_MODE_FINAL) { + if (getScoreMode(gameId) === ScoreMode.FINAL) { points += tileIdxs.length - } else if (getScoreMode(gameId) === SCORE_MODE_ANY) { + } else if (getScoreMode(gameId) === ScoreMode.ANY) { points += 1 } else { // no score mode... should never occur, because there is a - // fallback to SCORE_MODE_FINAL in getScoreMode function + // fallback to ScoreMode.FINAL in getScoreMode function } changePlayer(gameId, playerId, { d, ts, points }) _playerChange() @@ -809,7 +817,7 @@ function handleInput(gameId: string, playerId: string, input: any, ts: number) { break } } - if (snapped && getScoreMode(gameId) === SCORE_MODE_ANY) { + if (snapped && getScoreMode(gameId) === ScoreMode.ANY) { const points = getPlayerPoints(gameId, playerId) + 1 changePlayer(gameId, playerId, { d, ts, points }) _playerChange() @@ -881,6 +889,4 @@ export default { getStartTs, getFinishTs, handleInput, - SCORE_MODE_FINAL, - SCORE_MODE_ANY, } diff --git a/src/common/Geometry.ts b/src/common/Geometry.ts index 1e9edc8..47394ff 100644 --- a/src/common/Geometry.ts +++ b/src/common/Geometry.ts @@ -1,14 +1,18 @@ -interface Point { +export interface Point { x: number y: number } -interface Rect { +export interface Rect { x: number y: number w: number h: number } +export interface Dim { + w: number + h: number +} function pointSub(a: Point, b: Point): Point { return { x: a.x - b.x, y: a.y - b.y } diff --git a/src/common/Time.ts b/src/common/Time.ts index 1933a62..0a77340 100644 --- a/src/common/Time.ts +++ b/src/common/Time.ts @@ -4,7 +4,7 @@ const MIN = SEC * 60 const HOUR = MIN * 60 const DAY = HOUR * 24 -export const timestamp = () => { +export const timestamp = (): number => { const d = new Date(); return Date.UTC( d.getUTCFullYear(), @@ -17,7 +17,7 @@ export const timestamp = () => { ) } -export const durationStr = (duration: number) => { +export const durationStr = (duration: number): string => { const d = Math.floor(duration / DAY) duration = duration % DAY diff --git a/src/common/Util.ts b/src/common/Util.ts index 2593428..9bf6ae9 100644 --- a/src/common/Util.ts +++ b/src/common/Util.ts @@ -1,3 +1,5 @@ +import { EncodedPiece, EncodedPieceShape, EncodedPlayer, Piece, PieceShape, Player } from './GameCommon' +import { Point } from './Geometry' import { Rng } from './Rng' @@ -27,10 +29,7 @@ export const logger = (...pre: Array) => { // get a unique id export const uniqId = () => Date.now().toString(36) + Math.random().toString(36).substring(2) -function encodeShape(data: any): number { - if (typeof data === 'number') { - return data - } +function encodeShape(data: PieceShape): EncodedPieceShape { /* encoded in 1 byte: 00000000 ^^ top @@ -44,10 +43,7 @@ function encodeShape(data: any): number { | ((data.left + 1) << 6) } -function decodeShape(data: any) { - if (typeof data !== 'number') { - return data - } +function decodeShape(data: EncodedPieceShape): PieceShape { return { top: (data >> 0 & 0b11) - 1, right: (data >> 2 & 0b11) - 1, @@ -56,17 +52,11 @@ function decodeShape(data: any) { } } -function encodeTile(data: any): Array { - if (Array.isArray(data)) { - return data - } +function encodeTile(data: Piece): EncodedPiece { return [data.idx, data.pos.x, data.pos.y, data.z, data.owner, data.group] } -function decodeTile(data: any) { - if (!Array.isArray(data)) { - return data - } +function decodeTile(data: EncodedPiece): Piece { return { idx: data[0], pos: { @@ -79,10 +69,7 @@ function decodeTile(data: any) { } } -function encodePlayer(data: any): Array { - if (Array.isArray(data)) { - return data - } +function encodePlayer(data: Player): EncodedPlayer { return [ data.id, data.x, @@ -96,10 +83,7 @@ function encodePlayer(data: any): Array { ] } -function decodePlayer(data: any) { - if (!Array.isArray(data)) { - return data - } +function decodePlayer(data: EncodedPlayer): Player { return { id: data[0], x: data[1], @@ -145,7 +129,7 @@ function decodeGame(data: any) { } } -function coordByTileIdx(info: any, tileIdx: number) { +function coordByTileIdx(info: any, tileIdx: number): Point { const wTiles = info.width / info.tileSize return { x: tileIdx % wTiles, diff --git a/src/frontend/Camera.ts b/src/frontend/Camera.ts index 20f4e4a..fad305a 100644 --- a/src/frontend/Camera.ts +++ b/src/frontend/Camera.ts @@ -1,16 +1,10 @@ +import { Dim, Point } from "../common/Geometry" + const MIN_ZOOM = .1 const MAX_ZOOM = 6 const ZOOM_STEP = .05 type ZOOM_DIR = 'in'|'out' -interface Point { - x: number - y: number -} -interface Dim { - w: number - h: number -} export default function Camera () { let x = 0 diff --git a/src/frontend/Graphics.ts b/src/frontend/Graphics.ts index 787370d..7306002 100644 --- a/src/frontend/Graphics.ts +++ b/src/frontend/Graphics.ts @@ -17,15 +17,23 @@ async function loadImageToBitmap(imagePath: string): Promise { }) } -async function resizeBitmap (bitmap: ImageBitmap, width: number, height: number): Promise { +async function resizeBitmap ( + bitmap: ImageBitmap, + width: number, + height: number +): Promise { const c = createCanvas(width, height) const ctx = c.getContext('2d') as CanvasRenderingContext2D ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, width, height) return await createImageBitmap(c) } -async function colorize(image: ImageBitmap, mask: ImageBitmap, color: string): Promise { - const c = createCanvas(image.width, image.height) +async function colorize( + bitmap: ImageBitmap, + mask: ImageBitmap, + color: string +): Promise { + const c = createCanvas(bitmap.width, bitmap.height) const ctx = c.getContext('2d') as CanvasRenderingContext2D ctx.save() ctx.drawImage(mask, 0, 0) @@ -35,7 +43,7 @@ async function colorize(image: ImageBitmap, mask: ImageBitmap, color: string): P ctx.restore() ctx.save() ctx.globalCompositeOperation = "destination-over" - ctx.drawImage(image, 0, 0) + ctx.drawImage(bitmap, 0, 0) ctx.restore() return await createImageBitmap(c) } diff --git a/src/frontend/PuzzleGraphics.ts b/src/frontend/PuzzleGraphics.ts index a68444c..52f37ad 100644 --- a/src/frontend/PuzzleGraphics.ts +++ b/src/frontend/PuzzleGraphics.ts @@ -1,13 +1,17 @@ "use strict" -import Geometry from '../common/Geometry' +import Geometry, { Rect } from '../common/Geometry' import Graphics from './Graphics' import Util, { logger } from './../common/Util' import { Puzzle, PuzzleInfo, PieceShape } from './../common/GameCommon' const log = logger('PuzzleGraphics.js') -async function createPuzzleTileBitmaps(img: ImageBitmap, tiles: Array, info: PuzzleInfo) { +async function createPuzzleTileBitmaps( + img: ImageBitmap, + tiles: Array, + info: PuzzleInfo +): Promise> { log.log('start createPuzzleTileBitmaps') var tileSize = info.tileSize var tileMarginWidth = info.tileMarginWidth @@ -23,7 +27,7 @@ async function createPuzzleTileBitmaps(img: ImageBitmap, tiles: Array, info 63, 5, 65, 15, 100, 0 ]; - const bitmaps = new Array(tiles.length) + const bitmaps: Array = new Array(tiles.length) const paths: Record = {} function pathForShape(shape: PieceShape) { @@ -89,7 +93,7 @@ async function createPuzzleTileBitmaps(img: ImageBitmap, tiles: Array, info const c2 = Graphics.createCanvas(tileDrawSize, tileDrawSize) const ctx2 = c2.getContext('2d') as CanvasRenderingContext2D - for (let t of tiles) { + for (const t of tiles) { const tile = Util.decodeTile(t) const srcRect = srcRectByIdx(info, tile.idx) const path = pathForShape(Util.decodeShape(info.shapes[tile.idx])) @@ -198,7 +202,7 @@ async function createPuzzleTileBitmaps(img: ImageBitmap, tiles: Array, info return bitmaps } -function srcRectByIdx(puzzleInfo: PuzzleInfo, idx: number) { +function srcRectByIdx(puzzleInfo: PuzzleInfo, idx: number): Rect { const c = Util.coordByTileIdx(puzzleInfo, idx) return { x: c.x * puzzleInfo.tileSize, @@ -208,7 +212,7 @@ function srcRectByIdx(puzzleInfo: PuzzleInfo, idx: number) { } } -async function loadPuzzleBitmaps(puzzle: Puzzle) { +async function loadPuzzleBitmaps(puzzle: Puzzle): Promise> { // load bitmap, to determine the original size of the image const bmp = await Graphics.loadImageToBitmap(puzzle.info.imageUrl) diff --git a/src/frontend/components/NewGameDialog.vue b/src/frontend/components/NewGameDialog.vue index 6ac0edc..33d9789 100644 --- a/src/frontend/components/NewGameDialog.vue +++ b/src/frontend/components/NewGameDialog.vue @@ -44,7 +44,7 @@