From 9a13838d339b9afd6de560ce54bd2eb38243536c Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 5 Jun 2021 15:14:36 +0200 Subject: [PATCH 01/45] replay with ws --- src/common/Protocol.ts | 10 +++ src/frontend/Communication.ts | 58 +++++++++++++++++ src/frontend/game.ts | 71 ++++++++++----------- src/server/GameLog.ts | 115 +++++++++++++++++++++++++++++++++- src/server/main.ts | 31 +++++++++ 5 files changed, 247 insertions(+), 38 deletions(-) diff --git a/src/common/Protocol.ts b/src/common/Protocol.ts index 1a5e124..d1dd698 100644 --- a/src/common/Protocol.ts +++ b/src/common/Protocol.ts @@ -43,6 +43,11 @@ const EV_SERVER_INIT = 4 const EV_CLIENT_EVENT = 2 const EV_CLIENT_INIT = 3 +const EV_CLIENT_INIT_REPLAY = 5 +const EV_SERVER_INIT_REPLAY = 6 +const EV_CLIENT_REPLAY_EVENT = 7 +const EV_SERVER_REPLAY_EVENT = 8 + const LOG_HEADER = 1 const LOG_ADD_PLAYER = 2 const LOG_UPDATE_PLAYER = 4 @@ -70,6 +75,11 @@ export default { EV_CLIENT_EVENT, EV_CLIENT_INIT, + EV_CLIENT_INIT_REPLAY, + EV_SERVER_INIT_REPLAY, + EV_CLIENT_REPLAY_EVENT, + EV_SERVER_REPLAY_EVENT, + LOG_HEADER, LOG_ADD_PLAYER, LOG_UPDATE_PLAYER, diff --git a/src/frontend/Communication.ts b/src/frontend/Communication.ts index 5301faa..753ce6f 100644 --- a/src/frontend/Communication.ts +++ b/src/frontend/Communication.ts @@ -112,6 +112,54 @@ function connect( }) } + +let onReplayDataCallback = (logEntry: any[]) => {} +const onReplayData = (fn: any) => { + onReplayDataCallback = fn +} +function connectReplay( + address: string, + gameId: string, + clientId: string +): Promise { + clientSeq = 0 + events = {} + setConnectionState(CONN_STATE_CONNECTING) + return new Promise(resolve => { + ws = new WebSocket(address, clientId + '|' + gameId) + ws.onopen = () => { + setConnectionState(CONN_STATE_CONNECTED) + send([Protocol.EV_CLIENT_INIT_REPLAY]) + } + + ws.onmessage = (e: MessageEvent) => { + const msg: ServerEvent = JSON.parse(e.data) + const msgType = msg[0] + if (msgType === Protocol.EV_SERVER_INIT_REPLAY) { + const game = msg[1] + resolve(game) + } else if (msgType === Protocol.EV_SERVER_REPLAY_EVENT) { + onReplayDataCallback(msg[1]) + } else { + throw `[ 2021-06-05 invalid connectReplay msgType ${msgType} ]` + } + } + + ws.onerror = () => { + setConnectionState(CONN_STATE_DISCONNECTED) + throw `[ 2021-05-15 onerror ]` + } + + ws.onclose = (e: CloseEvent) => { + if (e.code === CODE_CUSTOM_DISCONNECT || e.code === CODE_GOING_AWAY) { + setConnectionState(CONN_STATE_CLOSED) + } else { + setConnectionState(CONN_STATE_DISCONNECTED) + } + } + }) +} + async function requestReplayData( gameId: string, offset: number, @@ -139,7 +187,17 @@ function sendClientEvent(evt: GameEvent): void { send([Protocol.EV_CLIENT_EVENT, clientSeq, events[clientSeq]]) } +function requestMoreReplayData(): void { + send([Protocol.EV_CLIENT_REPLAY_EVENT]) +} + export default { + connectReplay, + requestMoreReplayData, + onReplayData, + + + connect, requestReplayData, disconnect, diff --git a/src/frontend/game.ts b/src/frontend/game.ts index 7a14e42..15df76d 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -310,28 +310,6 @@ export async function main( HUD.setConnectionState(state) }) - const queryNextReplayBatch = async ( - gameId: string - ): Promise => { - const offset = REPLAY.dataOffset - REPLAY.dataOffset += REPLAY.dataSize - const replay: ReplayData = await Communication.requestReplayData( - gameId, - offset, - REPLAY.dataSize - ) - - // cut log that was already handled - REPLAY.log = REPLAY.log.slice(REPLAY.logPointer) - REPLAY.logPointer = 0 - REPLAY.log.push(...replay.log) - - if (replay.log.length < REPLAY.dataSize) { - REPLAY.final = true - } - return replay - } - let TIME: () => number = () => 0 const connect = async () => { if (MODE === MODE_PLAY) { @@ -344,15 +322,31 @@ export async function main( REPLAY.dataSize = 10000 REPLAY.speeds = [0.5, 1, 2, 5, 10, 20, 50, 100, 250, 500] REPLAY.speedIdx = 1 - const replay: ReplayData = await queryNextReplayBatch(gameId) - if (!replay.game) { + + Communication.onReplayData((logLines: any[][]) => { + if (logLines.length === 0) { + console.log('MUHHHHH FINAL!!!!') + REPLAY.final = true + } else { + if (REPLAY.logPointer === 0) { + REPLAY.log.push(...logLines) + } else { + REPLAY.log = REPLAY.log.slice(REPLAY.logPointer - 1) + REPLAY.logPointer = 1 + REPLAY.log.push(...logLines) + } + } + }) + + const game: any = await Communication.connectReplay(wsAddress, gameId, clientId) + if (!game) { throw '[ 2021-05-29 no game received ]' } - const gameObject: GameType = Util.decodeGame(replay.game) + const gameObject: GameType = Util.decodeGame(game) Game.setGame(gameObject.id, gameObject) REPLAY.lastRealTs = Time.timestamp() - REPLAY.gameStartTs = parseInt(replay.log[0][4], 10) + REPLAY.gameStartTs = Game.getStartTs(gameId) REPLAY.lastGameTs = REPLAY.gameStartTs REPLAY.paused = false REPLAY.skipNonActionPhases = false @@ -521,7 +515,7 @@ export async function main( } // // TODO: remove (make changable via interface) - // REPLAY.skipNonActionPhases = true + REPLAY.skipNonActionPhases = true if (MODE === MODE_PLAY) { Communication.onServerChange((msg: ServerEvent) => { @@ -580,8 +574,10 @@ export async function main( } const next = async () => { - if (REPLAY.logPointer + 1 >= REPLAY.log.length) { - await queryNextReplayBatch(gameId) + if (REPLAY.logPointer >= REPLAY.log.length) { + Communication.requestMoreReplayData() + to = setTimeout(next, 50) + return } const realTs = Time.timestamp() @@ -597,19 +593,18 @@ export async function main( if (REPLAY.paused) { break } - const nextIdx = REPLAY.logPointer + 1 - if (nextIdx >= REPLAY.log.length) { + if (REPLAY.logPointer >= REPLAY.log.length) { break } - const currLogEntry = REPLAY.log[REPLAY.logPointer] - const currTs: Timestamp = REPLAY.gameStartTs + currLogEntry[currLogEntry.length - 1] - const nextLogEntry = REPLAY.log[nextIdx] + const lastLogEntry = REPLAY.logPointer > 0 ? REPLAY.log[REPLAY.logPointer - 1] : null + const lastTs: Timestamp = REPLAY.gameStartTs + (lastLogEntry ? lastLogEntry[lastLogEntry.length - 1] : 0) + const nextLogEntry = REPLAY.log[REPLAY.logPointer] const nextTs: Timestamp = REPLAY.gameStartTs + nextLogEntry[nextLogEntry.length - 1] if (nextTs > maxGameTs) { // next log entry is too far into the future if (REPLAY.skipNonActionPhases && (maxGameTs + 50 < nextTs)) { - const skipInterval = nextTs - currTs + const skipInterval = nextTs - lastTs // lets skip to the next log entry // log.info('skipping non-action, from', maxGameTs, skipInterval) maxGameTs += skipInterval @@ -620,13 +615,15 @@ export async function main( if (handleLogEntry(nextLogEntry, nextTs)) { RERENDER = true } - REPLAY.logPointer = nextIdx + REPLAY.logPointer++ } while (true) REPLAY.lastRealTs = realTs REPLAY.lastGameTs = maxGameTs updateTimerElements() - if (!REPLAY.final) { + if (REPLAY.final && REPLAY.logPointer + 1 >= REPLAY.log.length) { + // done + } else { to = setTimeout(next, 50) } } diff --git a/src/server/GameLog.ts b/src/server/GameLog.ts index d764304..72d69ec 100644 --- a/src/server/GameLog.ts +++ b/src/server/GameLog.ts @@ -1,10 +1,12 @@ +import WebSocket from 'ws' import fs from 'fs' import readline from 'readline' import stream from 'stream' import Time from '../common/Time' -import { Timestamp } from '../common/Types' +import { Game as GameType, ScoreMode, ShapeMode, SnapMode, Timestamp } from '../common/Types' import { logger } from './../common/Util' import { DATA_DIR } from './../server/Dirs' +import Game from './Game' const log = logger('GameLog.js') @@ -81,7 +83,118 @@ const get = async ( }) } +interface LineReader { + readLine: () => Promise, +} + +const createLineReader = async ( + gameId: string +): Promise => { + const stream: fs.ReadStream = await new Promise(resolve => { + const file = filename(gameId) + if (!fs.existsSync(file)) { + return null + } + + const instream = fs.createReadStream(file) + instream.on('readable', () => { + resolve(instream) + }) + }) + + let line = '' + const readLine = async (): Promise => { + return new Promise(resolve => { + let chunk + let resolved = false + while (null !== (chunk = stream.read(1))) { + if (chunk.toString() === "\n") { + resolve(line) + line = '' + resolved = true + break + } + line += chunk + } + + if (!resolved) { + resolve('') + } + }) + } + + return { + readLine, + } +} + +interface SocketGameLog { + socket: WebSocket + rl: LineReader +} + +const connected: SocketGameLog[] = [] +const getSocketGameLog = ( + socket: WebSocket, +): SocketGameLog|null => { + for (const entry of connected) { + if (entry.socket === socket) { + return entry + } + } + return null +} + +const open = async (socket: WebSocket, gameId: string): Promise => { + const rl = await createLineReader(gameId) + if (!rl) { + return null + } + const socketGameLog = { + socket: socket, + rl: rl, + } + const line = await rl.readLine() + const log = JSON.parse(line) + connected.push(socketGameLog) + const g: GameType = await Game.createGameObject( + gameId, + log[2], + log[3], + log[4], + log[5] || ScoreMode.FINAL, + log[6] || ShapeMode.NORMAL, + log[7] || SnapMode.NORMAL, + ) + return g +} + +const getNextBySocket = async (socket: WebSocket): Promise => { + const socketGameLog = getSocketGameLog(socket) + if (!socketGameLog) { + return [] + } + + const lines = [] + for (let i = 0; i < 1000; i++) { + const line = await socketGameLog.rl.readLine() + if (line) { + try { + lines.push(JSON.parse(line)) + } catch (e) { + log.error(e) + log.error(line) + } + } else { + break + } + } + return lines +} + export default { + open, + getNextBySocket, shouldLog, create, exists, diff --git a/src/server/main.ts b/src/server/main.ts index 215ec6f..fd75379 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -247,6 +247,37 @@ wss.on('message', async ( const msg = JSON.parse(data as string) const msgType = msg[0] switch (msgType) { + case Protocol.EV_CLIENT_INIT_REPLAY: { + if (!GameLog.exists(gameId)) { + throw `[gamelog ${gameId} does not exist... ]` + } + + // should connect the socket with game log + // pseudo code + const game = await GameLog.open(socket, gameId) + if (!game) { + throw `[game not created :/ ]` + } + notify( + [Protocol.EV_SERVER_INIT_REPLAY, Util.encodeGame(game)], + [socket] + ) + } break + + case Protocol.EV_CLIENT_REPLAY_EVENT: { + if (!GameLog.exists(gameId)) { + throw `[gamelog ${gameId} does not exist... ]` + } + // should read next some lines from game log, using the game + // log connected with this socket + // pseudo code + const logEntries = await GameLog.getNextBySocket(socket) + notify( + [Protocol.EV_SERVER_REPLAY_EVENT, logEntries], + [socket] + ) + } break + case Protocol.EV_CLIENT_INIT: { if (!GameCommon.exists(gameId)) { throw `[game ${gameId} does not exist... ]` From 514b3c6b2282b88205de7896971db27ab7820f54 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 5 Jun 2021 17:13:17 +0200 Subject: [PATCH 02/45] split logs so that replay works for long games --- build/public/assets/index.7efa4c6c.js | 1 - build/public/assets/index.ab1d6e0f.js | 1 + build/public/index.html | 2 +- build/server/main.js | 75 +++++++++++++------------- scripts/split_logs.ts | 72 +++++++++++++++++++++++++ src/frontend/Communication.ts | 5 +- src/frontend/game.ts | 29 ++++------ src/frontend/views/Replay.vue | 8 +++ src/server/GameLog.ts | 78 ++++++++++++++------------- src/server/main.ts | 2 +- 10 files changed, 171 insertions(+), 102 deletions(-) delete mode 100644 build/public/assets/index.7efa4c6c.js create mode 100644 build/public/assets/index.ab1d6e0f.js create mode 100644 scripts/split_logs.ts diff --git a/build/public/assets/index.7efa4c6c.js b/build/public/assets/index.7efa4c6c.js deleted file mode 100644 index 375a85b..0000000 --- a/build/public/assets/index.7efa4c6c.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as n,w as o,b as l,r as a,o as i,e as s,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as x,s as C,u as k,x as A,y as S}from"./vendor.684f7bc8.js";var z=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const T={id:"app"},P={key:0,class:"nav"},I=s("Index"),D=s("New game");z.render=function(e,s,r,d,c,u){const p=a("router-link"),g=a("router-view");return i(),t("div",T,[e.showNav?(i(),t("ul",P,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[D])),_:1})])])):l("",!0),n(g)])};const E=864e5,_=e=>{const t=Math.floor(e/E);e%=E;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 M=1e3,O=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},N=(e,t)=>_(t-e),B=_,U=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||O();return`${n} ${N(o,l)}`}}});const R={class:"game-info-text"},V=n("br",null,null,-1),$=n("br",null,null,-1),G=n("br",null,null,-1),F=s(" ↪️ Watch replay ");U.render=function(e,d,c,u,p,g){const h=a("router-link");return i(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",R,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),V,s(" 👥 "+r(e.game.players),1),$,s(" "+r(e.time(e.game.started,e.game.finished)),1),G])])),_:1},8,["to"]),e.game.hasReplay?(i(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[F])),_:1},8,["to"])):l("",!0)],4)};var L=e({components:{GameTeaser:U},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 j=n("h1",null,"Running games",-1),W=n("h1",null,"Finished games",-1);L.render=function(e,o,l,s,r,u){const p=a("game-teaser");return i(),t("div",null,[j,(i(!0),t(d,null,c(e.gamesRunning,((e,o)=>(i(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128)),W,(i(!0),t(d,null,c(e.gamesFinished,((e,o)=>(i(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128))])};var q=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});q.render=function(e,o,l,a,s,r){return i(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var H,Y,Q,Z,K,X,J,ee,te=e({name:"image-library",components:{ImageTeaser:q},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});te.render=function(e,n,o,l,s,r){const u=a("image-teaser");return i(),t("div",null,[(i(!0),t(d,null,c(e.images,((n,o)=>(i(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(Y=H||(H={}))[Y.Flat=0]="Flat",Y[Y.Out=1]="Out",Y[Y.In=-1]="In",(Z=Q||(Q={}))[Z.FINAL=0]="FINAL",Z[Z.ANY=1]="ANY",(X=K||(K={}))[X.NORMAL=0]="NORMAL",X[X.ANY=1]="ANY",X[X.FLAT=2]="FLAT",(ee=J||(J={}))[ee.NORMAL=0]="NORMAL",ee[ee.REAL=1]="REAL";class ne{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new ne(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const oe=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},le=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=oe(o.getHours(),"00"),a=oe(o.getMinutes(),"00"),i=oe(o.getSeconds(),"00");console[t](`${l}:${a}:${i}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var ae={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",ne.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Q.FINAL,e.shapeMode||K.ANY,e.snapMode||J.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:ne.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const ie={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};ie.render=function(e,n,o,l,a,s){return i(),t("div",{style:s.style,title:o.title},null,12,["title"])};var se=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.key&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const re=m();y("data-v-39ed99c7");const de={key:0,class:"autocomplete"};f();const ce=re(((e,o,a,s,u,m)=>(i(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(i(),t("div",de,[n("ul",null,[(i(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(i(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(i(!0),t(d,null,c(e.values,((n,o)=>(i(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));se.render=ce,se.__scopeId="data-v-39ed99c7";const ue=le("NewImageDialog.vue");var pe=e({name:"new-image-dialog",components:{ResponsiveImage:ie,TagsInput:se},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){ue.info("onDragleave"),this.droppable=!1}}});const ge={key:0,class:"has-image"},he={key:1},me={class:"upload"},ye=n("span",{class:"btn"},"Upload File",-1),fe={class:"area-settings"},ve=n("td",null,[n("label",null,"Title")],-1),we=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),be=n("td",null,[n("label",null,"Tags")],-1),xe={class:"area-buttons"},Ce=s("🧩 Post to gallery "),ke=n("br",null,null,-1),Ae=s(" + set up game");pe.render=function(e,o,l,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[e.previewUrl?(i(),t("div",ge,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(i(),t("div",he,[n("label",me,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ye])]))],34),n("div",fe,[n("table",null,[n("tr",null,[ve,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),we,n("tr",null,[be,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",xe,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ce,ke,Ae],8,["disabled"])])])])};var Se=e({name:"edit-image-dialog",components:{ResponsiveImage:ie,TagsInput:se},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const ze={class:"area-image"},Te={class:"has-image"},Pe={class:"area-settings"},Ie=n("td",null,[n("label",null,"Title")],-1),De=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ee=n("td",null,[n("label",null,"Tags")],-1),_e={class:"area-buttons"};Se.render=function(e,o,l,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",ze,[n("div",Te,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Pe,[n("table",null,[n("tr",null,[Ie,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),De,n("tr",null,[Ee,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",_e,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Me=e({name:"new-game-dialog",components:{ResponsiveImage:ie},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Q.ANY,shapeMode:K.NORMAL,snapMode:J.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Oe={class:"area-image"},Ne={class:"has-image"},Be={key:0,class:"image-title"},Ue={class:"area-settings"},Re=n("td",null,[n("label",null,"Pieces")],-1),Ve=n("td",null,[n("label",null,"Scoring: ")],-1),$e=s(" Any (Score when pieces are connected to each other or on final location)"),Ge=n("br",null,null,-1),Fe=s(" Final (Score when pieces are put to their final location)"),Le=n("td",null,[n("label",null,"Shapes: ")],-1),je=s(" Normal"),We=n("br",null,null,-1),qe=s(" Any (flat pieces can occur anywhere)"),He=n("br",null,null,-1),Ye=s(" Flat (all pieces flat on all sides)"),Qe=n("td",null,[n("label",null,"Snapping: ")],-1),Ze=s(" Normal (pieces snap to final destination and to each other)"),Ke=n("br",null,null,-1),Xe=s(" Real (pieces snap only to corners, already snapped pieces and to each other)"),Je={class:"area-buttons"};Me.render=function(e,o,s,d,c,h){const m=a("responsive-image");return i(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Oe,[n("div",Ne,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(i(),t("div",Be,'"'+r(e.image.title)+'"',1)):l("",!0)]),n("div",Ue,[n("table",null,[n("tr",null,[Re,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[Ve,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),$e]),Ge,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),Fe])])]),n("tr",null,[Le,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),je]),We,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),qe]),He,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),Ye])])]),n("tr",null,[Qe,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Ze]),Ke,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),Xe])])])])]),n("div",Je,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var et=e({components:{ImageLibrary:te,NewImageDialog:pe,EditImageDialog:Se,NewGameDialog:Me},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${ae.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const tt={class:"upload-image-teaser"},nt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),ot={key:0},lt=s(" Tags: "),at=s(" Sort by: "),it=n("option",{value:"date_desc"},"Newest first",-1),st=n("option",{value:"date_asc"},"Oldest first",-1),rt=n("option",{value:"alpha_asc"},"A-Z",-1),dt=n("option",{value:"alpha_desc"},"Z-A",-1);et.render=function(e,o,s,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return i(),t("div",null,[n("div",tt,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),nt]),n("div",null,[e.tags.length>0?(i(),t("label",ot,[lt,(i(!0),t(d,null,c(e.relevantTags,((n,o)=>(i(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[at,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[it,st,rt,dt],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(i(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(i(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(i(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ct=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 ut={class:"scores"},pt=n("div",null,"Scores",-1),gt=n("td",null,"⚡",-1),ht=n("td",null,"💤",-1);ct.render=function(e,o,l,a,s,u){return i(),t("div",ut,[pt,n("table",null,[(i(!0),t(d,null,c(e.actives,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[gt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(i(!0),t(d,null,c(e.idles,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[ht,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var mt=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 B(this.duration)}}});const yt={class:"timer"};mt.render=function(e,o,l,a,s,d){return i(),t("div",yt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var ft=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 vt=n("td",null,[n("label",null,"Background: ")],-1),wt=n("td",null,[n("label",null,"Color: ")],-1),bt=n("td",null,[n("label",null,"Name: ")],-1),xt=n("td",null,[n("label",null,"Sounds: ")],-1);ft.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:o[6]||(o[6]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[5]||(o[5]=u((()=>{}),["stop"]))},[n("tr",null,[vt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[wt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[bt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[xt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[x,e.modelValue.soundsEnabled]])])])])])};var Ct=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const kt={class:"preview"};Ct.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",kt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var At=1,St=4,zt=2,Tt=3,Pt=2,It=4,Dt=3,Et=9,_t=1,Mt=2,Ot=3,Nt=4,Bt=5,Ut=6,Rt=7,Vt=8,$t=10,Gt=11,Ft=1,Lt=2,jt=3;const Wt=le("Communication.js");let qt,Ht=[],Yt=e=>{Ht.push(e)},Qt=[],Zt=e=>{Qt.push(e)};let Kt=0;const Xt=e=>{Kt!==e&&(Kt=e,Zt(e))};function Jt(e){if(2===Kt)try{qt.send(JSON.stringify(e))}catch(t){Wt.info("unable to send message.. maybe because ws is invalid?")}}let en,tn;var nn={connect:function(e,t,n){return en=0,tn={},Xt(3),new Promise((o=>{qt=new WebSocket(e,n+"|"+t),qt.onopen=()=>{Xt(2),Jt([Tt])},qt.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===St){const e=t[1];o(e)}else{if(l!==At)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&tn[o])return void delete tn[o];Yt(t)}}},qt.onerror=()=>{throw Xt(1),"[ 2021-05-15 onerror ]"},qt.onclose=e=>{4e3===e.code||1001===e.code?Xt(4):Xt(1)}}))},requestReplayData:async function(e,t,n){const o={gameId:e,offset:t,size:n},l=await fetch(`/api/replay-data${ae.asQueryArgs(o)}`);return await l.json()},disconnect:function(){qt&&qt.close(4e3),en=0,tn={}},sendClientEvent:function(e){en++,tn[en]=e,Jt([zt,en,tn[en]])},onServerChange:function(e){Yt=e;for(const t of Ht)Yt(t);Ht=[]},onConnectionStateChange:function(e){Zt=e;for(const t of Qt)Zt(t);Qt=[]},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},on=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===nn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===nn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const ln={key:0,class:"overlay connection-lost"},an={key:0,class:"overlay-content"},sn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),rn={key:1,class:"overlay-content"},dn=n("div",null,"Connecting...",-1);on.render=function(e,o,a,s,r,d){return e.show?(i(),t("div",ln,[e.lostConnection?(i(),t("div",an,[sn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(i(),t("div",rn,[dn])):l("",!0)])):l("",!0)};var cn=e({name:"help-overlay",emits:{bgclick:null}});const un=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),pn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),gn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),hn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),mn=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),yn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),fn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),vn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),wn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),bn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),xn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1);cn.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:o[2]||(o[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:o[1]||(o[1]=u((()=>{}),["stop"]))},[un,pn,gn,hn,mn,yn,fn,vn,wn,bn,xn])])};var Cn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),kn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),An=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Sn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),zn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Tn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),i=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function Pn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var In={createCanvas:Pn,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=Pn(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=Pn(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(),o}};const Dn=le("Debug.js");let En=0,_n=0;var Mn=e=>{En=performance.now(),_n=e},On=e=>{const t=performance.now(),n=t-En;n>_n&&Dn.log(e+": "+n),En=t};function Nn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function Bn(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var Un={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:Nn,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:Bn,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 Nn(Bn(e),Bn(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const Rn=le("PuzzleGraphics.js");function Vn(e,t){const n=ae.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var $n={loadPuzzleBitmaps:async function(e){const t=await In.loadImageToBitmap(e.info.imageUrl),n=await In.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Rn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,i=o/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=Un.pointAdd(a,{x:o,y:0}),c=Un.pointAdd(r,{x:0,y:o}),u=Un.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;oae.decodePiece(Gn[e].puzzle.tiles[t]),to=(e,t)=>eo(e,t).group,no=(e,t)=>{const n=Gn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},oo=(e,t)=>{const n=Gn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Gn[e].puzzle.info,o=ae.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return Un.pointAdd(o,l)},lo=(e,t)=>eo(e,t).pos,ao=e=>{const t=ko(e),n=Ao(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}},io=(e,t)=>{const n=uo(e),o=eo(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},so=(e,t)=>eo(e,t).z,ro=(e,t)=>{for(const n of Gn[e].puzzle.tiles){const e=ae.decodePiece(n);if(e.owner===t)return e.idx}return-1},co=e=>Gn[e].puzzle.info.tileDrawSize,uo=e=>Gn[e].puzzle.info.tileSize,po=e=>Gn[e].puzzle.data.maxGroup,go=e=>Gn[e].puzzle.data.maxZ;function ho(e,t){const n=Gn[e].puzzle.info,o=ae.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const mo=(e,t,n)=>{for(const o of t)Jn(e,o,{z:n})},yo=(e,t,n)=>{const o=lo(e,t);Jn(e,t,{pos:Un.pointAdd(o,n)})},fo=(e,t,n)=>{const o=co(e),l=ao(e),a=n;for(const i of t){const t=eo(e,i);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const i of t)yo(e,i,a)},vo=(e,t)=>eo(e,t).owner,wo=(e,t)=>{for(const n of t)Jn(e,n,{owner:-1,z:1})},bo=(e,t,n)=>{for(const o of t)Jn(e,o,{owner:n})};function xo(e,t){const n=Gn[e].puzzle.tiles,o=ae.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=ae.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const Co=(e,t)=>{const n=Ln(e,t);return n?n.points:0},ko=e=>Gn[e].puzzle.info.table.width,Ao=e=>Gn[e].puzzle.info.table.height;var So={setGame:function(e,t){Gn[e]=t},exists:function(e){return!!Gn[e]||!1},playerExists:Wn,getActivePlayers:function(e,t){const n=t-30*M;return qn(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*M;return qn(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Wn(e,t)?Kn(e,t,{ts:n}):jn(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:Zn,getPieceCount:Hn,getImageUrl:function(e){return Gn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Gn[e].puzzle.info.imageUrl=t},get:function(e){return Gn[e]||null},getAllGames:function(){return Object.values(Gn).sort(((e,t)=>Qn(e.id)===Qn(t.id)?t.puzzle.data.started-e.puzzle.data.started:Qn(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Ln(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Ln(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Ln(e,t);return n?n.name:null},getPlayerIndexById:Fn,getPlayerIdByIndex:function(e,t){return Gn[e].players.length>t?ae.decodePlayer(Gn[e].players[t]).id:null},changePlayer:Kn,setPlayer:jn,setPiece:function(e,t,n){Gn[e].puzzle.tiles[t]=ae.encodePiece(n)},setPuzzleData:function(e,t){Gn[e].puzzle.data=t},getTableWidth:ko,getTableHeight:Ao,getPuzzle:e=>Gn[e].puzzle,getRng:e=>Gn[e].rng.obj,getPuzzleWidth:e=>Gn[e].puzzle.info.width,getPuzzleHeight:e=>Gn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Gn[e].puzzle.tiles.map(ae.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=ro(e,t);return n<0?null:Gn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Gn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:co,getFinalPiecePos:oo,getStartTs:e=>Gn[e].puzzle.data.started,getFinishTs:e=>Gn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Gn[e].puzzle,i=function(e,t){return t in Gn[e].evtInfos?Gn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],r=()=>{s.push([Ft,a.data])},d=t=>{s.push([Lt,ae.encodePiece(eo(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=Ln(e,t);n&&s.push([jt,ae.encodePlayer(n)])},p=n[0];if(p===Ut){const l=n[1];Kn(e,t,{bgcolor:l,ts:o}),u()}else if(p===Rt){const l=n[1];Kn(e,t,{color:l,ts:o}),u()}else if(p===Vt){const l=`${n[1]}`.substr(0,16);Kn(e,t,{name:l,ts:o}),u()}else if(p===Et){const l=n[1],a=n[2],i=Ln(e,t);if(i){const n=i.x-l,s=i.y-a;Kn(e,t,{ts:o,x:n,y:s}),u()}}else if(p===_t){const l={x:n[1],y:n[2]};Kn(e,t,{d:1,ts:o}),u(),i._last_mouse_down=l;const a=((e,t)=>{const n=Gn[e].puzzle.info,o=Gn[e].puzzle.tiles;let l=-1,a=-1;for(let i=0;il)&&(l=e.z,a=i)}return a})(e,l);if(a>=0){const n=go(e)+1;Xn(e,{maxZ:n}),r();const o=xo(e,a);mo(e,o,go(e)),bo(e,o,t),c(o)}i._last_mouse=l}else if(p===Ot){const l=n[1],a=n[2],s={x:l,y:a};if(null===i._last_mouse_down)Kn(e,t,{x:l,y:a,ts:o}),u();else{const n=ro(e,t);if(n>=0){Kn(e,t,{x:l,y:a,ts:o}),u();const r=xo(e,n);let d=Un.pointInBounds(s,ao(e))&&Un.pointInBounds(i._last_mouse_down,ao(e));for(const t of r){const n=io(e,t);if(Un.pointInBounds(s,n)){d=!0;break}}if(d){const t=l-i._last_mouse_down.x,n=a-i._last_mouse_down.y;fo(e,r,{x:t,y:n}),c(r)}}else Kn(e,t,{ts:o}),u();i._last_mouse_down=s}i._last_mouse=s}else if(p===Mt){const s={x:n[1],y:n[2]},p=0;i._last_mouse_down=null;const g=ro(e,t);if(g>=0){const n=xo(e,g);bo(e,n,0),c(n);const i=lo(e,g),s=oo(e,g);let h=!1;if(function(e){return Gn[e].snapMode||J.NORMAL}(e)===J.REAL){for(const t of n)if(no(e,t)){h=!0;break}}else h=!0;if(h&&Un.pointDistance(s,i){const l=Gn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=to(e,t),l=to(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=lo(e,t),i=Un.pointAdd(lo(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(Un.pointDistance(a,i){const o=Gn[e].puzzle.tiles,l=to(e,t),a=to(e,n);let i;const s=[];l&&s.push(l),a&&s.push(a),l?i=l:a?i=a:(Xn(e,{maxGroup:po(e)+1}),r(),i=po(e));if(Jn(e,t,{group:i}),d(t),Jn(e,n,{group:i}),d(n),s.length>0)for(const r of o){const t=ae.decodePiece(r);s.includes(t.group)&&(Jn(e,t.idx,{group:i}),d(t.idx))}})(e,t,n),l=xo(e,t),((e,t)=>-1===vo(e,t))(e,n))wo(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=so(e,o);t>n&&(n=t)}return n})(e,l);mo(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of xo(e,g)){const o=ho(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])){a=!0;break}}if(a&&Yn(e)===Q.ANY){const n=Co(e,t)+1;Kn(e,t,{d:p,ts:o,points:n}),u()}else Kn(e,t,{d:p,ts:o}),u();a&&l&&l(t)}}else Kn(e,t,{d:p,ts:o}),u();i._last_mouse=s}else if(p===Nt){const l=n[1],a=n[2];Kn(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else if(p===Bt){const l=n[1],a=n[2];Kn(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else Kn(e,t,{ts:o}),u();return function(e,t,n){Gn[e].evtInfos[t]=n}(e,t,i),s}};let zo=-10,To=20,Po=2,Io=15;class Do{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=zo+Math.random()*To,this.vy=-1*(Po+Math.random()*Io),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Po=t/2,Io=t-Po;const n=1/4*this.canvas.width/(t/2);zo=-n,To=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Do(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Do(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(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(In.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Uo=!0})),t}(l,In.createCanvas()),w={final:!1,requesting:!0,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,dataOffset:0,dataSize:1e4};nn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{w.requesting=!0;const t=await nn.requestReplayData(e,w.dataOffset,w.dataSize);return w.dataOffset+=w.dataSize,w.requesting=!1,t};let x=()=>0;const C=async()=>{if("play"===o){const o=await nn.connect(n,e,t),l=ae.decodeGame(o);So.setGame(l.id,l),x=()=>O()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=ae.decodeGame(t.game);So.setGame(n.id,n),w.requesting=!1,w.log=t.log,w.lastRealTs=O(),w.gameStartTs=parseInt(w.log[0][4],10),w.lastGameTs=w.gameStartTs,w.final=!1,w.logPointer=0,w.speeds=[.5,1,2,5,10,20,50,100,250,500],w.speedIdx=1,w.paused=!1,w.dataOffset=0,w.dataSize=1e4,x=()=>w.lastGameTs}}Uo=!0};await C();const k=So.getPieceDrawOffset(e),A=So.getPieceDrawSize(e),S=So.getPuzzleWidth(e),z=So.getPuzzleHeight(e),T=So.getTableWidth(e),P=So.getTableHeight(e),I={x:(T-S)/2,y:(P-z)/2},D={w:S,h:z},E={w:A,h:A},_=await $n.loadPuzzleBitmaps(So.getPuzzle(e)),M=new _o(v,So.getRng(e));M.init();const N=v.getContext("2d");v.classList.add("loaded");const B=Tn();B.move(-(T-v.width)/2,-(P-v.height)/2);const U=function(e,t,n){let o=[],l=!0,a=!1,i=!1,s=!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]},g=e=>p(e.offsetX,e.offsetY),h=()=>p(e.width/2,e.height/2),m=(e,t)=>{l&&("Shift"===t.key?u=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?s=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?r=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?a=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?i=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};let y=null;e.addEventListener("mousedown",(e=>{y=g(e),0===e.button&&f([_t,...y])})),e.addEventListener("mouseup",(e=>{y=g(e),0===e.button&&f([Mt,...y])})),e.addEventListener("mousemove",(e=>{y=g(e),f([Ot,...y])})),e.addEventListener("wheel",(e=>{if(y=g(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Nt:Bt;f([t,...y])}})),t.addEventListener("keydown",(e=>m(!0,e))),t.addEventListener("keyup",(e=>m(!1,e))),t.addEventListener("keypress",(e=>{l&&(" "===e.key&&f([$t]),"F"!==e.key&&"f"!==e.key||(No=!No,Uo=!0),"G"!==e.key&&"g"!==e.key||(Bo=!Bo,Uo=!0),"M"!==e.key&&"m"!==e.key||f([Gt]))}));const f=e=>{o.push(e)};return{addEvent:f,consumeAll:()=>{if(0===o.length)return[];const e=o.slice();return o=[],e},createKeyEvents:()=>{const e=(a?1:0)-(i?1:0),t=(s?1:0)-(r?1:0);if(0!==e||0!==t){const o=(u?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});f([Et,l.w,l.h]),y&&(y[0]-=l.w,y[1]-=l.h)}if(d&&c);else if(d){if(n.canZoom("in")){const e=y||h();f([Nt,...e])}}else if(c&&n.canZoom("out")){const e=y||h();f([Bt,...e])}},setHotkeys:e=>{l=e}}}(v,window,B),R=So.getImageUrl(e),V=()=>{const t=So.getStartTs(e),n=So.getFinishTs(e),o=x();a.setFinished(!!n),a.setDuration((n||o)-t)};V(),a.setPiecesDone(So.getFinishedPiecesCount(e)),a.setPiecesTotal(So.getPieceCount(e));const $=x();a.setActivePlayers(So.getActivePlayers(e,$)),a.setIdlePlayers(So.getIdlePlayers(e,$));const G=!!So.getFinishTs(e);let F=G;const L=()=>F&&!G,j=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},W=()=>So.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",q=()=>So.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let H="",Y="",Q=!1;const Z=e=>{Q=e;const[t,n]=e?[H,"grab"]:[Y,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},K=e=>{H=In.colorizedCanvas(r,c,e).toDataURL(),Y=In.colorizedCanvas(d,u,e).toDataURL(),Z(Q)};K(q());const X=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},J=[],ee=()=>{J.forEach((e=>{clearInterval(e)}))};let te;"play"===o?J.push(setInterval((()=>{V()}),1e3)):"replay"===o&&X(),"play"===o?nn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case jt:{const n=ae.decodePlayer(a);n.id!==t&&(So.setPlayer(e,n.id,n),Uo=!0)}break;case Lt:{const t=ae.decodePiece(a);So.setPiece(e,t.idx,t),Uo=!0}break;case Ft:So.setPuzzleData(e,a),Uo=!0}F=!!So.getFinishTs(e)})):"replay"===o&&J.push(setInterval((()=>{const t=O();if(w.requesting)return void(w.lastRealTs=t);if(w.logPointer+1>=w.log.length)return w.lastRealTs=t,void(async e=>{const t=await b(e);w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...t.log),t.log.length=w.log.length){w.final&&ee();break}const n=w.log[t],l=w.gameStartTs+n[n.length-1];if(l>o)break;const a=n.slice();if(a[0]===Pt){const t=a[1];So.addPlayer(e,t,l),Uo=!0}else if(a[0]===It){const t=So.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";So.addPlayer(e,t,l),Uo=!0}else if(a[0]===Dt){const t=So.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=a[2];So.handleInput(e,t,n,l),Uo=!0}w.logPointer=t}w.lastRealTs=t,w.lastGameTs=o,V()}),50));let ne=null;return te=(e=>{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,i=window.requestAnimationFrame,s=1/n,r=o*s;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(s);a(c/o),u=d,t||i(p)};return i(p),{stop:()=>{t=!0}}})({update:()=>{U.createKeyEvents();for(const n of U.consumeAll())if("play"===o){const o=n[0];if(o===Et){const e=n[1],t=n[2],o=B.worldDimToViewport({w:e,h:t});Uo=!0,B.move(o.w,o.h)}else if(o===Ot){if(ne&&!So.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-ne.x),l=Math.round(t.y-ne.y);Uo=!0,B.move(o,l),ne=t}}else if(o===Rt)K(n[1]);else if(o===_t){const e={x:n[1],y:n[2]};ne=B.worldToViewport(e),Z(!0)}else if(o===Mt)ne=null,Z(!1);else if(o===Nt){const e={x:n[1],y:n[2]};Uo=!0,B.zoom("in",B.worldToViewport(e))}else if(o===Bt){const e={x:n[1],y:n[2]};Uo=!0,B.zoom("out",B.worldToViewport(e))}else o===$t?a.togglePreview():o===Gt&&a.toggleSoundsEnabled();const l=x();So.handleInput(e,t,n,l,(e=>{j()&&s.play()})).length>0&&(Uo=!0),nn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===Et){const e=n[1],t=n[2];Uo=!0,B.move(e,t)}else if(e===Ot){if(ne){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-ne.x),l=Math.round(t.y-ne.y);Uo=!0,B.move(o,l),ne=t}}else if(e===_t){const e={x:n[1],y:n[2]};ne=B.worldToViewport(e)}else if(e===Mt)ne=null;else if(e===Nt){const e={x:n[1],y:n[2]};Uo=!0,B.zoom("in",B.worldToViewport(e))}else if(e===Bt){const e={x:n[1],y:n[2]};Uo=!0,B.zoom("out",B.worldToViewport(e))}else e===$t&&a.togglePreview()}F=!!So.getFinishTs(e),L()&&(M.update(),Uo=!0)},render:async()=>{if(!Uo)return;const n=x();let l,i,s;window.DEBUG&&Mn(0),N.fillStyle=W(),N.fillRect(0,0,v.width,v.height),window.DEBUG&&On("clear done"),l=B.worldToViewportRaw(I),i=B.worldDimToViewportRaw(D),N.fillStyle="rgba(255, 255, 255, .3)",N.fillRect(l.x,l.y,i.w,i.h),window.DEBUG&&On("board done");const r=So.getPiecesSortedByZIndex(e);window.DEBUG&&On("get tiles done"),i=B.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?No:Bo)&&(s=_[e.idx],l=B.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),N.drawImage(s,0,0,s.width,s.height,l.x,l.y,i.w,i.h));window.DEBUG&&On("tiles done");const d=[];for(const a of So.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(s=await f(a),l=B.worldToViewport(a),N.drawImage(s,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;N.fillStyle="white",N.textAlign="center";for(const[e,t,o]of d)N.fillText(e,t,o);window.DEBUG&&On("players done"),a.setActivePlayers(So.getActivePlayers(e,n)),a.setIdlePlayers(So.getIdlePlayers(e,n)),a.setPiecesDone(So.getFinishedPiecesCount(e)),window.DEBUG&&On("HUD done"),L()&&M.render(),Uo=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([Ut,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([Rt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Vt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},replayOnSpeedUp:()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,X())},replayOnPauseToggle:()=>{w.paused=!w.paused,X()},previewImageUrl:R,player:{background:W(),color:q(),name:So.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:j()},disconnect:nn.disconnect,connect:C,unload:()=>{ee(),te&&te.stop()}}}var Vo=e({name:"game",components:{PuzzleStatus:mt,Scores:ct,SettingsOverlay:ft,PreviewOverlay:Ct,ConnectionOverlay:on,HelpOverlay:cn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Ro(`${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)},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 $o={id:"game"},Go={class:"menu"},Fo={class:"tabs"},Lo=s("🧩 Puzzles");Vo.render=function(e,l,s,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",$o,[p(n(u,{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"]),[[C,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",Go,[n("div",Fo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Lo])),_: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 jo=e({name:"replay",components:{PuzzleStatus:mt,Scores:ct,SettingsOverlay:ft,PreviewOverlay:Ct,HelpOverlay:cn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Ro(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 Wo={id:"replay"},qo={class:"menu"},Ho={class:"tabs"},Yo=s("🧩 Puzzles");jo.render=function(e,l,s,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Wo,[p(n(g,{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"]),[[C,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick: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",qo,[n("div",Ho,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Yo])),_: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=k({history:A(),routes:[{name:"index",path:"/",component:L},{name:"new-game",path:"/new-game",component:et},{name:"game",path:"/g/:id",component:Vo},{name:"replay",path:"/replay/:id",component:jo}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=S(z);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=ae.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); diff --git a/build/public/assets/index.ab1d6e0f.js b/build/public/assets/index.ab1d6e0f.js new file mode 100644 index 0000000..d745c60 --- /dev/null +++ b/build/public/assets/index.ab1d6e0f.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as o,b as l,r as a,o as i,e as s,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as k,s as x,u as C,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const P={id:"app"},z={key:0,class:"nav"},I=s("Index"),D=s("New game");T.render=function(e,s,r,d,c,u){const p=a("router-link"),g=a("router-view");return i(),t("div",P,[e.showNav?(i(),t("ul",z,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[D])),_:1})])])):l("",!0),n(g)])};const E=864e5,_=e=>{const t=Math.floor(e/E);e%=E;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 M=1,N=1e3,O=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>_(t-e),U=_,R=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||O();return`${n} ${B(o,l)}`}}});const V={class:"game-info-text"},$=n("br",null,null,-1),G=n("br",null,null,-1),F=n("br",null,null,-1),L=s(" ↪️ Watch replay ");R.render=function(e,d,c,u,p,g){const h=a("router-link");return i(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",V,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),$,s(" 👥 "+r(e.game.players),1),G,s(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(i(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[L])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:R},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 W=n("h1",null,"Running games",-1),H=n("h1",null,"Finished games",-1);j.render=function(e,o,l,s,r,u){const p=a("game-teaser");return i(),t("div",null,[W,(i(!0),t(d,null,c(e.gamesRunning,((e,o)=>(i(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128)),H,(i(!0),t(d,null,c(e.gamesFinished,((e,o)=>(i(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128))])};var Y=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});Y.render=function(e,o,l,a,s,r){return i(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Q,q,Z,K,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:Y},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,s,r){const u=a("image-teaser");return i(),t("div",null,[(i(!0),t(d,null,c(e.images,((n,o)=>(i(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(q=Q||(Q={}))[q.Flat=0]="Flat",q[q.Out=1]="Out",q[q.In=-1]="In",(K=Z||(Z={}))[K.FINAL=0]="FINAL",K[K.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),i=le(o.getSeconds(),"00");console[t](`${l}:${a}:${i}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var ie={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Z.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const se={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};se.render=function(e,n,o,l,a,s){return i(),t("div",{style:s.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.key&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-39ed99c7");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,s,u,m)=>(i(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(i(),t("div",ce,[n("ul",null,[(i(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(i(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(i(!0),t(d,null,c(e.values,((n,o)=>(i(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-39ed99c7";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he={key:0,class:"has-image"},me={key:1},ye={class:"upload"},fe=n("span",{class:"btn"},"Upload File",-1),ve={class:"area-settings"},we=n("td",null,[n("label",null,"Title")],-1),be=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ke=n("td",null,[n("label",null,"Tags")],-1),xe={class:"area-buttons"},Ce=s("🧩 Post to gallery "),Ae=n("br",null,null,-1),Se=s(" + set up game");ge.render=function(e,o,l,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[e.previewUrl?(i(),t("div",he,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(i(),t("div",me,[n("label",ye,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),fe])]))],34),n("div",ve,[n("table",null,[n("tr",null,[we,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),be,n("tr",null,[ke,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",xe,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ce,Ae,Se],8,["disabled"])])])])};var Te=e({name:"edit-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Pe={class:"area-image"},ze={class:"has-image"},Ie={class:"area-settings"},De=n("td",null,[n("label",null,"Title")],-1),Ee=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),_e=n("td",null,[n("label",null,"Tags")],-1),Me={class:"area-buttons"};Te.render=function(e,o,l,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Pe,[n("div",ze,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ie,[n("table",null,[n("tr",null,[De,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ee,n("tr",null,[_e,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Me,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Ne=e({name:"new-game-dialog",components:{ResponsiveImage:se},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Z.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Oe={class:"area-image"},Be={class:"has-image"},Ue={key:0,class:"image-title"},Re={class:"area-settings"},Ve=n("td",null,[n("label",null,"Pieces")],-1),$e=n("td",null,[n("label",null,"Scoring: ")],-1),Ge=s(" Any (Score when pieces are connected to each other or on final location)"),Fe=n("br",null,null,-1),Le=s(" Final (Score when pieces are put to their final location)"),je=n("td",null,[n("label",null,"Shapes: ")],-1),We=s(" Normal"),He=n("br",null,null,-1),Ye=s(" Any (flat pieces can occur anywhere)"),Qe=n("br",null,null,-1),qe=s(" Flat (all pieces flat on all sides)"),Ze=n("td",null,[n("label",null,"Snapping: ")],-1),Ke=s(" Normal (pieces snap to final destination and to each other)"),Xe=n("br",null,null,-1),Je=s(" Real (pieces snap only to corners, already snapped pieces and to each other)"),et={class:"area-buttons"};Ne.render=function(e,o,s,d,c,h){const m=a("responsive-image");return i(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Oe,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(i(),t("div",Ue,'"'+r(e.image.title)+'"',1)):l("",!0)]),n("div",Re,[n("table",null,[n("tr",null,[Ve,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[$e,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Ge]),Fe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),Le])])]),n("tr",null,[je,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),We]),He,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ye]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),qe])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Ke]),Xe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),Je])])])])]),n("div",et,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var tt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Te,NewGameDialog:Ne},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${ie.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const nt={class:"upload-image-teaser"},ot=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),lt={key:0},at=s(" Tags: "),it=s(" Sort by: "),st=n("option",{value:"date_desc"},"Newest first",-1),rt=n("option",{value:"date_asc"},"Oldest first",-1),dt=n("option",{value:"alpha_asc"},"A-Z",-1),ct=n("option",{value:"alpha_desc"},"Z-A",-1);tt.render=function(e,o,s,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return i(),t("div",null,[n("div",nt,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),ot]),n("div",null,[e.tags.length>0?(i(),t("label",lt,[at,(i(!0),t(d,null,c(e.relevantTags,((n,o)=>(i(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[it,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[st,rt,dt,ct],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(i(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(i(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(i(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ut=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 pt={class:"scores"},gt=n("div",null,"Scores",-1),ht=n("td",null,"⚡",-1),mt=n("td",null,"💤",-1);ut.render=function(e,o,l,a,s,u){return i(),t("div",pt,[gt,n("table",null,[(i(!0),t(d,null,c(e.actives,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[ht,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(i(!0),t(d,null,c(e.idles,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[mt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var yt=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 U(this.duration)}}});const ft={class:"timer"};yt.render=function(e,o,l,a,s,d){return i(),t("div",ft,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var vt=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 wt=n("td",null,[n("label",null,"Background: ")],-1),bt=n("td",null,[n("label",null,"Color: ")],-1),kt=n("td",null,[n("label",null,"Name: ")],-1),xt=n("td",null,[n("label",null,"Sounds: ")],-1);vt.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:o[6]||(o[6]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[5]||(o[5]=u((()=>{}),["stop"]))},[n("tr",null,[wt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[bt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[kt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[xt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[k,e.modelValue.soundsEnabled]])])])])])};var Ct=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const At={class:"preview"};Ct.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",At,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var St=1,Tt=4,Pt=2,zt=3,It=2,Dt=4,Et=3,_t=9,Mt=1,Nt=2,Ot=3,Bt=4,Ut=5,Rt=6,Vt=7,$t=8,Gt=10,Ft=11,Lt=1,jt=2,Wt=3;const Ht=ae("Communication.js");let Yt,Qt=[],qt=e=>{Qt.push(e)},Zt=[],Kt=e=>{Zt.push(e)};let Xt=0;const Jt=e=>{Xt!==e&&(Xt=e,Kt(e))};function en(e){if(2===Xt)try{Yt.send(JSON.stringify(e))}catch(t){Ht.info("unable to send message.. maybe because ws is invalid?")}}let tn,nn;var on={connect:function(e,t,n){return tn=0,nn={},Jt(3),new Promise((o=>{Yt=new WebSocket(e,n+"|"+t),Yt.onopen=()=>{Jt(2),en([zt])},Yt.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Tt){const e=t[1];o(e)}else{if(l!==St)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&nn[o])return void delete nn[o];qt(t)}}},Yt.onerror=()=>{throw Jt(1),"[ 2021-05-15 onerror ]"},Yt.onclose=e=>{4e3===e.code||1001===e.code?Jt(4):Jt(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${ie.asQueryArgs(n)}`);return await o.json()},disconnect:function(){Yt&&Yt.close(4e3),tn=0,nn={}},sendClientEvent:function(e){tn++,nn[tn]=e,en([Pt,tn,nn[tn]])},onServerChange:function(e){qt=e;for(const t of Qt)qt(t);Qt=[]},onConnectionStateChange:function(e){Kt=e;for(const t of Zt)Kt(t);Zt=[]},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},ln=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===on.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===on.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const an={key:0,class:"overlay connection-lost"},sn={key:0,class:"overlay-content"},rn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),dn={key:1,class:"overlay-content"},cn=n("div",null,"Connecting...",-1);ln.render=function(e,o,a,s,r,d){return e.show?(i(),t("div",an,[e.lostConnection?(i(),t("div",sn,[rn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(i(),t("div",dn,[cn])):l("",!0)])):l("",!0)};var un=e({name:"help-overlay",emits:{bgclick:null}});const pn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),gn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),hn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),mn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),yn=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),fn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),vn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),wn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),bn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),kn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),xn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1);un.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:o[2]||(o[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:o[1]||(o[1]=u((()=>{}),["stop"]))},[pn,gn,hn,mn,yn,fn,vn,wn,bn,kn,xn])])};var Cn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),An=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="}),Tn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Pn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function zn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),i=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function In(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Dn={createCanvas:In,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=In(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=In(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(),o}};const En=ae("Debug.js");let _n=0,Mn=0;var Nn=e=>{_n=performance.now(),Mn=e},On=e=>{const t=performance.now(),n=t-_n;n>Mn&&En.log(e+": "+n),_n=t};function Bn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function Un(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var Rn={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:Bn,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:Un,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 Bn(Un(e),Un(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const Vn=ae("PuzzleGraphics.js");function $n(e,t){const n=ie.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Gn={loadPuzzleBitmaps:async function(e){const t=await Dn.loadImageToBitmap(e.info.imageUrl),n=await Dn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Vn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,i=o/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=Rn.pointAdd(a,{x:o,y:0}),c=Rn.pointAdd(r,{x:0,y:o}),u=Rn.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;oie.decodePiece(Fn[e].puzzle.tiles[t]),no=(e,t)=>to(e,t).group,oo=(e,t)=>{const n=Fn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},lo=(e,t)=>{const n=Fn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Fn[e].puzzle.info,o=ie.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return Rn.pointAdd(o,l)},ao=(e,t)=>to(e,t).pos,io=e=>{const t=Ao(e),n=So(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}},so=(e,t)=>{const n=po(e),o=to(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},ro=(e,t)=>to(e,t).z,co=(e,t)=>{for(const n of Fn[e].puzzle.tiles){const e=ie.decodePiece(n);if(e.owner===t)return e.idx}return-1},uo=e=>Fn[e].puzzle.info.tileDrawSize,po=e=>Fn[e].puzzle.info.tileSize,go=e=>Fn[e].puzzle.data.maxGroup,ho=e=>Fn[e].puzzle.data.maxZ;function mo(e,t){const n=Fn[e].puzzle.info,o=ie.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const yo=(e,t,n)=>{for(const o of t)eo(e,o,{z:n})},fo=(e,t,n)=>{const o=ao(e,t);eo(e,t,{pos:Rn.pointAdd(o,n)})},vo=(e,t,n)=>{const o=uo(e),l=io(e),a=n;for(const i of t){const t=to(e,i);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const i of t)fo(e,i,a)},wo=(e,t)=>to(e,t).owner,bo=(e,t)=>{for(const n of t)eo(e,n,{owner:-1,z:1})},ko=(e,t,n)=>{for(const o of t)eo(e,o,{owner:n})};function xo(e,t){const n=Fn[e].puzzle.tiles,o=ie.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=ie.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const Co=(e,t)=>{const n=jn(e,t);return n?n.points:0},Ao=e=>Fn[e].puzzle.info.table.width,So=e=>Fn[e].puzzle.info.table.height;var To={setGame:function(e,t){Fn[e]=t},exists:function(e){return!!Fn[e]||!1},playerExists:Hn,getActivePlayers:function(e,t){const n=t-30*N;return Yn(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*N;return Yn(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Hn(e,t)?Xn(e,t,{ts:n}):Wn(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:Kn,getPieceCount:Qn,getImageUrl:function(e){return Fn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Fn[e].puzzle.info.imageUrl=t},get:function(e){return Fn[e]||null},getAllGames:function(){return Object.values(Fn).sort(((e,t)=>Zn(e.id)===Zn(t.id)?t.puzzle.data.started-e.puzzle.data.started:Zn(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=jn(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=jn(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=jn(e,t);return n?n.name:null},getPlayerIndexById:Ln,getPlayerIdByIndex:function(e,t){return Fn[e].players.length>t?ie.decodePlayer(Fn[e].players[t]).id:null},changePlayer:Xn,setPlayer:Wn,setPiece:function(e,t,n){Fn[e].puzzle.tiles[t]=ie.encodePiece(n)},setPuzzleData:function(e,t){Fn[e].puzzle.data=t},getTableWidth:Ao,getTableHeight:So,getPuzzle:e=>Fn[e].puzzle,getRng:e=>Fn[e].rng.obj,getPuzzleWidth:e=>Fn[e].puzzle.info.width,getPuzzleHeight:e=>Fn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Fn[e].puzzle.tiles.map(ie.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=co(e,t);return n<0?null:Fn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Fn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:uo,getFinalPiecePos:lo,getStartTs:e=>Fn[e].puzzle.data.started,getFinishTs:e=>Fn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Fn[e].puzzle,i=function(e,t){return t in Fn[e].evtInfos?Fn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],r=()=>{s.push([Lt,a.data])},d=t=>{s.push([jt,ie.encodePiece(to(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=jn(e,t);n&&s.push([Wt,ie.encodePlayer(n)])},p=n[0];if(p===Rt){const l=n[1];Xn(e,t,{bgcolor:l,ts:o}),u()}else if(p===Vt){const l=n[1];Xn(e,t,{color:l,ts:o}),u()}else if(p===$t){const l=`${n[1]}`.substr(0,16);Xn(e,t,{name:l,ts:o}),u()}else if(p===_t){const l=n[1],a=n[2],i=jn(e,t);if(i){const n=i.x-l,s=i.y-a;Xn(e,t,{ts:o,x:n,y:s}),u()}}else if(p===Mt){const l={x:n[1],y:n[2]};Xn(e,t,{d:1,ts:o}),u(),i._last_mouse_down=l;const a=((e,t)=>{const n=Fn[e].puzzle.info,o=Fn[e].puzzle.tiles;let l=-1,a=-1;for(let i=0;il)&&(l=e.z,a=i)}return a})(e,l);if(a>=0){const n=ho(e)+1;Jn(e,{maxZ:n}),r();const o=xo(e,a);yo(e,o,ho(e)),ko(e,o,t),c(o)}i._last_mouse=l}else if(p===Ot){const l=n[1],a=n[2],s={x:l,y:a};if(null===i._last_mouse_down)Xn(e,t,{x:l,y:a,ts:o}),u();else{const n=co(e,t);if(n>=0){Xn(e,t,{x:l,y:a,ts:o}),u();const r=xo(e,n);let d=Rn.pointInBounds(s,io(e))&&Rn.pointInBounds(i._last_mouse_down,io(e));for(const t of r){const n=so(e,t);if(Rn.pointInBounds(s,n)){d=!0;break}}if(d){const t=l-i._last_mouse_down.x,n=a-i._last_mouse_down.y;vo(e,r,{x:t,y:n}),c(r)}}else Xn(e,t,{ts:o}),u();i._last_mouse_down=s}i._last_mouse=s}else if(p===Nt){const s={x:n[1],y:n[2]},p=0;i._last_mouse_down=null;const g=co(e,t);if(g>=0){const n=xo(e,g);ko(e,n,0),c(n);const i=ao(e,g),s=lo(e,g);let h=!1;if(function(e){return Fn[e].snapMode||ee.NORMAL}(e)===ee.REAL){for(const t of n)if(oo(e,t)){h=!0;break}}else h=!0;if(h&&Rn.pointDistance(s,i){const l=Fn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=no(e,t),l=no(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=ao(e,t),i=Rn.pointAdd(ao(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(Rn.pointDistance(a,i){const o=Fn[e].puzzle.tiles,l=no(e,t),a=no(e,n);let i;const s=[];l&&s.push(l),a&&s.push(a),l?i=l:a?i=a:(Jn(e,{maxGroup:go(e)+1}),r(),i=go(e));if(eo(e,t,{group:i}),d(t),eo(e,n,{group:i}),d(n),s.length>0)for(const r of o){const t=ie.decodePiece(r);s.includes(t.group)&&(eo(e,t.idx,{group:i}),d(t.idx))}})(e,t,n),l=xo(e,t),((e,t)=>-1===wo(e,t))(e,n))bo(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=ro(e,o);t>n&&(n=t)}return n})(e,l);yo(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of xo(e,g)){const o=mo(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])){a=!0;break}}if(a&&qn(e)===Z.ANY){const n=Co(e,t)+1;Xn(e,t,{d:p,ts:o,points:n}),u()}else Xn(e,t,{d:p,ts:o}),u();a&&l&&l(t)}}else Xn(e,t,{d:p,ts:o}),u();i._last_mouse=s}else if(p===Bt){const l=n[1],a=n[2];Xn(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else if(p===Ut){const l=n[1],a=n[2];Xn(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else Xn(e,t,{ts:o}),u();return function(e,t,n){Fn[e].evtInfos[t]=n}(e,t,i),s}};let Po=-10,zo=20,Io=2,Do=15;class Eo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Po+Math.random()*zo,this.vy=-1*(Io+Math.random()*Do),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Io=t/2,Do=t-Io;const n=1/4*this.canvas.width/(t/2);Po=-n,zo=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Eo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Eo(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(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Dn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Ro=!0})),t}(l,Dn.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};on.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await on.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let k=()=>0;const x=async()=>{if("play"===o){const o=await on.connect(n,e,t),l=ie.decodeGame(o);To.setGame(l.id,l),k=()=>O()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=ie.decodeGame(t.game);To.setGame(n.id,n),w.lastRealTs=O(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,k=()=>w.lastGameTs}}Ro=!0};await x();const C=To.getPieceDrawOffset(e),A=To.getPieceDrawSize(e),S=To.getPuzzleWidth(e),T=To.getPuzzleHeight(e),P=To.getTableWidth(e),z=To.getTableHeight(e),I={x:(P-S)/2,y:(z-T)/2},D={w:S,h:T},E={w:A,h:A},_=await Gn.loadPuzzleBitmaps(To.getPuzzle(e)),N=new Mo(v,To.getRng(e));N.init();const B=v.getContext("2d");v.classList.add("loaded");const U=zn();U.move(-(P-v.width)/2,-(z-v.height)/2);const R=function(e,t,n){let o=[],l=!0,a=!1,i=!1,s=!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]},g=e=>p(e.offsetX,e.offsetY),h=()=>p(e.width/2,e.height/2),m=(e,t)=>{l&&("Shift"===t.key?u=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?s=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?r=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?a=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?i=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};let y=null;e.addEventListener("mousedown",(e=>{y=g(e),0===e.button&&f([Mt,...y])})),e.addEventListener("mouseup",(e=>{y=g(e),0===e.button&&f([Nt,...y])})),e.addEventListener("mousemove",(e=>{y=g(e),f([Ot,...y])})),e.addEventListener("wheel",(e=>{if(y=g(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Bt:Ut;f([t,...y])}})),t.addEventListener("keydown",(e=>m(!0,e))),t.addEventListener("keyup",(e=>m(!1,e))),t.addEventListener("keypress",(e=>{l&&(" "===e.key&&f([Gt]),"F"!==e.key&&"f"!==e.key||(Bo=!Bo,Ro=!0),"G"!==e.key&&"g"!==e.key||(Uo=!Uo,Ro=!0),"M"!==e.key&&"m"!==e.key||f([Ft]))}));const f=e=>{o.push(e)};return{addEvent:f,consumeAll:()=>{if(0===o.length)return[];const e=o.slice();return o=[],e},createKeyEvents:()=>{const e=(a?1:0)-(i?1:0),t=(s?1:0)-(r?1:0);if(0!==e||0!==t){const o=(u?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});f([_t,l.w,l.h]),y&&(y[0]-=l.w,y[1]-=l.h)}if(d&&c);else if(d){if(n.canZoom("in")){const e=y||h();f([Bt,...e])}}else if(c&&n.canZoom("out")){const e=y||h();f([Ut,...e])}},setHotkeys:e=>{l=e}}}(v,window,U),V=To.getImageUrl(e),$=()=>{const t=To.getStartTs(e),n=To.getFinishTs(e),o=k();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(To.getFinishedPiecesCount(e)),a.setPiecesTotal(To.getPieceCount(e));const G=k();a.setActivePlayers(To.getActivePlayers(e,G)),a.setIdlePlayers(To.getIdlePlayers(e,G));const F=!!To.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},H=()=>To.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Y=()=>To.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let Q="",q="",Z=!1;const K=e=>{Z=e;const[t,n]=e?[Q,"grab"]:[q,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},X=e=>{Q=Dn.colorizedCanvas(r,c,e).toDataURL(),q=Dn.colorizedCanvas(d,u,e).toDataURL(),K(Z)};X(Y());const J=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ee=[];let te;let ne;if("play"===o?ee.push(setInterval((()=>{$()}),1e3)):"replay"===o&&J(),"play"===o)on.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Wt:{const n=ie.decodePlayer(a);n.id!==t&&(To.setPlayer(e,n.id,n),Ro=!0)}break;case jt:{const t=ie.decodePiece(a);To.setPiece(e,t.idx,t),Ro=!0}break;case Lt:To.setPuzzleData(e,a),Ro=!0}L=!!To.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===It){const t=o[1];return To.addPlayer(e,t,n),!0}if(o[0]===Dt){const t=To.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return To.addPlayer(e,t,n),!0}if(o[0]===Et){const t=To.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return To.handleInput(e,t,l,n),!0}return!1},n=async()=>{w.logPointer+1>=w.log.length&&await b(e);const o=O();if(w.paused)return w.lastRealTs=o,void(te=setTimeout(n,50));const l=(o-w.lastRealTs)*w.speeds[w.speedIdx];let a=w.lastGameTs+l;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const n=w.log[w.logPointer],o=w.gameStartTs+n[n.length-1],l=w.log[e],i=w.gameStartTs+l[l.length-1];if(i>a){if(w.skipNonActionPhases&&a+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,i=window.requestAnimationFrame,s=1/n,r=o*s;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(s);a(c/o),u=d,t||i(p)};return i(p),{stop:()=>{t=!0}}})({update:()=>{R.createKeyEvents();for(const n of R.consumeAll())if("play"===o){const o=n[0];if(o===_t){const e=n[1],t=n[2],o=U.worldDimToViewport({w:e,h:t});Ro=!0,U.move(o.w,o.h)}else if(o===Ot){if(oe&&!To.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-oe.x),l=Math.round(t.y-oe.y);Ro=!0,U.move(o,l),oe=t}}else if(o===Vt)X(n[1]);else if(o===Mt){const e={x:n[1],y:n[2]};oe=U.worldToViewport(e),K(!0)}else if(o===Nt)oe=null,K(!1);else if(o===Bt){const e={x:n[1],y:n[2]};Ro=!0,U.zoom("in",U.worldToViewport(e))}else if(o===Ut){const e={x:n[1],y:n[2]};Ro=!0,U.zoom("out",U.worldToViewport(e))}else o===Gt?a.togglePreview():o===Ft&&a.toggleSoundsEnabled();const l=k();To.handleInput(e,t,n,l,(e=>{W()&&s.play()})).length>0&&(Ro=!0),on.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===_t){const e=n[1],t=n[2];Ro=!0,U.move(e,t)}else if(e===Ot){if(oe){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-oe.x),l=Math.round(t.y-oe.y);Ro=!0,U.move(o,l),oe=t}}else if(e===Mt){const e={x:n[1],y:n[2]};oe=U.worldToViewport(e)}else if(e===Nt)oe=null;else if(e===Bt){const e={x:n[1],y:n[2]};Ro=!0,U.zoom("in",U.worldToViewport(e))}else if(e===Ut){const e={x:n[1],y:n[2]};Ro=!0,U.zoom("out",U.worldToViewport(e))}else e===Gt&&a.togglePreview()}L=!!To.getFinishTs(e),j()&&(N.update(),Ro=!0)},render:async()=>{if(!Ro)return;const n=k();let l,i,s;window.DEBUG&&Nn(0),B.fillStyle=H(),B.fillRect(0,0,v.width,v.height),window.DEBUG&&On("clear done"),l=U.worldToViewportRaw(I),i=U.worldDimToViewportRaw(D),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(l.x,l.y,i.w,i.h),window.DEBUG&&On("board done");const r=To.getPiecesSortedByZIndex(e);window.DEBUG&&On("get tiles done"),i=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?Bo:Uo)&&(s=_[e.idx],l=U.worldToViewportRaw({x:C+e.pos.x,y:C+e.pos.y}),B.drawImage(s,0,0,s.width,s.height,l.x,l.y,i.w,i.h));window.DEBUG&&On("tiles done");const d=[];for(const a of To.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(s=await f(a),l=U.worldToViewport(a),B.drawImage(s,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,o]of d)B.fillText(e,t,o);window.DEBUG&&On("players done"),a.setActivePlayers(To.getActivePlayers(e,n)),a.setIdlePlayers(To.getIdlePlayers(e,n)),a.setPiecesDone(To.getFinishedPiecesCount(e)),window.DEBUG&&On("HUD done"),j()&&N.render(),Ro=!1}}),{setHotkeys:e=>{R.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),R.addEvent([Rt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),R.addEvent([Vt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),R.addEvent([$t,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},replayOnSpeedUp:()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,J())},replayOnPauseToggle:()=>{w.paused=!w.paused,J()},replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:V,player:{background:H(),color:Y(),name:To.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:W()},disconnect:on.disconnect,connect:x,unload:()=>{ee.forEach((e=>{clearInterval(e)})),te&&clearTimeout(te),ne&&ne.stop()}}}var $o=e({name:"game",components:{PuzzleStatus:yt,Scores:ut,SettingsOverlay:vt,PreviewOverlay:Ct,ConnectionOverlay:ln,HelpOverlay:un},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Vo(`${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)},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 Go={id:"game"},Fo={class:"menu"},Lo={class:"tabs"},jo=s("🧩 Puzzles");$o.render=function(e,l,s,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Go,[p(n(u,{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"]),[[x,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",Fo,[n("div",Lo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[jo])),_: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 Wo=e({name:"replay",components:{PuzzleStatus:yt,Scores:ut,SettingsOverlay:vt,PreviewOverlay:Ct,HelpOverlay:un},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Vo(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),this.g.disconnect()},methods:{toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}},computed:{replayText(){return"Replay-Speed: "+this.replay.speed+"x"+(this.replay.paused?" Paused":"")}}});const Ho={id:"replay"},Yo=s("Skip no action phases: "),Qo={class:"menu"},qo={class:"tabs"},Zo=s("🧩 Puzzles");Wo.render=function(e,l,s,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Ho,[p(n(g,{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"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[Yo,p(n("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[k,e.skipNoAction]])])]),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",Qo,[n("div",qo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Zo])),_:1}),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:l[12]||(l[12]=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=C({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:tt},{name:"game",path:"/g/:id",component:$o},{name:"replay",path:"/replay/:id",component:Wo}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=S(T);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=ie.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 2fe3679..456eedc 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,7 +4,7 @@ 🧩 jigsaw.hyottoko.club - + diff --git a/build/server/main.js b/build/server/main.js index 6a02829..c87b04b 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -3,8 +3,6 @@ import express from 'express'; import compression from 'compression'; import multer from 'multer'; import fs from 'fs'; -import readline from 'readline'; -import stream from 'stream'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; import sizeOf from 'image-size'; @@ -1249,6 +1247,7 @@ const PUBLIC_DIR = `${BASE_DIR}/build/public/`; const DB_PATCHES_DIR = `${BASE_DIR}/src/dbpatches`; const DB_FILE = `${BASE_DIR}/data/db.sqlite`; +const LINES_PER_LOG_FILE = 10000; const POST_GAME_LOG_DURATION = 5 * Time.MIN; const shouldLog = (finishTs, currentTs) => { // when not finished yet, always log @@ -1260,54 +1259,52 @@ const shouldLog = (finishTs, currentTs) => { const timeSinceGameEnd = currentTs - finishTs; return timeSinceGameEnd <= POST_GAME_LOG_DURATION; }; -const filename = (gameId) => `${DATA_DIR}/log_${gameId}.log`; +const filename = (gameId, offset) => `${DATA_DIR}/log_${gameId}-${offset}.log`; +const idxname = (gameId) => `${DATA_DIR}/log_${gameId}.idx.log`; const create = (gameId) => { - const file = filename(gameId); - if (!fs.existsSync(file)) { - fs.appendFileSync(file, ''); + const idxfile = idxname(gameId); + if (!fs.existsSync(idxfile)) { + const logfile = filename(gameId, 0); + fs.appendFileSync(logfile, ""); + fs.appendFileSync(idxfile, JSON.stringify({ + total: 0, + currentFile: logfile, + perFile: LINES_PER_LOG_FILE, + })); } }; const exists = (gameId) => { - const file = filename(gameId); - return fs.existsSync(file); + const idxfile = idxname(gameId); + return fs.existsSync(idxfile); }; const _log = (gameId, ...args) => { - const file = filename(gameId); - if (!fs.existsSync(file)) { + const idxfile = idxname(gameId); + if (!fs.existsSync(idxfile)) { return; } - const str = JSON.stringify(args); - fs.appendFileSync(file, str + "\n"); + const idx = JSON.parse(fs.readFileSync(idxfile, 'utf-8')); + idx.total++; + fs.appendFileSync(idx.currentFile, JSON.stringify(args) + "\n"); + // prepare next log file + if (idx.total % idx.perFile === 0) { + const logfile = filename(gameId, idx.total); + fs.appendFileSync(logfile, ""); + idx.currentFile = logfile; + } + fs.writeFileSync(idxfile, JSON.stringify(idx)); }; -const get = async (gameId, offset = 0, size = 10000) => { - const file = filename(gameId); +const get = (gameId, offset = 0) => { + const idxfile = idxname(gameId); + if (!fs.existsSync(idxfile)) { + return []; + } + const file = filename(gameId, offset); if (!fs.existsSync(file)) { return []; } - return new Promise((resolve) => { - const instream = fs.createReadStream(file); - const outstream = new stream.Writable(); - const rl = readline.createInterface(instream, outstream); - const lines = []; - let i = -1; - rl.on('line', (line) => { - if (!line) { - // skip empty - return; - } - i++; - if (offset > i) { - return; - } - if (offset + size <= i) { - rl.close(); - return; - } - lines.push(JSON.parse(line)); - }); - rl.on('close', () => { - resolve(lines); - }); + const log = fs.readFileSync(file, 'utf-8').split("\n"); + return log.map(line => { + return JSON.parse(line); }); }; var GameLog = { @@ -2061,7 +2058,7 @@ app.get('/api/replay-data', async (req, res) => { res.status(404).send({ reason: 'no log found' }); return; } - const log = await GameLog.get(gameId, offset, size); + const log = GameLog.get(gameId, offset); let game = null; if (offset === 0) { // also need the game diff --git a/scripts/split_logs.ts b/scripts/split_logs.ts new file mode 100644 index 0000000..3da036f --- /dev/null +++ b/scripts/split_logs.ts @@ -0,0 +1,72 @@ +import fs from 'fs' +import readline from 'readline' +import stream from 'stream' +import { logger } from '../src/common/Util' +import { DATA_DIR } from '../src/server/Dirs' + +const log = logger('rewrite_logs') + +const doit = (file: string): Promise => { + const filename = (offset: number) => file.replace(/\.log$/, `-${offset}.log`) + const idxname = () => file.replace(/\.log$/, `.idx.log`) + + let perfile = 10000 + const idx = { + total: 0, + currentFile: '', + perFile: perfile, + } + + return new Promise((resolve) => { + const instream = fs.createReadStream(DATA_DIR + '/' + file) + const outstream = new stream.Writable() + const rl = readline.createInterface(instream, outstream) + + + let lines: any[] = [] + let offset = 0 + let count = 0 + rl.on('line', (line) => { + if (!line) { + // skip empty + return + } + count++ + lines.push(line) + if (count >= perfile) { + const fn = filename(offset) + idx.currentFile = fn + idx.total += count + fs.writeFileSync(DATA_DIR + '/' + fn, lines.join("\n")) + count = 0 + offset += perfile + lines = [] + } + }) + + rl.on('close', () => { + if (count > 0) { + const fn = filename(offset) + idx.currentFile = fn + idx.total += count + fs.writeFileSync(DATA_DIR + '/' + fn, lines.join("\n")) + count = 0 + offset += perfile + lines = [] + } + + fs.writeFileSync(DATA_DIR + '/' + idxname(), JSON.stringify(idx)) + resolve() + }) + }) +} + +let logs = fs.readdirSync(DATA_DIR) + .filter(f => f.toLowerCase().match(/^log_.*\.log$/)) + + +;(async () => { + for (const file of logs) { + await doit(file) + } +})() diff --git a/src/frontend/Communication.ts b/src/frontend/Communication.ts index 5301faa..70a61c8 100644 --- a/src/frontend/Communication.ts +++ b/src/frontend/Communication.ts @@ -114,10 +114,9 @@ function connect( async function requestReplayData( gameId: string, - offset: number, - size: number + offset: number ): Promise { - const args = { gameId, offset, size } + const args = { gameId, offset } const res = await fetch(`/api/replay-data${Util.asQueryArgs(args)}`) const json: ReplayData = await res.json() return json diff --git a/src/frontend/game.ts b/src/frontend/game.ts index 7a14e42..2a7d3fd 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -69,7 +69,6 @@ interface Replay { skipNonActionPhases: boolean // dataOffset: number - dataSize: number } const shouldDrawPiece = (piece: Piece) => { @@ -301,9 +300,8 @@ export async function main( lastRealTs: 0, lastGameTs: 0, gameStartTs: 0, - skipNonActionPhases: false, + skipNonActionPhases: true, dataOffset: 0, - dataSize: 10000, } Communication.onConnectionStateChange((state) => { @@ -314,11 +312,10 @@ export async function main( gameId: string ): Promise => { const offset = REPLAY.dataOffset - REPLAY.dataOffset += REPLAY.dataSize + REPLAY.dataOffset += 10000 // meh const replay: ReplayData = await Communication.requestReplayData( gameId, - offset, - REPLAY.dataSize + offset ) // cut log that was already handled @@ -326,7 +323,7 @@ export async function main( REPLAY.logPointer = 0 REPLAY.log.push(...replay.log) - if (replay.log.length < REPLAY.dataSize) { + if (replay.log.length === 0) { REPLAY.final = true } return replay @@ -340,10 +337,6 @@ export async function main( Game.setGame(gameObject.id, gameObject) TIME = () => Time.timestamp() } else if (MODE === MODE_REPLAY) { - REPLAY.logPointer = 0 - REPLAY.dataSize = 10000 - REPLAY.speeds = [0.5, 1, 2, 5, 10, 20, 50, 100, 250, 500] - REPLAY.speedIdx = 1 const replay: ReplayData = await queryNextReplayBatch(gameId) if (!replay.game) { throw '[ 2021-05-29 no game received ]' @@ -354,8 +347,6 @@ export async function main( REPLAY.lastRealTs = Time.timestamp() REPLAY.gameStartTs = parseInt(replay.log[0][4], 10) REPLAY.lastGameTs = REPLAY.gameStartTs - REPLAY.paused = false - REPLAY.skipNonActionPhases = false TIME = () => REPLAY.lastGameTs } else { @@ -493,6 +484,10 @@ export async function main( doSetSpeedStatus() } + const replayOnSkipToggle = () => { + REPLAY.skipNonActionPhases = !REPLAY.skipNonActionPhases + } + const intervals: NodeJS.Timeout[] = [] let to: NodeJS.Timeout const clearIntervals = () => { @@ -520,9 +515,6 @@ export async function main( doSetSpeedStatus() } - // // TODO: remove (make changable via interface) - // REPLAY.skipNonActionPhases = true - if (MODE === MODE_PLAY) { Communication.onServerChange((msg: ServerEvent) => { const msgType = msg[0] @@ -608,10 +600,8 @@ export async function main( const nextTs: Timestamp = REPLAY.gameStartTs + nextLogEntry[nextLogEntry.length - 1] if (nextTs > maxGameTs) { // next log entry is too far into the future - if (REPLAY.skipNonActionPhases && (maxGameTs + 50 < nextTs)) { + if (REPLAY.skipNonActionPhases && (maxGameTs + 500 * Time.MS < nextTs)) { const skipInterval = nextTs - currTs - // lets skip to the next log entry - // log.info('skipping non-action, from', maxGameTs, skipInterval) maxGameTs += skipInterval } break @@ -874,6 +864,7 @@ export async function main( replayOnSpeedUp, replayOnSpeedDown, replayOnPauseToggle, + replayOnSkipToggle, previewImageUrl, player: { background: playerBgColor(), diff --git a/src/frontend/views/Replay.vue b/src/frontend/views/Replay.vue index d846dbc..74bcbba 100644 --- a/src/frontend/views/Replay.vue +++ b/src/frontend/views/Replay.vue @@ -12,6 +12,12 @@ >
{{replayText}}
+
+ +
@@ -59,6 +65,7 @@ export default defineComponent({ duration: 0, piecesDone: 0, piecesTotal: 0, + skipNoAction: true, overlay: '', @@ -80,6 +87,7 @@ export default defineComponent({ replayOnSpeedUp: () => {}, replayOnSpeedDown: () => {}, replayOnPauseToggle: () => {}, + replayOnSkipToggle: () => {}, connect: () => {}, disconnect: () => {}, unload: () => {}, diff --git a/src/server/GameLog.ts b/src/server/GameLog.ts index d764304..0c016ad 100644 --- a/src/server/GameLog.ts +++ b/src/server/GameLog.ts @@ -8,6 +8,7 @@ import { DATA_DIR } from './../server/Dirs' const log = logger('GameLog.js') +const LINES_PER_LOG_FILE = 10000 const POST_GAME_LOG_DURATION = 5 * Time.MIN const shouldLog = (finishTs: Timestamp, currentTs: Timestamp): boolean => { @@ -22,62 +23,63 @@ const shouldLog = (finishTs: Timestamp, currentTs: Timestamp): boolean => { return timeSinceGameEnd <= POST_GAME_LOG_DURATION } -const filename = (gameId: string) => `${DATA_DIR}/log_${gameId}.log` +const filename = (gameId: string, offset: number) => `${DATA_DIR}/log_${gameId}-${offset}.log` +const idxname = (gameId: string) => `${DATA_DIR}/log_${gameId}.idx.log` const create = (gameId: string): void => { - const file = filename(gameId) - if (!fs.existsSync(file)) { - fs.appendFileSync(file, '') + const idxfile = idxname(gameId) + if (!fs.existsSync(idxfile)) { + const logfile = filename(gameId, 0) + fs.appendFileSync(logfile, "") + fs.appendFileSync(idxfile, JSON.stringify({ + total: 0, + currentFile: logfile, + perFile: LINES_PER_LOG_FILE, + })) } } const exists = (gameId: string): boolean => { - const file = filename(gameId) - return fs.existsSync(file) + const idxfile = idxname(gameId) + return fs.existsSync(idxfile) } const _log = (gameId: string, ...args: Array): void => { - const file = filename(gameId) - if (!fs.existsSync(file)) { + const idxfile = idxname(gameId) + if (!fs.existsSync(idxfile)) { return } - const str = JSON.stringify(args) - fs.appendFileSync(file, str + "\n") + + const idx = JSON.parse(fs.readFileSync(idxfile, 'utf-8')) + idx.total++ + fs.appendFileSync(idx.currentFile, JSON.stringify(args) + "\n") + + // prepare next log file + if (idx.total % idx.perFile === 0) { + const logfile = filename(gameId, idx.total) + fs.appendFileSync(logfile, "") + idx.currentFile = logfile + } + fs.writeFileSync(idxfile, JSON.stringify(idx)) } -const get = async ( +const get = ( gameId: string, offset: number = 0, - size: number = 10000 -): Promise => { - const file = filename(gameId) +): any[] => { + const idxfile = idxname(gameId) + if (!fs.existsSync(idxfile)) { + return [] + } + + const file = filename(gameId, offset) if (!fs.existsSync(file)) { return [] } - return new Promise((resolve) => { - const instream = fs.createReadStream(file) - const outstream = new stream.Writable() - const rl = readline.createInterface(instream, outstream) - const lines: any[] = [] - let i = -1 - rl.on('line', (line) => { - if (!line) { - // skip empty - return - } - i++ - if (offset > i) { - return - } - if (offset + size <= i) { - rl.close() - return - } - lines.push(JSON.parse(line)) - }) - rl.on('close', () => { - resolve(lines) - }) + + const log = fs.readFileSync(file, 'utf-8').split("\n") + return log.map(line => { + return JSON.parse(line) }) } diff --git a/src/server/main.ts b/src/server/main.ts index 215ec6f..4d8e185 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -80,7 +80,7 @@ app.get('/api/replay-data', async (req, res): Promise => { res.status(404).send({ reason: 'no log found' }) return } - const log = await GameLog.get(gameId, offset, size) + const log = GameLog.get(gameId, offset) let game: GameType|null = null if (offset === 0) { // also need the game From 3447681f103dfb4683ba4be6fbd064dafb52eedf Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 5 Jun 2021 17:45:55 +0200 Subject: [PATCH 03/45] add hotkeys for replay speed up/down pause --- build/public/assets/index.ab1d6e0f.js | 1 - build/public/assets/index.c6197c7d.js | 1 + build/public/index.html | 2 +- build/server/main.js | 6 +++++ src/common/Protocol.ts | 8 +++++++ src/frontend/components/HelpOverlay.vue | 4 ++++ src/frontend/game.ts | 31 ++++++++++++++++++++++--- 7 files changed, 48 insertions(+), 5 deletions(-) delete mode 100644 build/public/assets/index.ab1d6e0f.js create mode 100644 build/public/assets/index.c6197c7d.js diff --git a/build/public/assets/index.ab1d6e0f.js b/build/public/assets/index.ab1d6e0f.js deleted file mode 100644 index d745c60..0000000 --- a/build/public/assets/index.ab1d6e0f.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as n,w as o,b as l,r as a,o as i,e as s,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as k,s as x,u as C,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const P={id:"app"},z={key:0,class:"nav"},I=s("Index"),D=s("New game");T.render=function(e,s,r,d,c,u){const p=a("router-link"),g=a("router-view");return i(),t("div",P,[e.showNav?(i(),t("ul",z,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[D])),_:1})])])):l("",!0),n(g)])};const E=864e5,_=e=>{const t=Math.floor(e/E);e%=E;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 M=1,N=1e3,O=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>_(t-e),U=_,R=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||O();return`${n} ${B(o,l)}`}}});const V={class:"game-info-text"},$=n("br",null,null,-1),G=n("br",null,null,-1),F=n("br",null,null,-1),L=s(" ↪️ Watch replay ");R.render=function(e,d,c,u,p,g){const h=a("router-link");return i(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",V,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),$,s(" 👥 "+r(e.game.players),1),G,s(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(i(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[L])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:R},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 W=n("h1",null,"Running games",-1),H=n("h1",null,"Finished games",-1);j.render=function(e,o,l,s,r,u){const p=a("game-teaser");return i(),t("div",null,[W,(i(!0),t(d,null,c(e.gamesRunning,((e,o)=>(i(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128)),H,(i(!0),t(d,null,c(e.gamesFinished,((e,o)=>(i(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128))])};var Y=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});Y.render=function(e,o,l,a,s,r){return i(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Q,q,Z,K,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:Y},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,s,r){const u=a("image-teaser");return i(),t("div",null,[(i(!0),t(d,null,c(e.images,((n,o)=>(i(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(q=Q||(Q={}))[q.Flat=0]="Flat",q[q.Out=1]="Out",q[q.In=-1]="In",(K=Z||(Z={}))[K.FINAL=0]="FINAL",K[K.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),i=le(o.getSeconds(),"00");console[t](`${l}:${a}:${i}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var ie={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Z.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const se={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};se.render=function(e,n,o,l,a,s){return i(),t("div",{style:s.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.key&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-39ed99c7");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,s,u,m)=>(i(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(i(),t("div",ce,[n("ul",null,[(i(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(i(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(i(!0),t(d,null,c(e.values,((n,o)=>(i(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-39ed99c7";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he={key:0,class:"has-image"},me={key:1},ye={class:"upload"},fe=n("span",{class:"btn"},"Upload File",-1),ve={class:"area-settings"},we=n("td",null,[n("label",null,"Title")],-1),be=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ke=n("td",null,[n("label",null,"Tags")],-1),xe={class:"area-buttons"},Ce=s("🧩 Post to gallery "),Ae=n("br",null,null,-1),Se=s(" + set up game");ge.render=function(e,o,l,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[e.previewUrl?(i(),t("div",he,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(i(),t("div",me,[n("label",ye,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),fe])]))],34),n("div",ve,[n("table",null,[n("tr",null,[we,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),be,n("tr",null,[ke,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",xe,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ce,Ae,Se],8,["disabled"])])])])};var Te=e({name:"edit-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Pe={class:"area-image"},ze={class:"has-image"},Ie={class:"area-settings"},De=n("td",null,[n("label",null,"Title")],-1),Ee=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),_e=n("td",null,[n("label",null,"Tags")],-1),Me={class:"area-buttons"};Te.render=function(e,o,l,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Pe,[n("div",ze,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ie,[n("table",null,[n("tr",null,[De,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ee,n("tr",null,[_e,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Me,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Ne=e({name:"new-game-dialog",components:{ResponsiveImage:se},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Z.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Oe={class:"area-image"},Be={class:"has-image"},Ue={key:0,class:"image-title"},Re={class:"area-settings"},Ve=n("td",null,[n("label",null,"Pieces")],-1),$e=n("td",null,[n("label",null,"Scoring: ")],-1),Ge=s(" Any (Score when pieces are connected to each other or on final location)"),Fe=n("br",null,null,-1),Le=s(" Final (Score when pieces are put to their final location)"),je=n("td",null,[n("label",null,"Shapes: ")],-1),We=s(" Normal"),He=n("br",null,null,-1),Ye=s(" Any (flat pieces can occur anywhere)"),Qe=n("br",null,null,-1),qe=s(" Flat (all pieces flat on all sides)"),Ze=n("td",null,[n("label",null,"Snapping: ")],-1),Ke=s(" Normal (pieces snap to final destination and to each other)"),Xe=n("br",null,null,-1),Je=s(" Real (pieces snap only to corners, already snapped pieces and to each other)"),et={class:"area-buttons"};Ne.render=function(e,o,s,d,c,h){const m=a("responsive-image");return i(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Oe,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(i(),t("div",Ue,'"'+r(e.image.title)+'"',1)):l("",!0)]),n("div",Re,[n("table",null,[n("tr",null,[Ve,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[$e,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Ge]),Fe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),Le])])]),n("tr",null,[je,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),We]),He,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ye]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),qe])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Ke]),Xe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),Je])])])])]),n("div",et,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var tt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Te,NewGameDialog:Ne},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${ie.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const nt={class:"upload-image-teaser"},ot=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),lt={key:0},at=s(" Tags: "),it=s(" Sort by: "),st=n("option",{value:"date_desc"},"Newest first",-1),rt=n("option",{value:"date_asc"},"Oldest first",-1),dt=n("option",{value:"alpha_asc"},"A-Z",-1),ct=n("option",{value:"alpha_desc"},"Z-A",-1);tt.render=function(e,o,s,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return i(),t("div",null,[n("div",nt,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),ot]),n("div",null,[e.tags.length>0?(i(),t("label",lt,[at,(i(!0),t(d,null,c(e.relevantTags,((n,o)=>(i(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[it,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[st,rt,dt,ct],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(i(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(i(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(i(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ut=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 pt={class:"scores"},gt=n("div",null,"Scores",-1),ht=n("td",null,"⚡",-1),mt=n("td",null,"💤",-1);ut.render=function(e,o,l,a,s,u){return i(),t("div",pt,[gt,n("table",null,[(i(!0),t(d,null,c(e.actives,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[ht,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(i(!0),t(d,null,c(e.idles,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[mt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var yt=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 U(this.duration)}}});const ft={class:"timer"};yt.render=function(e,o,l,a,s,d){return i(),t("div",ft,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var vt=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 wt=n("td",null,[n("label",null,"Background: ")],-1),bt=n("td",null,[n("label",null,"Color: ")],-1),kt=n("td",null,[n("label",null,"Name: ")],-1),xt=n("td",null,[n("label",null,"Sounds: ")],-1);vt.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:o[6]||(o[6]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[5]||(o[5]=u((()=>{}),["stop"]))},[n("tr",null,[wt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[bt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[kt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[xt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[k,e.modelValue.soundsEnabled]])])])])])};var Ct=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const At={class:"preview"};Ct.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",At,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var St=1,Tt=4,Pt=2,zt=3,It=2,Dt=4,Et=3,_t=9,Mt=1,Nt=2,Ot=3,Bt=4,Ut=5,Rt=6,Vt=7,$t=8,Gt=10,Ft=11,Lt=1,jt=2,Wt=3;const Ht=ae("Communication.js");let Yt,Qt=[],qt=e=>{Qt.push(e)},Zt=[],Kt=e=>{Zt.push(e)};let Xt=0;const Jt=e=>{Xt!==e&&(Xt=e,Kt(e))};function en(e){if(2===Xt)try{Yt.send(JSON.stringify(e))}catch(t){Ht.info("unable to send message.. maybe because ws is invalid?")}}let tn,nn;var on={connect:function(e,t,n){return tn=0,nn={},Jt(3),new Promise((o=>{Yt=new WebSocket(e,n+"|"+t),Yt.onopen=()=>{Jt(2),en([zt])},Yt.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Tt){const e=t[1];o(e)}else{if(l!==St)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&nn[o])return void delete nn[o];qt(t)}}},Yt.onerror=()=>{throw Jt(1),"[ 2021-05-15 onerror ]"},Yt.onclose=e=>{4e3===e.code||1001===e.code?Jt(4):Jt(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${ie.asQueryArgs(n)}`);return await o.json()},disconnect:function(){Yt&&Yt.close(4e3),tn=0,nn={}},sendClientEvent:function(e){tn++,nn[tn]=e,en([Pt,tn,nn[tn]])},onServerChange:function(e){qt=e;for(const t of Qt)qt(t);Qt=[]},onConnectionStateChange:function(e){Kt=e;for(const t of Zt)Kt(t);Zt=[]},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},ln=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===on.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===on.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const an={key:0,class:"overlay connection-lost"},sn={key:0,class:"overlay-content"},rn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),dn={key:1,class:"overlay-content"},cn=n("div",null,"Connecting...",-1);ln.render=function(e,o,a,s,r,d){return e.show?(i(),t("div",an,[e.lostConnection?(i(),t("div",sn,[rn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(i(),t("div",dn,[cn])):l("",!0)])):l("",!0)};var un=e({name:"help-overlay",emits:{bgclick:null}});const pn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),gn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),hn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),mn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),yn=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),fn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),vn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),wn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),bn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),kn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),xn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1);un.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:o[2]||(o[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:o[1]||(o[1]=u((()=>{}),["stop"]))},[pn,gn,hn,mn,yn,fn,vn,wn,bn,kn,xn])])};var Cn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),An=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="}),Tn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Pn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function zn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),i=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function In(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Dn={createCanvas:In,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=In(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=In(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(),o}};const En=ae("Debug.js");let _n=0,Mn=0;var Nn=e=>{_n=performance.now(),Mn=e},On=e=>{const t=performance.now(),n=t-_n;n>Mn&&En.log(e+": "+n),_n=t};function Bn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function Un(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var Rn={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:Bn,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:Un,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 Bn(Un(e),Un(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const Vn=ae("PuzzleGraphics.js");function $n(e,t){const n=ie.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Gn={loadPuzzleBitmaps:async function(e){const t=await Dn.loadImageToBitmap(e.info.imageUrl),n=await Dn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Vn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,i=o/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=Rn.pointAdd(a,{x:o,y:0}),c=Rn.pointAdd(r,{x:0,y:o}),u=Rn.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;oie.decodePiece(Fn[e].puzzle.tiles[t]),no=(e,t)=>to(e,t).group,oo=(e,t)=>{const n=Fn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},lo=(e,t)=>{const n=Fn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Fn[e].puzzle.info,o=ie.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return Rn.pointAdd(o,l)},ao=(e,t)=>to(e,t).pos,io=e=>{const t=Ao(e),n=So(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}},so=(e,t)=>{const n=po(e),o=to(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},ro=(e,t)=>to(e,t).z,co=(e,t)=>{for(const n of Fn[e].puzzle.tiles){const e=ie.decodePiece(n);if(e.owner===t)return e.idx}return-1},uo=e=>Fn[e].puzzle.info.tileDrawSize,po=e=>Fn[e].puzzle.info.tileSize,go=e=>Fn[e].puzzle.data.maxGroup,ho=e=>Fn[e].puzzle.data.maxZ;function mo(e,t){const n=Fn[e].puzzle.info,o=ie.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const yo=(e,t,n)=>{for(const o of t)eo(e,o,{z:n})},fo=(e,t,n)=>{const o=ao(e,t);eo(e,t,{pos:Rn.pointAdd(o,n)})},vo=(e,t,n)=>{const o=uo(e),l=io(e),a=n;for(const i of t){const t=to(e,i);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const i of t)fo(e,i,a)},wo=(e,t)=>to(e,t).owner,bo=(e,t)=>{for(const n of t)eo(e,n,{owner:-1,z:1})},ko=(e,t,n)=>{for(const o of t)eo(e,o,{owner:n})};function xo(e,t){const n=Fn[e].puzzle.tiles,o=ie.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=ie.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const Co=(e,t)=>{const n=jn(e,t);return n?n.points:0},Ao=e=>Fn[e].puzzle.info.table.width,So=e=>Fn[e].puzzle.info.table.height;var To={setGame:function(e,t){Fn[e]=t},exists:function(e){return!!Fn[e]||!1},playerExists:Hn,getActivePlayers:function(e,t){const n=t-30*N;return Yn(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*N;return Yn(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Hn(e,t)?Xn(e,t,{ts:n}):Wn(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:Kn,getPieceCount:Qn,getImageUrl:function(e){return Fn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Fn[e].puzzle.info.imageUrl=t},get:function(e){return Fn[e]||null},getAllGames:function(){return Object.values(Fn).sort(((e,t)=>Zn(e.id)===Zn(t.id)?t.puzzle.data.started-e.puzzle.data.started:Zn(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=jn(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=jn(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=jn(e,t);return n?n.name:null},getPlayerIndexById:Ln,getPlayerIdByIndex:function(e,t){return Fn[e].players.length>t?ie.decodePlayer(Fn[e].players[t]).id:null},changePlayer:Xn,setPlayer:Wn,setPiece:function(e,t,n){Fn[e].puzzle.tiles[t]=ie.encodePiece(n)},setPuzzleData:function(e,t){Fn[e].puzzle.data=t},getTableWidth:Ao,getTableHeight:So,getPuzzle:e=>Fn[e].puzzle,getRng:e=>Fn[e].rng.obj,getPuzzleWidth:e=>Fn[e].puzzle.info.width,getPuzzleHeight:e=>Fn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Fn[e].puzzle.tiles.map(ie.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=co(e,t);return n<0?null:Fn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Fn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:uo,getFinalPiecePos:lo,getStartTs:e=>Fn[e].puzzle.data.started,getFinishTs:e=>Fn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Fn[e].puzzle,i=function(e,t){return t in Fn[e].evtInfos?Fn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],r=()=>{s.push([Lt,a.data])},d=t=>{s.push([jt,ie.encodePiece(to(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=jn(e,t);n&&s.push([Wt,ie.encodePlayer(n)])},p=n[0];if(p===Rt){const l=n[1];Xn(e,t,{bgcolor:l,ts:o}),u()}else if(p===Vt){const l=n[1];Xn(e,t,{color:l,ts:o}),u()}else if(p===$t){const l=`${n[1]}`.substr(0,16);Xn(e,t,{name:l,ts:o}),u()}else if(p===_t){const l=n[1],a=n[2],i=jn(e,t);if(i){const n=i.x-l,s=i.y-a;Xn(e,t,{ts:o,x:n,y:s}),u()}}else if(p===Mt){const l={x:n[1],y:n[2]};Xn(e,t,{d:1,ts:o}),u(),i._last_mouse_down=l;const a=((e,t)=>{const n=Fn[e].puzzle.info,o=Fn[e].puzzle.tiles;let l=-1,a=-1;for(let i=0;il)&&(l=e.z,a=i)}return a})(e,l);if(a>=0){const n=ho(e)+1;Jn(e,{maxZ:n}),r();const o=xo(e,a);yo(e,o,ho(e)),ko(e,o,t),c(o)}i._last_mouse=l}else if(p===Ot){const l=n[1],a=n[2],s={x:l,y:a};if(null===i._last_mouse_down)Xn(e,t,{x:l,y:a,ts:o}),u();else{const n=co(e,t);if(n>=0){Xn(e,t,{x:l,y:a,ts:o}),u();const r=xo(e,n);let d=Rn.pointInBounds(s,io(e))&&Rn.pointInBounds(i._last_mouse_down,io(e));for(const t of r){const n=so(e,t);if(Rn.pointInBounds(s,n)){d=!0;break}}if(d){const t=l-i._last_mouse_down.x,n=a-i._last_mouse_down.y;vo(e,r,{x:t,y:n}),c(r)}}else Xn(e,t,{ts:o}),u();i._last_mouse_down=s}i._last_mouse=s}else if(p===Nt){const s={x:n[1],y:n[2]},p=0;i._last_mouse_down=null;const g=co(e,t);if(g>=0){const n=xo(e,g);ko(e,n,0),c(n);const i=ao(e,g),s=lo(e,g);let h=!1;if(function(e){return Fn[e].snapMode||ee.NORMAL}(e)===ee.REAL){for(const t of n)if(oo(e,t)){h=!0;break}}else h=!0;if(h&&Rn.pointDistance(s,i){const l=Fn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=no(e,t),l=no(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=ao(e,t),i=Rn.pointAdd(ao(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(Rn.pointDistance(a,i){const o=Fn[e].puzzle.tiles,l=no(e,t),a=no(e,n);let i;const s=[];l&&s.push(l),a&&s.push(a),l?i=l:a?i=a:(Jn(e,{maxGroup:go(e)+1}),r(),i=go(e));if(eo(e,t,{group:i}),d(t),eo(e,n,{group:i}),d(n),s.length>0)for(const r of o){const t=ie.decodePiece(r);s.includes(t.group)&&(eo(e,t.idx,{group:i}),d(t.idx))}})(e,t,n),l=xo(e,t),((e,t)=>-1===wo(e,t))(e,n))bo(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=ro(e,o);t>n&&(n=t)}return n})(e,l);yo(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of xo(e,g)){const o=mo(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])){a=!0;break}}if(a&&qn(e)===Z.ANY){const n=Co(e,t)+1;Xn(e,t,{d:p,ts:o,points:n}),u()}else Xn(e,t,{d:p,ts:o}),u();a&&l&&l(t)}}else Xn(e,t,{d:p,ts:o}),u();i._last_mouse=s}else if(p===Bt){const l=n[1],a=n[2];Xn(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else if(p===Ut){const l=n[1],a=n[2];Xn(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else Xn(e,t,{ts:o}),u();return function(e,t,n){Fn[e].evtInfos[t]=n}(e,t,i),s}};let Po=-10,zo=20,Io=2,Do=15;class Eo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Po+Math.random()*zo,this.vy=-1*(Io+Math.random()*Do),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Io=t/2,Do=t-Io;const n=1/4*this.canvas.width/(t/2);Po=-n,zo=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Eo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Eo(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(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Dn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Ro=!0})),t}(l,Dn.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};on.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await on.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let k=()=>0;const x=async()=>{if("play"===o){const o=await on.connect(n,e,t),l=ie.decodeGame(o);To.setGame(l.id,l),k=()=>O()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=ie.decodeGame(t.game);To.setGame(n.id,n),w.lastRealTs=O(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,k=()=>w.lastGameTs}}Ro=!0};await x();const C=To.getPieceDrawOffset(e),A=To.getPieceDrawSize(e),S=To.getPuzzleWidth(e),T=To.getPuzzleHeight(e),P=To.getTableWidth(e),z=To.getTableHeight(e),I={x:(P-S)/2,y:(z-T)/2},D={w:S,h:T},E={w:A,h:A},_=await Gn.loadPuzzleBitmaps(To.getPuzzle(e)),N=new Mo(v,To.getRng(e));N.init();const B=v.getContext("2d");v.classList.add("loaded");const U=zn();U.move(-(P-v.width)/2,-(z-v.height)/2);const R=function(e,t,n){let o=[],l=!0,a=!1,i=!1,s=!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]},g=e=>p(e.offsetX,e.offsetY),h=()=>p(e.width/2,e.height/2),m=(e,t)=>{l&&("Shift"===t.key?u=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?s=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?r=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?a=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?i=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};let y=null;e.addEventListener("mousedown",(e=>{y=g(e),0===e.button&&f([Mt,...y])})),e.addEventListener("mouseup",(e=>{y=g(e),0===e.button&&f([Nt,...y])})),e.addEventListener("mousemove",(e=>{y=g(e),f([Ot,...y])})),e.addEventListener("wheel",(e=>{if(y=g(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Bt:Ut;f([t,...y])}})),t.addEventListener("keydown",(e=>m(!0,e))),t.addEventListener("keyup",(e=>m(!1,e))),t.addEventListener("keypress",(e=>{l&&(" "===e.key&&f([Gt]),"F"!==e.key&&"f"!==e.key||(Bo=!Bo,Ro=!0),"G"!==e.key&&"g"!==e.key||(Uo=!Uo,Ro=!0),"M"!==e.key&&"m"!==e.key||f([Ft]))}));const f=e=>{o.push(e)};return{addEvent:f,consumeAll:()=>{if(0===o.length)return[];const e=o.slice();return o=[],e},createKeyEvents:()=>{const e=(a?1:0)-(i?1:0),t=(s?1:0)-(r?1:0);if(0!==e||0!==t){const o=(u?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});f([_t,l.w,l.h]),y&&(y[0]-=l.w,y[1]-=l.h)}if(d&&c);else if(d){if(n.canZoom("in")){const e=y||h();f([Bt,...e])}}else if(c&&n.canZoom("out")){const e=y||h();f([Ut,...e])}},setHotkeys:e=>{l=e}}}(v,window,U),V=To.getImageUrl(e),$=()=>{const t=To.getStartTs(e),n=To.getFinishTs(e),o=k();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(To.getFinishedPiecesCount(e)),a.setPiecesTotal(To.getPieceCount(e));const G=k();a.setActivePlayers(To.getActivePlayers(e,G)),a.setIdlePlayers(To.getIdlePlayers(e,G));const F=!!To.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},H=()=>To.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Y=()=>To.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let Q="",q="",Z=!1;const K=e=>{Z=e;const[t,n]=e?[Q,"grab"]:[q,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},X=e=>{Q=Dn.colorizedCanvas(r,c,e).toDataURL(),q=Dn.colorizedCanvas(d,u,e).toDataURL(),K(Z)};X(Y());const J=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ee=[];let te;let ne;if("play"===o?ee.push(setInterval((()=>{$()}),1e3)):"replay"===o&&J(),"play"===o)on.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Wt:{const n=ie.decodePlayer(a);n.id!==t&&(To.setPlayer(e,n.id,n),Ro=!0)}break;case jt:{const t=ie.decodePiece(a);To.setPiece(e,t.idx,t),Ro=!0}break;case Lt:To.setPuzzleData(e,a),Ro=!0}L=!!To.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===It){const t=o[1];return To.addPlayer(e,t,n),!0}if(o[0]===Dt){const t=To.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return To.addPlayer(e,t,n),!0}if(o[0]===Et){const t=To.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return To.handleInput(e,t,l,n),!0}return!1},n=async()=>{w.logPointer+1>=w.log.length&&await b(e);const o=O();if(w.paused)return w.lastRealTs=o,void(te=setTimeout(n,50));const l=(o-w.lastRealTs)*w.speeds[w.speedIdx];let a=w.lastGameTs+l;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const n=w.log[w.logPointer],o=w.gameStartTs+n[n.length-1],l=w.log[e],i=w.gameStartTs+l[l.length-1];if(i>a){if(w.skipNonActionPhases&&a+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,i=window.requestAnimationFrame,s=1/n,r=o*s;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(s);a(c/o),u=d,t||i(p)};return i(p),{stop:()=>{t=!0}}})({update:()=>{R.createKeyEvents();for(const n of R.consumeAll())if("play"===o){const o=n[0];if(o===_t){const e=n[1],t=n[2],o=U.worldDimToViewport({w:e,h:t});Ro=!0,U.move(o.w,o.h)}else if(o===Ot){if(oe&&!To.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-oe.x),l=Math.round(t.y-oe.y);Ro=!0,U.move(o,l),oe=t}}else if(o===Vt)X(n[1]);else if(o===Mt){const e={x:n[1],y:n[2]};oe=U.worldToViewport(e),K(!0)}else if(o===Nt)oe=null,K(!1);else if(o===Bt){const e={x:n[1],y:n[2]};Ro=!0,U.zoom("in",U.worldToViewport(e))}else if(o===Ut){const e={x:n[1],y:n[2]};Ro=!0,U.zoom("out",U.worldToViewport(e))}else o===Gt?a.togglePreview():o===Ft&&a.toggleSoundsEnabled();const l=k();To.handleInput(e,t,n,l,(e=>{W()&&s.play()})).length>0&&(Ro=!0),on.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===_t){const e=n[1],t=n[2];Ro=!0,U.move(e,t)}else if(e===Ot){if(oe){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-oe.x),l=Math.round(t.y-oe.y);Ro=!0,U.move(o,l),oe=t}}else if(e===Mt){const e={x:n[1],y:n[2]};oe=U.worldToViewport(e)}else if(e===Nt)oe=null;else if(e===Bt){const e={x:n[1],y:n[2]};Ro=!0,U.zoom("in",U.worldToViewport(e))}else if(e===Ut){const e={x:n[1],y:n[2]};Ro=!0,U.zoom("out",U.worldToViewport(e))}else e===Gt&&a.togglePreview()}L=!!To.getFinishTs(e),j()&&(N.update(),Ro=!0)},render:async()=>{if(!Ro)return;const n=k();let l,i,s;window.DEBUG&&Nn(0),B.fillStyle=H(),B.fillRect(0,0,v.width,v.height),window.DEBUG&&On("clear done"),l=U.worldToViewportRaw(I),i=U.worldDimToViewportRaw(D),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(l.x,l.y,i.w,i.h),window.DEBUG&&On("board done");const r=To.getPiecesSortedByZIndex(e);window.DEBUG&&On("get tiles done"),i=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?Bo:Uo)&&(s=_[e.idx],l=U.worldToViewportRaw({x:C+e.pos.x,y:C+e.pos.y}),B.drawImage(s,0,0,s.width,s.height,l.x,l.y,i.w,i.h));window.DEBUG&&On("tiles done");const d=[];for(const a of To.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(s=await f(a),l=U.worldToViewport(a),B.drawImage(s,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,o]of d)B.fillText(e,t,o);window.DEBUG&&On("players done"),a.setActivePlayers(To.getActivePlayers(e,n)),a.setIdlePlayers(To.getIdlePlayers(e,n)),a.setPiecesDone(To.getFinishedPiecesCount(e)),window.DEBUG&&On("HUD done"),j()&&N.render(),Ro=!1}}),{setHotkeys:e=>{R.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),R.addEvent([Rt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),R.addEvent([Vt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),R.addEvent([$t,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},replayOnSpeedUp:()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,J())},replayOnPauseToggle:()=>{w.paused=!w.paused,J()},replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:V,player:{background:H(),color:Y(),name:To.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:W()},disconnect:on.disconnect,connect:x,unload:()=>{ee.forEach((e=>{clearInterval(e)})),te&&clearTimeout(te),ne&&ne.stop()}}}var $o=e({name:"game",components:{PuzzleStatus:yt,Scores:ut,SettingsOverlay:vt,PreviewOverlay:Ct,ConnectionOverlay:ln,HelpOverlay:un},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Vo(`${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)},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 Go={id:"game"},Fo={class:"menu"},Lo={class:"tabs"},jo=s("🧩 Puzzles");$o.render=function(e,l,s,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Go,[p(n(u,{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"]),[[x,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",Fo,[n("div",Lo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[jo])),_: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 Wo=e({name:"replay",components:{PuzzleStatus:yt,Scores:ut,SettingsOverlay:vt,PreviewOverlay:Ct,HelpOverlay:un},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Vo(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),this.g.disconnect()},methods:{toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}},computed:{replayText(){return"Replay-Speed: "+this.replay.speed+"x"+(this.replay.paused?" Paused":"")}}});const Ho={id:"replay"},Yo=s("Skip no action phases: "),Qo={class:"menu"},qo={class:"tabs"},Zo=s("🧩 Puzzles");Wo.render=function(e,l,s,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Ho,[p(n(g,{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"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[Yo,p(n("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[k,e.skipNoAction]])])]),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",Qo,[n("div",qo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Zo])),_:1}),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:l[12]||(l[12]=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=C({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:tt},{name:"game",path:"/g/:id",component:$o},{name:"replay",path:"/replay/:id",component:Wo}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=S(T);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=ie.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); diff --git a/build/public/assets/index.c6197c7d.js b/build/public/assets/index.c6197c7d.js new file mode 100644 index 0000000..8e7d0fc --- /dev/null +++ b/build/public/assets/index.c6197c7d.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as o,b as l,r as a,o as i,e as s,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as k,s as x,u as C,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const P={id:"app"},z={key:0,class:"nav"},I=s("Index"),D=s("New game");T.render=function(e,s,r,d,c,u){const p=a("router-link"),g=a("router-view");return i(),t("div",P,[e.showNav?(i(),t("ul",z,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[D])),_:1})])])):l("",!0),n(g)])};const E=864e5,_=e=>{const t=Math.floor(e/E);e%=E;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 M=1,N=1e3,O=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>_(t-e),U=_,R=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||O();return`${n} ${B(o,l)}`}}});const V={class:"game-info-text"},$=n("br",null,null,-1),G=n("br",null,null,-1),F=n("br",null,null,-1),L=s(" ↪️ Watch replay ");R.render=function(e,d,c,u,p,g){const h=a("router-link");return i(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",V,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),$,s(" 👥 "+r(e.game.players),1),G,s(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(i(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[L])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:R},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 W=n("h1",null,"Running games",-1),H=n("h1",null,"Finished games",-1);j.render=function(e,o,l,s,r,u){const p=a("game-teaser");return i(),t("div",null,[W,(i(!0),t(d,null,c(e.gamesRunning,((e,o)=>(i(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128)),H,(i(!0),t(d,null,c(e.gamesFinished,((e,o)=>(i(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128))])};var Y=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});Y.render=function(e,o,l,a,s,r){return i(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Q,q,Z,K,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:Y},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,s,r){const u=a("image-teaser");return i(),t("div",null,[(i(!0),t(d,null,c(e.images,((n,o)=>(i(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(q=Q||(Q={}))[q.Flat=0]="Flat",q[q.Out=1]="Out",q[q.In=-1]="In",(K=Z||(Z={}))[K.FINAL=0]="FINAL",K[K.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),i=le(o.getSeconds(),"00");console[t](`${l}:${a}:${i}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var ie={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Z.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const se={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};se.render=function(e,n,o,l,a,s){return i(),t("div",{style:s.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.key&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-39ed99c7");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,s,u,m)=>(i(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(i(),t("div",ce,[n("ul",null,[(i(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(i(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(i(!0),t(d,null,c(e.values,((n,o)=>(i(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-39ed99c7";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he={key:0,class:"has-image"},me={key:1},ye={class:"upload"},fe=n("span",{class:"btn"},"Upload File",-1),ve={class:"area-settings"},we=n("td",null,[n("label",null,"Title")],-1),be=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ke=n("td",null,[n("label",null,"Tags")],-1),xe={class:"area-buttons"},Ce=s("🧩 Post to gallery "),Ae=n("br",null,null,-1),Se=s(" + set up game");ge.render=function(e,o,l,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[e.previewUrl?(i(),t("div",he,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(i(),t("div",me,[n("label",ye,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),fe])]))],34),n("div",ve,[n("table",null,[n("tr",null,[we,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),be,n("tr",null,[ke,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",xe,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ce,Ae,Se],8,["disabled"])])])])};var Te=e({name:"edit-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Pe={class:"area-image"},ze={class:"has-image"},Ie={class:"area-settings"},De=n("td",null,[n("label",null,"Title")],-1),Ee=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),_e=n("td",null,[n("label",null,"Tags")],-1),Me={class:"area-buttons"};Te.render=function(e,o,l,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Pe,[n("div",ze,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ie,[n("table",null,[n("tr",null,[De,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ee,n("tr",null,[_e,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Me,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Ne=e({name:"new-game-dialog",components:{ResponsiveImage:se},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Z.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Oe={class:"area-image"},Be={class:"has-image"},Ue={key:0,class:"image-title"},Re={class:"area-settings"},Ve=n("td",null,[n("label",null,"Pieces")],-1),$e=n("td",null,[n("label",null,"Scoring: ")],-1),Ge=s(" Any (Score when pieces are connected to each other or on final location)"),Fe=n("br",null,null,-1),Le=s(" Final (Score when pieces are put to their final location)"),je=n("td",null,[n("label",null,"Shapes: ")],-1),We=s(" Normal"),He=n("br",null,null,-1),Ye=s(" Any (flat pieces can occur anywhere)"),Qe=n("br",null,null,-1),qe=s(" Flat (all pieces flat on all sides)"),Ze=n("td",null,[n("label",null,"Snapping: ")],-1),Ke=s(" Normal (pieces snap to final destination and to each other)"),Xe=n("br",null,null,-1),Je=s(" Real (pieces snap only to corners, already snapped pieces and to each other)"),et={class:"area-buttons"};Ne.render=function(e,o,s,d,c,h){const m=a("responsive-image");return i(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Oe,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(i(),t("div",Ue,'"'+r(e.image.title)+'"',1)):l("",!0)]),n("div",Re,[n("table",null,[n("tr",null,[Ve,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[$e,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Ge]),Fe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),Le])])]),n("tr",null,[je,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),We]),He,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ye]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),qe])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Ke]),Xe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),Je])])])])]),n("div",et,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var tt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Te,NewGameDialog:Ne},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${ie.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const nt={class:"upload-image-teaser"},ot=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),lt={key:0},at=s(" Tags: "),it=s(" Sort by: "),st=n("option",{value:"date_desc"},"Newest first",-1),rt=n("option",{value:"date_asc"},"Oldest first",-1),dt=n("option",{value:"alpha_asc"},"A-Z",-1),ct=n("option",{value:"alpha_desc"},"Z-A",-1);tt.render=function(e,o,s,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return i(),t("div",null,[n("div",nt,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),ot]),n("div",null,[e.tags.length>0?(i(),t("label",lt,[at,(i(!0),t(d,null,c(e.relevantTags,((n,o)=>(i(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[it,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[st,rt,dt,ct],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(i(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(i(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(i(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ut=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 pt={class:"scores"},gt=n("div",null,"Scores",-1),ht=n("td",null,"⚡",-1),mt=n("td",null,"💤",-1);ut.render=function(e,o,l,a,s,u){return i(),t("div",pt,[gt,n("table",null,[(i(!0),t(d,null,c(e.actives,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[ht,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(i(!0),t(d,null,c(e.idles,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[mt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var yt=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 U(this.duration)}}});const ft={class:"timer"};yt.render=function(e,o,l,a,s,d){return i(),t("div",ft,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var vt=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 wt=n("td",null,[n("label",null,"Background: ")],-1),bt=n("td",null,[n("label",null,"Color: ")],-1),kt=n("td",null,[n("label",null,"Name: ")],-1),xt=n("td",null,[n("label",null,"Sounds: ")],-1);vt.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:o[6]||(o[6]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[5]||(o[5]=u((()=>{}),["stop"]))},[n("tr",null,[wt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[bt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[kt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[xt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[k,e.modelValue.soundsEnabled]])])])])])};var Ct=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const At={class:"preview"};Ct.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",At,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var St=1,Tt=4,Pt=2,zt=3,It=2,Dt=4,Et=3,_t=9,Mt=1,Nt=2,Ot=3,Bt=4,Ut=5,Rt=6,Vt=7,$t=8,Gt=10,Ft=11,Lt=12,jt=13,Wt=14,Ht=1,Yt=2,Qt=3;const qt=ae("Communication.js");let Zt,Kt=[],Xt=e=>{Kt.push(e)},Jt=[],en=e=>{Jt.push(e)};let tn=0;const nn=e=>{tn!==e&&(tn=e,en(e))};function on(e){if(2===tn)try{Zt.send(JSON.stringify(e))}catch(t){qt.info("unable to send message.. maybe because ws is invalid?")}}let ln,an;var sn={connect:function(e,t,n){return ln=0,an={},nn(3),new Promise((o=>{Zt=new WebSocket(e,n+"|"+t),Zt.onopen=()=>{nn(2),on([zt])},Zt.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Tt){const e=t[1];o(e)}else{if(l!==St)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&an[o])return void delete an[o];Xt(t)}}},Zt.onerror=()=>{throw nn(1),"[ 2021-05-15 onerror ]"},Zt.onclose=e=>{4e3===e.code||1001===e.code?nn(4):nn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${ie.asQueryArgs(n)}`);return await o.json()},disconnect:function(){Zt&&Zt.close(4e3),ln=0,an={}},sendClientEvent:function(e){ln++,an[ln]=e,on([Pt,ln,an[ln]])},onServerChange:function(e){Xt=e;for(const t of Kt)Xt(t);Kt=[]},onConnectionStateChange:function(e){en=e;for(const t of Jt)en(t);Jt=[]},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},rn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===sn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===sn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const dn={key:0,class:"overlay connection-lost"},cn={key:0,class:"overlay-content"},un=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),pn={key:1,class:"overlay-content"},gn=n("div",null,"Connecting...",-1);rn.render=function(e,o,a,s,r,d){return e.show?(i(),t("div",dn,[e.lostConnection?(i(),t("div",cn,[un,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(i(),t("div",pn,[gn])):l("",!0)])):l("",!0)};var hn=e({name:"help-overlay",emits:{bgclick:null}});const mn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),yn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),fn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),vn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),wn=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),bn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),kn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),xn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Cn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),An=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Sn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),Tn=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),Pn=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),zn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);hn.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:o[2]||(o[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:o[1]||(o[1]=u((()=>{}),["stop"]))},[mn,yn,fn,vn,wn,bn,kn,xn,Cn,An,Sn,Tn,Pn,zn])])};var In=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Dn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),En=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),_n=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Mn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Nn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),i=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function On(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Bn={createCanvas:On,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=On(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=On(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(),o}};const Un=ae("Debug.js");let Rn=0,Vn=0;var $n=e=>{Rn=performance.now(),Vn=e},Gn=e=>{const t=performance.now(),n=t-Rn;n>Vn&&Un.log(e+": "+n),Rn=t};function Fn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function Ln(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var jn={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:Fn,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:Ln,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 Fn(Ln(e),Ln(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const Wn=ae("PuzzleGraphics.js");function Hn(e,t){const n=ie.coordByPieceIdx(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 Bn.loadImageToBitmap(e.info.imageUrl),n=await Bn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Wn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,i=o/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=jn.pointAdd(a,{x:o,y:0}),c=jn.pointAdd(r,{x:0,y:o}),u=jn.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;oie.decodePiece(Qn[e].puzzle.tiles[t]),ro=(e,t)=>so(e,t).group,co=(e,t)=>{const n=Qn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},uo=(e,t)=>{const n=Qn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Qn[e].puzzle.info,o=ie.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return jn.pointAdd(o,l)},po=(e,t)=>so(e,t).pos,go=e=>{const t=Do(e),n=Eo(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}},ho=(e,t)=>{const n=vo(e),o=so(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},mo=(e,t)=>so(e,t).z,yo=(e,t)=>{for(const n of Qn[e].puzzle.tiles){const e=ie.decodePiece(n);if(e.owner===t)return e.idx}return-1},fo=e=>Qn[e].puzzle.info.tileDrawSize,vo=e=>Qn[e].puzzle.info.tileSize,wo=e=>Qn[e].puzzle.data.maxGroup,bo=e=>Qn[e].puzzle.data.maxZ;function ko(e,t){const n=Qn[e].puzzle.info,o=ie.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const xo=(e,t,n)=>{for(const o of t)io(e,o,{z:n})},Co=(e,t,n)=>{const o=po(e,t);io(e,t,{pos:jn.pointAdd(o,n)})},Ao=(e,t,n)=>{const o=fo(e),l=go(e),a=n;for(const i of t){const t=so(e,i);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const i of t)Co(e,i,a)},So=(e,t)=>so(e,t).owner,To=(e,t)=>{for(const n of t)io(e,n,{owner:-1,z:1})},Po=(e,t,n)=>{for(const o of t)io(e,o,{owner:n})};function zo(e,t){const n=Qn[e].puzzle.tiles,o=ie.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=ie.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const Io=(e,t)=>{const n=Zn(e,t);return n?n.points:0},Do=e=>Qn[e].puzzle.info.table.width,Eo=e=>Qn[e].puzzle.info.table.height;var _o={setGame:function(e,t){Qn[e]=t},exists:function(e){return!!Qn[e]||!1},playerExists:Xn,getActivePlayers:function(e,t){const n=t-30*N;return Jn(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*N;return Jn(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Xn(e,t)?lo(e,t,{ts:n}):Kn(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:oo,getPieceCount:eo,getImageUrl:function(e){return Qn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Qn[e].puzzle.info.imageUrl=t},get:function(e){return Qn[e]||null},getAllGames:function(){return Object.values(Qn).sort(((e,t)=>no(e.id)===no(t.id)?t.puzzle.data.started-e.puzzle.data.started:no(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Zn(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Zn(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Zn(e,t);return n?n.name:null},getPlayerIndexById:qn,getPlayerIdByIndex:function(e,t){return Qn[e].players.length>t?ie.decodePlayer(Qn[e].players[t]).id:null},changePlayer:lo,setPlayer:Kn,setPiece:function(e,t,n){Qn[e].puzzle.tiles[t]=ie.encodePiece(n)},setPuzzleData:function(e,t){Qn[e].puzzle.data=t},getTableWidth:Do,getTableHeight:Eo,getPuzzle:e=>Qn[e].puzzle,getRng:e=>Qn[e].rng.obj,getPuzzleWidth:e=>Qn[e].puzzle.info.width,getPuzzleHeight:e=>Qn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Qn[e].puzzle.tiles.map(ie.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=yo(e,t);return n<0?null:Qn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Qn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:fo,getFinalPiecePos:uo,getStartTs:e=>Qn[e].puzzle.data.started,getFinishTs:e=>Qn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Qn[e].puzzle,i=function(e,t){return t in Qn[e].evtInfos?Qn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],r=()=>{s.push([Ht,a.data])},d=t=>{s.push([Yt,ie.encodePiece(so(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=Zn(e,t);n&&s.push([Qt,ie.encodePlayer(n)])},p=n[0];if(p===Rt){const l=n[1];lo(e,t,{bgcolor:l,ts:o}),u()}else if(p===Vt){const l=n[1];lo(e,t,{color:l,ts:o}),u()}else if(p===$t){const l=`${n[1]}`.substr(0,16);lo(e,t,{name:l,ts:o}),u()}else if(p===_t){const l=n[1],a=n[2],i=Zn(e,t);if(i){const n=i.x-l,s=i.y-a;lo(e,t,{ts:o,x:n,y:s}),u()}}else if(p===Mt){const l={x:n[1],y:n[2]};lo(e,t,{d:1,ts:o}),u(),i._last_mouse_down=l;const a=((e,t)=>{const n=Qn[e].puzzle.info,o=Qn[e].puzzle.tiles;let l=-1,a=-1;for(let i=0;il)&&(l=e.z,a=i)}return a})(e,l);if(a>=0){const n=bo(e)+1;ao(e,{maxZ:n}),r();const o=zo(e,a);xo(e,o,bo(e)),Po(e,o,t),c(o)}i._last_mouse=l}else if(p===Ot){const l=n[1],a=n[2],s={x:l,y:a};if(null===i._last_mouse_down)lo(e,t,{x:l,y:a,ts:o}),u();else{const n=yo(e,t);if(n>=0){lo(e,t,{x:l,y:a,ts:o}),u();const r=zo(e,n);let d=jn.pointInBounds(s,go(e))&&jn.pointInBounds(i._last_mouse_down,go(e));for(const t of r){const n=ho(e,t);if(jn.pointInBounds(s,n)){d=!0;break}}if(d){const t=l-i._last_mouse_down.x,n=a-i._last_mouse_down.y;Ao(e,r,{x:t,y:n}),c(r)}}else lo(e,t,{ts:o}),u();i._last_mouse_down=s}i._last_mouse=s}else if(p===Nt){const s={x:n[1],y:n[2]},p=0;i._last_mouse_down=null;const g=yo(e,t);if(g>=0){const n=zo(e,g);Po(e,n,0),c(n);const i=po(e,g),s=uo(e,g);let h=!1;if(function(e){return Qn[e].snapMode||ee.NORMAL}(e)===ee.REAL){for(const t of n)if(co(e,t)){h=!0;break}}else h=!0;if(h&&jn.pointDistance(s,i){const l=Qn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=ro(e,t),l=ro(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=po(e,t),i=jn.pointAdd(po(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(jn.pointDistance(a,i){const o=Qn[e].puzzle.tiles,l=ro(e,t),a=ro(e,n);let i;const s=[];l&&s.push(l),a&&s.push(a),l?i=l:a?i=a:(ao(e,{maxGroup:wo(e)+1}),r(),i=wo(e));if(io(e,t,{group:i}),d(t),io(e,n,{group:i}),d(n),s.length>0)for(const r of o){const t=ie.decodePiece(r);s.includes(t.group)&&(io(e,t.idx,{group:i}),d(t.idx))}})(e,t,n),l=zo(e,t),((e,t)=>-1===So(e,t))(e,n))To(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=mo(e,o);t>n&&(n=t)}return n})(e,l);xo(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of zo(e,g)){const o=ko(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])){a=!0;break}}if(a&&to(e)===Z.ANY){const n=Io(e,t)+1;lo(e,t,{d:p,ts:o,points:n}),u()}else lo(e,t,{d:p,ts:o}),u();a&&l&&l(t)}}else lo(e,t,{d:p,ts:o}),u();i._last_mouse=s}else if(p===Bt){const l=n[1],a=n[2];lo(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else if(p===Ut){const l=n[1],a=n[2];lo(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else lo(e,t,{ts:o}),u();return function(e,t,n){Qn[e].evtInfos[t]=n}(e,t,i),s}};let Mo=-10,No=20,Oo=2,Bo=15;class Uo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Mo+Math.random()*No,this.vy=-1*(Oo+Math.random()*Bo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Oo=t/2,Bo=t-Oo;const n=1/4*this.canvas.width/(t/2);Mo=-n,No=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Uo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Uo(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(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Bn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,jo=!0})),t}(l,Bn.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};sn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await sn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let k=()=>0;const x=async()=>{if("play"===o){const o=await sn.connect(n,e,t),l=ie.decodeGame(o);_o.setGame(l.id,l),k=()=>O()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=ie.decodeGame(t.game);_o.setGame(n.id,n),w.lastRealTs=O(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,k=()=>w.lastGameTs}}jo=!0};await x();const C=_o.getPieceDrawOffset(e),A=_o.getPieceDrawSize(e),S=_o.getPuzzleWidth(e),T=_o.getPuzzleHeight(e),P=_o.getTableWidth(e),z=_o.getTableHeight(e),I={x:(P-S)/2,y:(z-T)/2},D={w:S,h:T},E={w:A,h:A},_=await Yn.loadPuzzleBitmaps(_o.getPuzzle(e)),N=new Vo(v,_o.getRng(e));N.init();const B=v.getContext("2d");v.classList.add("loaded");const U=Nn();U.move(-(P-v.width)/2,-(z-v.height)/2);const R=function(e,t,n,o){let l=[],a=!0,i=!1,s=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("Shift"===t.key?p=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?r=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?d=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?u=e:"e"===t.key&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Mt,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Nt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([Ot,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Bt:Ut;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&(" "===e.key&&v([Gt]),"replay"===o&&("I"!==e.key&&"i"!==e.key||v([jt]),"O"!==e.key&&"o"!==e.key||v([Wt]),"P"!==e.key&&"p"!==e.key||v([Lt])),"F"!==e.key&&"f"!==e.key||(Fo=!Fo,jo=!0),"G"!==e.key&&"g"!==e.key||(Lo=!Lo,jo=!0),"M"!==e.key&&"m"!==e.key||v([Ft]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(i?1:0)-(s?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});v([_t,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Bt,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([Ut,...e])}},setHotkeys:e=>{a=e}}}(v,window,U,o),V=_o.getImageUrl(e),$=()=>{const t=_o.getStartTs(e),n=_o.getFinishTs(e),o=k();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(_o.getFinishedPiecesCount(e)),a.setPiecesTotal(_o.getPieceCount(e));const G=k();a.setActivePlayers(_o.getActivePlayers(e,G)),a.setIdlePlayers(_o.getIdlePlayers(e,G));const F=!!_o.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},H=()=>_o.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Y=()=>_o.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let Q="",q="",Z=!1;const K=e=>{Z=e;const[t,n]=e?[Q,"grab"]:[q,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},X=e=>{Q=Bn.colorizedCanvas(r,c,e).toDataURL(),q=Bn.colorizedCanvas(d,u,e).toDataURL(),K(Z)};X(Y());const J=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ee=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,J())},ne=()=>{w.paused=!w.paused,J()},oe=[];let le;let ae;if("play"===o?oe.push(setInterval((()=>{$()}),1e3)):"replay"===o&&J(),"play"===o)sn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Qt:{const n=ie.decodePlayer(a);n.id!==t&&(_o.setPlayer(e,n.id,n),jo=!0)}break;case Yt:{const t=ie.decodePiece(a);_o.setPiece(e,t.idx,t),jo=!0}break;case Ht:_o.setPuzzleData(e,a),jo=!0}L=!!_o.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===It){const t=o[1];return _o.addPlayer(e,t,n),!0}if(o[0]===Dt){const t=_o.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return _o.addPlayer(e,t,n),!0}if(o[0]===Et){const t=_o.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return _o.handleInput(e,t,l,n),!0}return!1},n=async()=>{w.logPointer+1>=w.log.length&&await b(e);const o=O();if(w.paused)return w.lastRealTs=o,void(le=setTimeout(n,50));const l=(o-w.lastRealTs)*w.speeds[w.speedIdx];let a=w.lastGameTs+l;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const n=w.log[w.logPointer],o=w.gameStartTs+n[n.length-1],l=w.log[e],i=w.gameStartTs+l[l.length-1];if(i>a){if(w.skipNonActionPhases&&a+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,i=window.requestAnimationFrame,s=1/n,r=o*s;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(s);a(c/o),u=d,t||i(p)};return i(p),{stop:()=>{t=!0}}})({update:()=>{R.createKeyEvents();for(const n of R.consumeAll())if("play"===o){const o=n[0];if(o===_t){const e=n[1],t=n[2],o=U.worldDimToViewport({w:e,h:t});jo=!0,U.move(o.w,o.h)}else if(o===Ot){if(se&&!_o.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-se.x),l=Math.round(t.y-se.y);jo=!0,U.move(o,l),se=t}}else if(o===Vt)X(n[1]);else if(o===Mt){const e={x:n[1],y:n[2]};se=U.worldToViewport(e),K(!0)}else if(o===Nt)se=null,K(!1);else if(o===Bt){const e={x:n[1],y:n[2]};jo=!0,U.zoom("in",U.worldToViewport(e))}else if(o===Ut){const e={x:n[1],y:n[2]};jo=!0,U.zoom("out",U.worldToViewport(e))}else o===Gt?a.togglePreview():o===Ft&&a.toggleSoundsEnabled();const l=k();_o.handleInput(e,t,n,l,(e=>{W()&&s.play()})).length>0&&(jo=!0),sn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===Lt)ne();else if(e===Wt)te();else if(e===jt)ee();else if(e===_t){const e=n[1],t=n[2];jo=!0,U.move(e,t)}else if(e===Ot){if(se){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-se.x),l=Math.round(t.y-se.y);jo=!0,U.move(o,l),se=t}}else if(e===Mt){const e={x:n[1],y:n[2]};se=U.worldToViewport(e)}else if(e===Nt)se=null;else if(e===Bt){const e={x:n[1],y:n[2]};jo=!0,U.zoom("in",U.worldToViewport(e))}else if(e===Ut){const e={x:n[1],y:n[2]};jo=!0,U.zoom("out",U.worldToViewport(e))}else e===Gt&&a.togglePreview()}L=!!_o.getFinishTs(e),j()&&(N.update(),jo=!0)},render:async()=>{if(!jo)return;const n=k();let l,i,s;window.DEBUG&&$n(0),B.fillStyle=H(),B.fillRect(0,0,v.width,v.height),window.DEBUG&&Gn("clear done"),l=U.worldToViewportRaw(I),i=U.worldDimToViewportRaw(D),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(l.x,l.y,i.w,i.h),window.DEBUG&&Gn("board done");const r=_o.getPiecesSortedByZIndex(e);window.DEBUG&&Gn("get tiles done"),i=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?Fo:Lo)&&(s=_[e.idx],l=U.worldToViewportRaw({x:C+e.pos.x,y:C+e.pos.y}),B.drawImage(s,0,0,s.width,s.height,l.x,l.y,i.w,i.h));window.DEBUG&&Gn("tiles done");const d=[];for(const a of _o.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(s=await f(a),l=U.worldToViewport(a),B.drawImage(s,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,o]of d)B.fillText(e,t,o);window.DEBUG&&Gn("players done"),a.setActivePlayers(_o.getActivePlayers(e,n)),a.setIdlePlayers(_o.getIdlePlayers(e,n)),a.setPiecesDone(_o.getFinishedPiecesCount(e)),window.DEBUG&&Gn("HUD done"),j()&&N.render(),jo=!1}}),{setHotkeys:e=>{R.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),R.addEvent([Rt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),R.addEvent([Vt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),R.addEvent([$t,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},replayOnSpeedUp:ee,replayOnSpeedDown:te,replayOnPauseToggle:ne,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:V,player:{background:H(),color:Y(),name:_o.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:W()},disconnect:sn.disconnect,connect:x,unload:()=>{oe.forEach((e=>{clearInterval(e)})),le&&clearTimeout(le),ae&&ae.stop()}}}var Ho=e({name:"game",components:{PuzzleStatus:yt,Scores:ut,SettingsOverlay:vt,PreviewOverlay:Ct,ConnectionOverlay:rn,HelpOverlay:hn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Wo(`${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)},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 Yo={id:"game"},Qo={class:"menu"},qo={class:"tabs"},Zo=s("🧩 Puzzles");Ho.render=function(e,l,s,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Yo,[p(n(u,{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"]),[[x,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",Qo,[n("div",qo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Zo])),_: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 Ko=e({name:"replay",components:{PuzzleStatus:yt,Scores:ut,SettingsOverlay:vt,PreviewOverlay:Ct,HelpOverlay:hn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Wo(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 Xo={id:"replay"},Jo=s("Skip no action phases: "),el={class:"menu"},tl={class:"tabs"},nl=s("🧩 Puzzles");Ko.render=function(e,l,s,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Xo,[p(n(g,{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"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[Jo,p(n("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[k,e.skipNoAction]])])]),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",el,[n("div",tl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[nl])),_:1}),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:l[12]||(l[12]=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=C({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:tt},{name:"game",path:"/g/:id",component:Ho},{name:"replay",path:"/replay/:id",component:Ko}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=S(T);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=ie.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 456eedc..0c8ec16 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,7 +4,7 @@ 🧩 jigsaw.hyottoko.club - + diff --git a/build/server/main.js b/build/server/main.js index c87b04b..0f3b103 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -358,6 +358,9 @@ const INPUT_EV_PLAYER_NAME = 8; const INPUT_EV_MOVE = 9; const INPUT_EV_TOGGLE_PREVIEW = 10; const INPUT_EV_TOGGLE_SOUNDS = 11; +const INPUT_EV_REPLAY_TOGGLE_PAUSE = 12; +const INPUT_EV_REPLAY_SPEED_UP = 13; +const INPUT_EV_REPLAY_SPEED_DOWN = 14; const CHANGE_DATA = 1; const CHANGE_TILE = 2; const CHANGE_PLAYER = 3; @@ -381,6 +384,9 @@ var Protocol = { INPUT_EV_PLAYER_NAME, INPUT_EV_TOGGLE_PREVIEW, INPUT_EV_TOGGLE_SOUNDS, + INPUT_EV_REPLAY_TOGGLE_PAUSE, + INPUT_EV_REPLAY_SPEED_UP, + INPUT_EV_REPLAY_SPEED_DOWN, CHANGE_DATA, CHANGE_TILE, CHANGE_PLAYER, diff --git a/src/common/Protocol.ts b/src/common/Protocol.ts index 1a5e124..0eedc09 100644 --- a/src/common/Protocol.ts +++ b/src/common/Protocol.ts @@ -60,6 +60,10 @@ const INPUT_EV_MOVE = 9 const INPUT_EV_TOGGLE_PREVIEW = 10 const INPUT_EV_TOGGLE_SOUNDS = 11 +const INPUT_EV_REPLAY_TOGGLE_PAUSE = 12 +const INPUT_EV_REPLAY_SPEED_UP = 13 +const INPUT_EV_REPLAY_SPEED_DOWN = 14 + const CHANGE_DATA = 1 const CHANGE_TILE = 2 const CHANGE_PLAYER = 3 @@ -90,6 +94,10 @@ export default { INPUT_EV_TOGGLE_PREVIEW, INPUT_EV_TOGGLE_SOUNDS, + INPUT_EV_REPLAY_TOGGLE_PAUSE, + INPUT_EV_REPLAY_SPEED_UP, + INPUT_EV_REPLAY_SPEED_DOWN, + CHANGE_DATA, CHANGE_TILE, CHANGE_PLAYER, diff --git a/src/frontend/components/HelpOverlay.vue b/src/frontend/components/HelpOverlay.vue index f57d8eb..4964eec 100644 --- a/src/frontend/components/HelpOverlay.vue +++ b/src/frontend/components/HelpOverlay.vue @@ -13,6 +13,10 @@ 🧩✔️ Toggle fixed pieces:
F
🧩❓ Toggle loose pieces:
G
🔉 Toggle sounds:
M
+ + ⏫ Speed up (replay):
I
+ ⏬ Speed down (replay):
O
+ ⏸️ Pause (replay):
P
diff --git a/src/frontend/game.ts b/src/frontend/game.ts index 2a7d3fd..7b14d4d 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -92,7 +92,12 @@ function addCanvasToDom(TARGET_EL: HTMLElement, canvas: HTMLCanvasElement) { return canvas } -function EventAdapter (canvas: HTMLCanvasElement, window: any, viewport: any) { +function EventAdapter ( + canvas: HTMLCanvasElement, + window: any, + viewport: any, + MODE: string +) { let events: Array = [] let KEYS_ON = true @@ -174,6 +179,20 @@ function EventAdapter (canvas: HTMLCanvasElement, window: any, viewport: any) { if (ev.key === ' ') { addEvent([Protocol.INPUT_EV_TOGGLE_PREVIEW]) } + + if (MODE === MODE_REPLAY) { + if (ev.key === 'I' || ev.key === 'i') { + addEvent([Protocol.INPUT_EV_REPLAY_SPEED_UP]) + } + + if (ev.key === 'O' || ev.key === 'o') { + addEvent([Protocol.INPUT_EV_REPLAY_SPEED_DOWN]) + } + + if (ev.key === 'P' || ev.key === 'p') { + addEvent([Protocol.INPUT_EV_REPLAY_TOGGLE_PAUSE]) + } + } if (ev.key === 'F' || ev.key === 'f') { PIECE_VIEW_FIXED = !PIECE_VIEW_FIXED RERENDER = true @@ -396,7 +415,7 @@ export async function main( -(TABLE_HEIGHT - canvas.height) /2 ) - const evts = EventAdapter(canvas, window, viewport) + const evts = EventAdapter(canvas, window, viewport, MODE) const previewImageUrl = Game.getImageUrl(gameId) @@ -699,7 +718,13 @@ export async function main( // LOCAL ONLY CHANGES // ------------------------------------------------------------- const type = evt[0] - if (type === Protocol.INPUT_EV_MOVE) { + if (type === Protocol.INPUT_EV_REPLAY_TOGGLE_PAUSE) { + replayOnPauseToggle() + } else if (type === Protocol.INPUT_EV_REPLAY_SPEED_DOWN) { + replayOnSpeedDown() + } else if (type === Protocol.INPUT_EV_REPLAY_SPEED_UP) { + replayOnSpeedUp() + } else if (type === Protocol.INPUT_EV_MOVE) { const diffX = evt[1] const diffY = evt[2] RERENDER = true From 849d39dac20d35cd8d106e8895eca0e170b01dee Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 5 Jun 2021 18:01:24 +0200 Subject: [PATCH 04/45] fix finalizing in 'real' snapmode, fix reading newly created replays --- .../assets/{index.c6197c7d.js => index.87048674.js} | 2 +- build/public/index.html | 2 +- build/server/main.js | 8 +++++++- src/common/GameCommon.ts | 7 +++++++ src/server/GameLog.ts | 2 +- 5 files changed, 17 insertions(+), 4 deletions(-) rename build/public/assets/{index.c6197c7d.js => index.87048674.js} (56%) diff --git a/build/public/assets/index.c6197c7d.js b/build/public/assets/index.87048674.js similarity index 56% rename from build/public/assets/index.c6197c7d.js rename to build/public/assets/index.87048674.js index 8e7d0fc..a83fb53 100644 --- a/build/public/assets/index.c6197c7d.js +++ b/build/public/assets/index.87048674.js @@ -1 +1 @@ -import{d as e,c as t,a as n,w as o,b as l,r as a,o as i,e as s,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as k,s as x,u as C,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const P={id:"app"},z={key:0,class:"nav"},I=s("Index"),D=s("New game");T.render=function(e,s,r,d,c,u){const p=a("router-link"),g=a("router-view");return i(),t("div",P,[e.showNav?(i(),t("ul",z,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[D])),_:1})])])):l("",!0),n(g)])};const E=864e5,_=e=>{const t=Math.floor(e/E);e%=E;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 M=1,N=1e3,O=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>_(t-e),U=_,R=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||O();return`${n} ${B(o,l)}`}}});const V={class:"game-info-text"},$=n("br",null,null,-1),G=n("br",null,null,-1),F=n("br",null,null,-1),L=s(" ↪️ Watch replay ");R.render=function(e,d,c,u,p,g){const h=a("router-link");return i(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",V,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),$,s(" 👥 "+r(e.game.players),1),G,s(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(i(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[L])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:R},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 W=n("h1",null,"Running games",-1),H=n("h1",null,"Finished games",-1);j.render=function(e,o,l,s,r,u){const p=a("game-teaser");return i(),t("div",null,[W,(i(!0),t(d,null,c(e.gamesRunning,((e,o)=>(i(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128)),H,(i(!0),t(d,null,c(e.gamesFinished,((e,o)=>(i(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128))])};var Y=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});Y.render=function(e,o,l,a,s,r){return i(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Q,q,Z,K,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:Y},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,s,r){const u=a("image-teaser");return i(),t("div",null,[(i(!0),t(d,null,c(e.images,((n,o)=>(i(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(q=Q||(Q={}))[q.Flat=0]="Flat",q[q.Out=1]="Out",q[q.In=-1]="In",(K=Z||(Z={}))[K.FINAL=0]="FINAL",K[K.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),i=le(o.getSeconds(),"00");console[t](`${l}:${a}:${i}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var ie={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Z.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const se={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};se.render=function(e,n,o,l,a,s){return i(),t("div",{style:s.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.key&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-39ed99c7");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,s,u,m)=>(i(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(i(),t("div",ce,[n("ul",null,[(i(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(i(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(i(!0),t(d,null,c(e.values,((n,o)=>(i(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-39ed99c7";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he={key:0,class:"has-image"},me={key:1},ye={class:"upload"},fe=n("span",{class:"btn"},"Upload File",-1),ve={class:"area-settings"},we=n("td",null,[n("label",null,"Title")],-1),be=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ke=n("td",null,[n("label",null,"Tags")],-1),xe={class:"area-buttons"},Ce=s("🧩 Post to gallery "),Ae=n("br",null,null,-1),Se=s(" + set up game");ge.render=function(e,o,l,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[e.previewUrl?(i(),t("div",he,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(i(),t("div",me,[n("label",ye,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),fe])]))],34),n("div",ve,[n("table",null,[n("tr",null,[we,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),be,n("tr",null,[ke,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",xe,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ce,Ae,Se],8,["disabled"])])])])};var Te=e({name:"edit-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Pe={class:"area-image"},ze={class:"has-image"},Ie={class:"area-settings"},De=n("td",null,[n("label",null,"Title")],-1),Ee=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),_e=n("td",null,[n("label",null,"Tags")],-1),Me={class:"area-buttons"};Te.render=function(e,o,l,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Pe,[n("div",ze,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ie,[n("table",null,[n("tr",null,[De,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ee,n("tr",null,[_e,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Me,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Ne=e({name:"new-game-dialog",components:{ResponsiveImage:se},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Z.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Oe={class:"area-image"},Be={class:"has-image"},Ue={key:0,class:"image-title"},Re={class:"area-settings"},Ve=n("td",null,[n("label",null,"Pieces")],-1),$e=n("td",null,[n("label",null,"Scoring: ")],-1),Ge=s(" Any (Score when pieces are connected to each other or on final location)"),Fe=n("br",null,null,-1),Le=s(" Final (Score when pieces are put to their final location)"),je=n("td",null,[n("label",null,"Shapes: ")],-1),We=s(" Normal"),He=n("br",null,null,-1),Ye=s(" Any (flat pieces can occur anywhere)"),Qe=n("br",null,null,-1),qe=s(" Flat (all pieces flat on all sides)"),Ze=n("td",null,[n("label",null,"Snapping: ")],-1),Ke=s(" Normal (pieces snap to final destination and to each other)"),Xe=n("br",null,null,-1),Je=s(" Real (pieces snap only to corners, already snapped pieces and to each other)"),et={class:"area-buttons"};Ne.render=function(e,o,s,d,c,h){const m=a("responsive-image");return i(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Oe,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(i(),t("div",Ue,'"'+r(e.image.title)+'"',1)):l("",!0)]),n("div",Re,[n("table",null,[n("tr",null,[Ve,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[$e,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Ge]),Fe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),Le])])]),n("tr",null,[je,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),We]),He,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ye]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),qe])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Ke]),Xe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),Je])])])])]),n("div",et,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var tt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Te,NewGameDialog:Ne},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${ie.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const nt={class:"upload-image-teaser"},ot=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),lt={key:0},at=s(" Tags: "),it=s(" Sort by: "),st=n("option",{value:"date_desc"},"Newest first",-1),rt=n("option",{value:"date_asc"},"Oldest first",-1),dt=n("option",{value:"alpha_asc"},"A-Z",-1),ct=n("option",{value:"alpha_desc"},"Z-A",-1);tt.render=function(e,o,s,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return i(),t("div",null,[n("div",nt,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),ot]),n("div",null,[e.tags.length>0?(i(),t("label",lt,[at,(i(!0),t(d,null,c(e.relevantTags,((n,o)=>(i(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[it,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[st,rt,dt,ct],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(i(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(i(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(i(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ut=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 pt={class:"scores"},gt=n("div",null,"Scores",-1),ht=n("td",null,"⚡",-1),mt=n("td",null,"💤",-1);ut.render=function(e,o,l,a,s,u){return i(),t("div",pt,[gt,n("table",null,[(i(!0),t(d,null,c(e.actives,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[ht,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(i(!0),t(d,null,c(e.idles,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[mt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var yt=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 U(this.duration)}}});const ft={class:"timer"};yt.render=function(e,o,l,a,s,d){return i(),t("div",ft,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var vt=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 wt=n("td",null,[n("label",null,"Background: ")],-1),bt=n("td",null,[n("label",null,"Color: ")],-1),kt=n("td",null,[n("label",null,"Name: ")],-1),xt=n("td",null,[n("label",null,"Sounds: ")],-1);vt.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:o[6]||(o[6]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[5]||(o[5]=u((()=>{}),["stop"]))},[n("tr",null,[wt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[bt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[kt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[xt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[k,e.modelValue.soundsEnabled]])])])])])};var Ct=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const At={class:"preview"};Ct.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",At,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var St=1,Tt=4,Pt=2,zt=3,It=2,Dt=4,Et=3,_t=9,Mt=1,Nt=2,Ot=3,Bt=4,Ut=5,Rt=6,Vt=7,$t=8,Gt=10,Ft=11,Lt=12,jt=13,Wt=14,Ht=1,Yt=2,Qt=3;const qt=ae("Communication.js");let Zt,Kt=[],Xt=e=>{Kt.push(e)},Jt=[],en=e=>{Jt.push(e)};let tn=0;const nn=e=>{tn!==e&&(tn=e,en(e))};function on(e){if(2===tn)try{Zt.send(JSON.stringify(e))}catch(t){qt.info("unable to send message.. maybe because ws is invalid?")}}let ln,an;var sn={connect:function(e,t,n){return ln=0,an={},nn(3),new Promise((o=>{Zt=new WebSocket(e,n+"|"+t),Zt.onopen=()=>{nn(2),on([zt])},Zt.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Tt){const e=t[1];o(e)}else{if(l!==St)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&an[o])return void delete an[o];Xt(t)}}},Zt.onerror=()=>{throw nn(1),"[ 2021-05-15 onerror ]"},Zt.onclose=e=>{4e3===e.code||1001===e.code?nn(4):nn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${ie.asQueryArgs(n)}`);return await o.json()},disconnect:function(){Zt&&Zt.close(4e3),ln=0,an={}},sendClientEvent:function(e){ln++,an[ln]=e,on([Pt,ln,an[ln]])},onServerChange:function(e){Xt=e;for(const t of Kt)Xt(t);Kt=[]},onConnectionStateChange:function(e){en=e;for(const t of Jt)en(t);Jt=[]},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},rn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===sn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===sn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const dn={key:0,class:"overlay connection-lost"},cn={key:0,class:"overlay-content"},un=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),pn={key:1,class:"overlay-content"},gn=n("div",null,"Connecting...",-1);rn.render=function(e,o,a,s,r,d){return e.show?(i(),t("div",dn,[e.lostConnection?(i(),t("div",cn,[un,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(i(),t("div",pn,[gn])):l("",!0)])):l("",!0)};var hn=e({name:"help-overlay",emits:{bgclick:null}});const mn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),yn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),fn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),vn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),wn=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),bn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),kn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),xn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Cn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),An=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Sn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),Tn=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),Pn=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),zn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);hn.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:o[2]||(o[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:o[1]||(o[1]=u((()=>{}),["stop"]))},[mn,yn,fn,vn,wn,bn,kn,xn,Cn,An,Sn,Tn,Pn,zn])])};var In=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Dn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),En=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),_n=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Mn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Nn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),i=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function On(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Bn={createCanvas:On,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=On(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=On(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(),o}};const Un=ae("Debug.js");let Rn=0,Vn=0;var $n=e=>{Rn=performance.now(),Vn=e},Gn=e=>{const t=performance.now(),n=t-Rn;n>Vn&&Un.log(e+": "+n),Rn=t};function Fn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function Ln(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var jn={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:Fn,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:Ln,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 Fn(Ln(e),Ln(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const Wn=ae("PuzzleGraphics.js");function Hn(e,t){const n=ie.coordByPieceIdx(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 Bn.loadImageToBitmap(e.info.imageUrl),n=await Bn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Wn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,i=o/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=jn.pointAdd(a,{x:o,y:0}),c=jn.pointAdd(r,{x:0,y:o}),u=jn.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;oie.decodePiece(Qn[e].puzzle.tiles[t]),ro=(e,t)=>so(e,t).group,co=(e,t)=>{const n=Qn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},uo=(e,t)=>{const n=Qn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Qn[e].puzzle.info,o=ie.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return jn.pointAdd(o,l)},po=(e,t)=>so(e,t).pos,go=e=>{const t=Do(e),n=Eo(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}},ho=(e,t)=>{const n=vo(e),o=so(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},mo=(e,t)=>so(e,t).z,yo=(e,t)=>{for(const n of Qn[e].puzzle.tiles){const e=ie.decodePiece(n);if(e.owner===t)return e.idx}return-1},fo=e=>Qn[e].puzzle.info.tileDrawSize,vo=e=>Qn[e].puzzle.info.tileSize,wo=e=>Qn[e].puzzle.data.maxGroup,bo=e=>Qn[e].puzzle.data.maxZ;function ko(e,t){const n=Qn[e].puzzle.info,o=ie.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const xo=(e,t,n)=>{for(const o of t)io(e,o,{z:n})},Co=(e,t,n)=>{const o=po(e,t);io(e,t,{pos:jn.pointAdd(o,n)})},Ao=(e,t,n)=>{const o=fo(e),l=go(e),a=n;for(const i of t){const t=so(e,i);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const i of t)Co(e,i,a)},So=(e,t)=>so(e,t).owner,To=(e,t)=>{for(const n of t)io(e,n,{owner:-1,z:1})},Po=(e,t,n)=>{for(const o of t)io(e,o,{owner:n})};function zo(e,t){const n=Qn[e].puzzle.tiles,o=ie.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=ie.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const Io=(e,t)=>{const n=Zn(e,t);return n?n.points:0},Do=e=>Qn[e].puzzle.info.table.width,Eo=e=>Qn[e].puzzle.info.table.height;var _o={setGame:function(e,t){Qn[e]=t},exists:function(e){return!!Qn[e]||!1},playerExists:Xn,getActivePlayers:function(e,t){const n=t-30*N;return Jn(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*N;return Jn(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Xn(e,t)?lo(e,t,{ts:n}):Kn(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:oo,getPieceCount:eo,getImageUrl:function(e){return Qn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Qn[e].puzzle.info.imageUrl=t},get:function(e){return Qn[e]||null},getAllGames:function(){return Object.values(Qn).sort(((e,t)=>no(e.id)===no(t.id)?t.puzzle.data.started-e.puzzle.data.started:no(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Zn(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Zn(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Zn(e,t);return n?n.name:null},getPlayerIndexById:qn,getPlayerIdByIndex:function(e,t){return Qn[e].players.length>t?ie.decodePlayer(Qn[e].players[t]).id:null},changePlayer:lo,setPlayer:Kn,setPiece:function(e,t,n){Qn[e].puzzle.tiles[t]=ie.encodePiece(n)},setPuzzleData:function(e,t){Qn[e].puzzle.data=t},getTableWidth:Do,getTableHeight:Eo,getPuzzle:e=>Qn[e].puzzle,getRng:e=>Qn[e].rng.obj,getPuzzleWidth:e=>Qn[e].puzzle.info.width,getPuzzleHeight:e=>Qn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Qn[e].puzzle.tiles.map(ie.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=yo(e,t);return n<0?null:Qn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Qn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:fo,getFinalPiecePos:uo,getStartTs:e=>Qn[e].puzzle.data.started,getFinishTs:e=>Qn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Qn[e].puzzle,i=function(e,t){return t in Qn[e].evtInfos?Qn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],r=()=>{s.push([Ht,a.data])},d=t=>{s.push([Yt,ie.encodePiece(so(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=Zn(e,t);n&&s.push([Qt,ie.encodePlayer(n)])},p=n[0];if(p===Rt){const l=n[1];lo(e,t,{bgcolor:l,ts:o}),u()}else if(p===Vt){const l=n[1];lo(e,t,{color:l,ts:o}),u()}else if(p===$t){const l=`${n[1]}`.substr(0,16);lo(e,t,{name:l,ts:o}),u()}else if(p===_t){const l=n[1],a=n[2],i=Zn(e,t);if(i){const n=i.x-l,s=i.y-a;lo(e,t,{ts:o,x:n,y:s}),u()}}else if(p===Mt){const l={x:n[1],y:n[2]};lo(e,t,{d:1,ts:o}),u(),i._last_mouse_down=l;const a=((e,t)=>{const n=Qn[e].puzzle.info,o=Qn[e].puzzle.tiles;let l=-1,a=-1;for(let i=0;il)&&(l=e.z,a=i)}return a})(e,l);if(a>=0){const n=bo(e)+1;ao(e,{maxZ:n}),r();const o=zo(e,a);xo(e,o,bo(e)),Po(e,o,t),c(o)}i._last_mouse=l}else if(p===Ot){const l=n[1],a=n[2],s={x:l,y:a};if(null===i._last_mouse_down)lo(e,t,{x:l,y:a,ts:o}),u();else{const n=yo(e,t);if(n>=0){lo(e,t,{x:l,y:a,ts:o}),u();const r=zo(e,n);let d=jn.pointInBounds(s,go(e))&&jn.pointInBounds(i._last_mouse_down,go(e));for(const t of r){const n=ho(e,t);if(jn.pointInBounds(s,n)){d=!0;break}}if(d){const t=l-i._last_mouse_down.x,n=a-i._last_mouse_down.y;Ao(e,r,{x:t,y:n}),c(r)}}else lo(e,t,{ts:o}),u();i._last_mouse_down=s}i._last_mouse=s}else if(p===Nt){const s={x:n[1],y:n[2]},p=0;i._last_mouse_down=null;const g=yo(e,t);if(g>=0){const n=zo(e,g);Po(e,n,0),c(n);const i=po(e,g),s=uo(e,g);let h=!1;if(function(e){return Qn[e].snapMode||ee.NORMAL}(e)===ee.REAL){for(const t of n)if(co(e,t)){h=!0;break}}else h=!0;if(h&&jn.pointDistance(s,i){const l=Qn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=ro(e,t),l=ro(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=po(e,t),i=jn.pointAdd(po(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(jn.pointDistance(a,i){const o=Qn[e].puzzle.tiles,l=ro(e,t),a=ro(e,n);let i;const s=[];l&&s.push(l),a&&s.push(a),l?i=l:a?i=a:(ao(e,{maxGroup:wo(e)+1}),r(),i=wo(e));if(io(e,t,{group:i}),d(t),io(e,n,{group:i}),d(n),s.length>0)for(const r of o){const t=ie.decodePiece(r);s.includes(t.group)&&(io(e,t.idx,{group:i}),d(t.idx))}})(e,t,n),l=zo(e,t),((e,t)=>-1===So(e,t))(e,n))To(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=mo(e,o);t>n&&(n=t)}return n})(e,l);xo(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of zo(e,g)){const o=ko(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])){a=!0;break}}if(a&&to(e)===Z.ANY){const n=Io(e,t)+1;lo(e,t,{d:p,ts:o,points:n}),u()}else lo(e,t,{d:p,ts:o}),u();a&&l&&l(t)}}else lo(e,t,{d:p,ts:o}),u();i._last_mouse=s}else if(p===Bt){const l=n[1],a=n[2];lo(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else if(p===Ut){const l=n[1],a=n[2];lo(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else lo(e,t,{ts:o}),u();return function(e,t,n){Qn[e].evtInfos[t]=n}(e,t,i),s}};let Mo=-10,No=20,Oo=2,Bo=15;class Uo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Mo+Math.random()*No,this.vy=-1*(Oo+Math.random()*Bo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Oo=t/2,Bo=t-Oo;const n=1/4*this.canvas.width/(t/2);Mo=-n,No=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Uo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Uo(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(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Bn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,jo=!0})),t}(l,Bn.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};sn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await sn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let k=()=>0;const x=async()=>{if("play"===o){const o=await sn.connect(n,e,t),l=ie.decodeGame(o);_o.setGame(l.id,l),k=()=>O()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=ie.decodeGame(t.game);_o.setGame(n.id,n),w.lastRealTs=O(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,k=()=>w.lastGameTs}}jo=!0};await x();const C=_o.getPieceDrawOffset(e),A=_o.getPieceDrawSize(e),S=_o.getPuzzleWidth(e),T=_o.getPuzzleHeight(e),P=_o.getTableWidth(e),z=_o.getTableHeight(e),I={x:(P-S)/2,y:(z-T)/2},D={w:S,h:T},E={w:A,h:A},_=await Yn.loadPuzzleBitmaps(_o.getPuzzle(e)),N=new Vo(v,_o.getRng(e));N.init();const B=v.getContext("2d");v.classList.add("loaded");const U=Nn();U.move(-(P-v.width)/2,-(z-v.height)/2);const R=function(e,t,n,o){let l=[],a=!0,i=!1,s=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("Shift"===t.key?p=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?r=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?d=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?u=e:"e"===t.key&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Mt,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Nt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([Ot,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Bt:Ut;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&(" "===e.key&&v([Gt]),"replay"===o&&("I"!==e.key&&"i"!==e.key||v([jt]),"O"!==e.key&&"o"!==e.key||v([Wt]),"P"!==e.key&&"p"!==e.key||v([Lt])),"F"!==e.key&&"f"!==e.key||(Fo=!Fo,jo=!0),"G"!==e.key&&"g"!==e.key||(Lo=!Lo,jo=!0),"M"!==e.key&&"m"!==e.key||v([Ft]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(i?1:0)-(s?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});v([_t,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Bt,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([Ut,...e])}},setHotkeys:e=>{a=e}}}(v,window,U,o),V=_o.getImageUrl(e),$=()=>{const t=_o.getStartTs(e),n=_o.getFinishTs(e),o=k();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(_o.getFinishedPiecesCount(e)),a.setPiecesTotal(_o.getPieceCount(e));const G=k();a.setActivePlayers(_o.getActivePlayers(e,G)),a.setIdlePlayers(_o.getIdlePlayers(e,G));const F=!!_o.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},H=()=>_o.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Y=()=>_o.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let Q="",q="",Z=!1;const K=e=>{Z=e;const[t,n]=e?[Q,"grab"]:[q,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},X=e=>{Q=Bn.colorizedCanvas(r,c,e).toDataURL(),q=Bn.colorizedCanvas(d,u,e).toDataURL(),K(Z)};X(Y());const J=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ee=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,J())},ne=()=>{w.paused=!w.paused,J()},oe=[];let le;let ae;if("play"===o?oe.push(setInterval((()=>{$()}),1e3)):"replay"===o&&J(),"play"===o)sn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Qt:{const n=ie.decodePlayer(a);n.id!==t&&(_o.setPlayer(e,n.id,n),jo=!0)}break;case Yt:{const t=ie.decodePiece(a);_o.setPiece(e,t.idx,t),jo=!0}break;case Ht:_o.setPuzzleData(e,a),jo=!0}L=!!_o.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===It){const t=o[1];return _o.addPlayer(e,t,n),!0}if(o[0]===Dt){const t=_o.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return _o.addPlayer(e,t,n),!0}if(o[0]===Et){const t=_o.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return _o.handleInput(e,t,l,n),!0}return!1},n=async()=>{w.logPointer+1>=w.log.length&&await b(e);const o=O();if(w.paused)return w.lastRealTs=o,void(le=setTimeout(n,50));const l=(o-w.lastRealTs)*w.speeds[w.speedIdx];let a=w.lastGameTs+l;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const n=w.log[w.logPointer],o=w.gameStartTs+n[n.length-1],l=w.log[e],i=w.gameStartTs+l[l.length-1];if(i>a){if(w.skipNonActionPhases&&a+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,i=window.requestAnimationFrame,s=1/n,r=o*s;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(s);a(c/o),u=d,t||i(p)};return i(p),{stop:()=>{t=!0}}})({update:()=>{R.createKeyEvents();for(const n of R.consumeAll())if("play"===o){const o=n[0];if(o===_t){const e=n[1],t=n[2],o=U.worldDimToViewport({w:e,h:t});jo=!0,U.move(o.w,o.h)}else if(o===Ot){if(se&&!_o.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-se.x),l=Math.round(t.y-se.y);jo=!0,U.move(o,l),se=t}}else if(o===Vt)X(n[1]);else if(o===Mt){const e={x:n[1],y:n[2]};se=U.worldToViewport(e),K(!0)}else if(o===Nt)se=null,K(!1);else if(o===Bt){const e={x:n[1],y:n[2]};jo=!0,U.zoom("in",U.worldToViewport(e))}else if(o===Ut){const e={x:n[1],y:n[2]};jo=!0,U.zoom("out",U.worldToViewport(e))}else o===Gt?a.togglePreview():o===Ft&&a.toggleSoundsEnabled();const l=k();_o.handleInput(e,t,n,l,(e=>{W()&&s.play()})).length>0&&(jo=!0),sn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===Lt)ne();else if(e===Wt)te();else if(e===jt)ee();else if(e===_t){const e=n[1],t=n[2];jo=!0,U.move(e,t)}else if(e===Ot){if(se){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-se.x),l=Math.round(t.y-se.y);jo=!0,U.move(o,l),se=t}}else if(e===Mt){const e={x:n[1],y:n[2]};se=U.worldToViewport(e)}else if(e===Nt)se=null;else if(e===Bt){const e={x:n[1],y:n[2]};jo=!0,U.zoom("in",U.worldToViewport(e))}else if(e===Ut){const e={x:n[1],y:n[2]};jo=!0,U.zoom("out",U.worldToViewport(e))}else e===Gt&&a.togglePreview()}L=!!_o.getFinishTs(e),j()&&(N.update(),jo=!0)},render:async()=>{if(!jo)return;const n=k();let l,i,s;window.DEBUG&&$n(0),B.fillStyle=H(),B.fillRect(0,0,v.width,v.height),window.DEBUG&&Gn("clear done"),l=U.worldToViewportRaw(I),i=U.worldDimToViewportRaw(D),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(l.x,l.y,i.w,i.h),window.DEBUG&&Gn("board done");const r=_o.getPiecesSortedByZIndex(e);window.DEBUG&&Gn("get tiles done"),i=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?Fo:Lo)&&(s=_[e.idx],l=U.worldToViewportRaw({x:C+e.pos.x,y:C+e.pos.y}),B.drawImage(s,0,0,s.width,s.height,l.x,l.y,i.w,i.h));window.DEBUG&&Gn("tiles done");const d=[];for(const a of _o.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(s=await f(a),l=U.worldToViewport(a),B.drawImage(s,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,o]of d)B.fillText(e,t,o);window.DEBUG&&Gn("players done"),a.setActivePlayers(_o.getActivePlayers(e,n)),a.setIdlePlayers(_o.getIdlePlayers(e,n)),a.setPiecesDone(_o.getFinishedPiecesCount(e)),window.DEBUG&&Gn("HUD done"),j()&&N.render(),jo=!1}}),{setHotkeys:e=>{R.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),R.addEvent([Rt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),R.addEvent([Vt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),R.addEvent([$t,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},replayOnSpeedUp:ee,replayOnSpeedDown:te,replayOnPauseToggle:ne,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:V,player:{background:H(),color:Y(),name:_o.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:W()},disconnect:sn.disconnect,connect:x,unload:()=>{oe.forEach((e=>{clearInterval(e)})),le&&clearTimeout(le),ae&&ae.stop()}}}var Ho=e({name:"game",components:{PuzzleStatus:yt,Scores:ut,SettingsOverlay:vt,PreviewOverlay:Ct,ConnectionOverlay:rn,HelpOverlay:hn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Wo(`${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)},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 Yo={id:"game"},Qo={class:"menu"},qo={class:"tabs"},Zo=s("🧩 Puzzles");Ho.render=function(e,l,s,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Yo,[p(n(u,{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"]),[[x,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",Qo,[n("div",qo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Zo])),_: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 Ko=e({name:"replay",components:{PuzzleStatus:yt,Scores:ut,SettingsOverlay:vt,PreviewOverlay:Ct,HelpOverlay:hn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Wo(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 Xo={id:"replay"},Jo=s("Skip no action phases: "),el={class:"menu"},tl={class:"tabs"},nl=s("🧩 Puzzles");Ko.render=function(e,l,s,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Xo,[p(n(g,{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"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[Jo,p(n("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[k,e.skipNoAction]])])]),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",el,[n("div",tl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[nl])),_:1}),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:l[12]||(l[12]=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=C({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:tt},{name:"game",path:"/g/:id",component:Ho},{name:"replay",path:"/replay/:id",component:Ko}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=S(T);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=ie.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); +import{d as e,c as t,a as n,w as o,b as l,r as a,o as i,e as s,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as k,s as x,u as C,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const P={id:"app"},z={key:0,class:"nav"},I=s("Index"),D=s("New game");T.render=function(e,s,r,d,c,u){const p=a("router-link"),g=a("router-view");return i(),t("div",P,[e.showNav?(i(),t("ul",z,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[D])),_:1})])])):l("",!0),n(g)])};const E=864e5,_=e=>{const t=Math.floor(e/E);e%=E;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 M=1,N=1e3,O=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>_(t-e),U=_,R=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||O();return`${n} ${B(o,l)}`}}});const V={class:"game-info-text"},$=n("br",null,null,-1),G=n("br",null,null,-1),F=n("br",null,null,-1),L=s(" ↪️ Watch replay ");R.render=function(e,d,c,u,p,g){const h=a("router-link");return i(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",V,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),$,s(" 👥 "+r(e.game.players),1),G,s(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(i(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[L])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:R},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 W=n("h1",null,"Running games",-1),H=n("h1",null,"Finished games",-1);j.render=function(e,o,l,s,r,u){const p=a("game-teaser");return i(),t("div",null,[W,(i(!0),t(d,null,c(e.gamesRunning,((e,o)=>(i(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128)),H,(i(!0),t(d,null,c(e.gamesFinished,((e,o)=>(i(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128))])};var Y=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});Y.render=function(e,o,l,a,s,r){return i(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Q,q,Z,K,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:Y},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,s,r){const u=a("image-teaser");return i(),t("div",null,[(i(!0),t(d,null,c(e.images,((n,o)=>(i(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(q=Q||(Q={}))[q.Flat=0]="Flat",q[q.Out=1]="Out",q[q.In=-1]="In",(K=Z||(Z={}))[K.FINAL=0]="FINAL",K[K.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),i=le(o.getSeconds(),"00");console[t](`${l}:${a}:${i}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var ie={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Z.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const se={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};se.render=function(e,n,o,l,a,s){return i(),t("div",{style:s.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.key&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-39ed99c7");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,s,u,m)=>(i(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(i(),t("div",ce,[n("ul",null,[(i(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(i(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(i(!0),t(d,null,c(e.values,((n,o)=>(i(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-39ed99c7";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he={key:0,class:"has-image"},me={key:1},ye={class:"upload"},fe=n("span",{class:"btn"},"Upload File",-1),ve={class:"area-settings"},we=n("td",null,[n("label",null,"Title")],-1),be=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ke=n("td",null,[n("label",null,"Tags")],-1),xe={class:"area-buttons"},Ce=s("🧩 Post to gallery "),Ae=n("br",null,null,-1),Se=s(" + set up game");ge.render=function(e,o,l,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[e.previewUrl?(i(),t("div",he,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(i(),t("div",me,[n("label",ye,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),fe])]))],34),n("div",ve,[n("table",null,[n("tr",null,[we,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),be,n("tr",null,[ke,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",xe,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ce,Ae,Se],8,["disabled"])])])])};var Te=e({name:"edit-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Pe={class:"area-image"},ze={class:"has-image"},Ie={class:"area-settings"},De=n("td",null,[n("label",null,"Title")],-1),Ee=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),_e=n("td",null,[n("label",null,"Tags")],-1),Me={class:"area-buttons"};Te.render=function(e,o,l,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Pe,[n("div",ze,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ie,[n("table",null,[n("tr",null,[De,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ee,n("tr",null,[_e,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Me,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Ne=e({name:"new-game-dialog",components:{ResponsiveImage:se},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Z.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Oe={class:"area-image"},Be={class:"has-image"},Ue={key:0,class:"image-title"},Re={class:"area-settings"},Ve=n("td",null,[n("label",null,"Pieces")],-1),$e=n("td",null,[n("label",null,"Scoring: ")],-1),Ge=s(" Any (Score when pieces are connected to each other or on final location)"),Fe=n("br",null,null,-1),Le=s(" Final (Score when pieces are put to their final location)"),je=n("td",null,[n("label",null,"Shapes: ")],-1),We=s(" Normal"),He=n("br",null,null,-1),Ye=s(" Any (flat pieces can occur anywhere)"),Qe=n("br",null,null,-1),qe=s(" Flat (all pieces flat on all sides)"),Ze=n("td",null,[n("label",null,"Snapping: ")],-1),Ke=s(" Normal (pieces snap to final destination and to each other)"),Xe=n("br",null,null,-1),Je=s(" Real (pieces snap only to corners, already snapped pieces and to each other)"),et={class:"area-buttons"};Ne.render=function(e,o,s,d,c,h){const m=a("responsive-image");return i(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Oe,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(i(),t("div",Ue,'"'+r(e.image.title)+'"',1)):l("",!0)]),n("div",Re,[n("table",null,[n("tr",null,[Ve,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[$e,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Ge]),Fe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),Le])])]),n("tr",null,[je,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),We]),He,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ye]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),qe])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Ke]),Xe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),Je])])])])]),n("div",et,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var tt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Te,NewGameDialog:Ne},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${ie.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const nt={class:"upload-image-teaser"},ot=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),lt={key:0},at=s(" Tags: "),it=s(" Sort by: "),st=n("option",{value:"date_desc"},"Newest first",-1),rt=n("option",{value:"date_asc"},"Oldest first",-1),dt=n("option",{value:"alpha_asc"},"A-Z",-1),ct=n("option",{value:"alpha_desc"},"Z-A",-1);tt.render=function(e,o,s,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return i(),t("div",null,[n("div",nt,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),ot]),n("div",null,[e.tags.length>0?(i(),t("label",lt,[at,(i(!0),t(d,null,c(e.relevantTags,((n,o)=>(i(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[it,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[st,rt,dt,ct],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(i(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(i(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(i(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ut=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 pt={class:"scores"},gt=n("div",null,"Scores",-1),ht=n("td",null,"⚡",-1),mt=n("td",null,"💤",-1);ut.render=function(e,o,l,a,s,u){return i(),t("div",pt,[gt,n("table",null,[(i(!0),t(d,null,c(e.actives,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[ht,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(i(!0),t(d,null,c(e.idles,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[mt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var yt=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 U(this.duration)}}});const ft={class:"timer"};yt.render=function(e,o,l,a,s,d){return i(),t("div",ft,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var vt=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 wt=n("td",null,[n("label",null,"Background: ")],-1),bt=n("td",null,[n("label",null,"Color: ")],-1),kt=n("td",null,[n("label",null,"Name: ")],-1),xt=n("td",null,[n("label",null,"Sounds: ")],-1);vt.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:o[6]||(o[6]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[5]||(o[5]=u((()=>{}),["stop"]))},[n("tr",null,[wt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[bt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[kt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[xt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[k,e.modelValue.soundsEnabled]])])])])])};var Ct=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const At={class:"preview"};Ct.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",At,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var St=1,Tt=4,Pt=2,zt=3,It=2,Dt=4,Et=3,_t=9,Mt=1,Nt=2,Ot=3,Bt=4,Ut=5,Rt=6,Vt=7,$t=8,Gt=10,Ft=11,Lt=12,jt=13,Wt=14,Ht=1,Yt=2,Qt=3;const qt=ae("Communication.js");let Zt,Kt=[],Xt=e=>{Kt.push(e)},Jt=[],en=e=>{Jt.push(e)};let tn=0;const nn=e=>{tn!==e&&(tn=e,en(e))};function on(e){if(2===tn)try{Zt.send(JSON.stringify(e))}catch(t){qt.info("unable to send message.. maybe because ws is invalid?")}}let ln,an;var sn={connect:function(e,t,n){return ln=0,an={},nn(3),new Promise((o=>{Zt=new WebSocket(e,n+"|"+t),Zt.onopen=()=>{nn(2),on([zt])},Zt.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Tt){const e=t[1];o(e)}else{if(l!==St)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&an[o])return void delete an[o];Xt(t)}}},Zt.onerror=()=>{throw nn(1),"[ 2021-05-15 onerror ]"},Zt.onclose=e=>{4e3===e.code||1001===e.code?nn(4):nn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${ie.asQueryArgs(n)}`);return await o.json()},disconnect:function(){Zt&&Zt.close(4e3),ln=0,an={}},sendClientEvent:function(e){ln++,an[ln]=e,on([Pt,ln,an[ln]])},onServerChange:function(e){Xt=e;for(const t of Kt)Xt(t);Kt=[]},onConnectionStateChange:function(e){en=e;for(const t of Jt)en(t);Jt=[]},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},rn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===sn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===sn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const dn={key:0,class:"overlay connection-lost"},cn={key:0,class:"overlay-content"},un=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),pn={key:1,class:"overlay-content"},gn=n("div",null,"Connecting...",-1);rn.render=function(e,o,a,s,r,d){return e.show?(i(),t("div",dn,[e.lostConnection?(i(),t("div",cn,[un,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(i(),t("div",pn,[gn])):l("",!0)])):l("",!0)};var hn=e({name:"help-overlay",emits:{bgclick:null}});const mn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),yn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),fn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),vn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),wn=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),bn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),kn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),xn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Cn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),An=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Sn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),Tn=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),Pn=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),zn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);hn.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:o[2]||(o[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:o[1]||(o[1]=u((()=>{}),["stop"]))},[mn,yn,fn,vn,wn,bn,kn,xn,Cn,An,Sn,Tn,Pn,zn])])};var In=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Dn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),En=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),_n=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Mn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Nn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),i=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function On(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Bn={createCanvas:On,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=On(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=On(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(),o}};const Un=ae("Debug.js");let Rn=0,Vn=0;var $n=e=>{Rn=performance.now(),Vn=e},Gn=e=>{const t=performance.now(),n=t-Rn;n>Vn&&Un.log(e+": "+n),Rn=t};function Fn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function Ln(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var jn={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:Fn,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:Ln,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 Fn(Ln(e),Ln(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const Wn=ae("PuzzleGraphics.js");function Hn(e,t){const n=ie.coordByPieceIdx(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 Bn.loadImageToBitmap(e.info.imageUrl),n=await Bn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Wn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,i=o/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=jn.pointAdd(a,{x:o,y:0}),c=jn.pointAdd(r,{x:0,y:o}),u=jn.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;oie.decodePiece(Qn[e].puzzle.tiles[t]),co=(e,t)=>ro(e,t).group,uo=(e,t)=>{const n=Qn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},po=(e,t)=>{const n=Qn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Qn[e].puzzle.info,o=ie.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return jn.pointAdd(o,l)},go=(e,t)=>ro(e,t).pos,ho=e=>{const t=Eo(e),n=_o(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}},mo=(e,t)=>{const n=wo(e),o=ro(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},yo=(e,t)=>ro(e,t).z,fo=(e,t)=>{for(const n of Qn[e].puzzle.tiles){const e=ie.decodePiece(n);if(e.owner===t)return e.idx}return-1},vo=e=>Qn[e].puzzle.info.tileDrawSize,wo=e=>Qn[e].puzzle.info.tileSize,bo=e=>Qn[e].puzzle.data.maxGroup,ko=e=>Qn[e].puzzle.data.maxZ;function xo(e,t){const n=Qn[e].puzzle.info,o=ie.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Co=(e,t,n)=>{for(const o of t)so(e,o,{z:n})},Ao=(e,t,n)=>{const o=go(e,t);so(e,t,{pos:jn.pointAdd(o,n)})},So=(e,t,n)=>{const o=vo(e),l=ho(e),a=n;for(const i of t){const t=ro(e,i);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const i of t)Ao(e,i,a)},To=(e,t)=>ro(e,t).owner,Po=(e,t)=>{for(const n of t)so(e,n,{owner:-1,z:1})},zo=(e,t,n)=>{for(const o of t)so(e,o,{owner:n})};function Io(e,t){const n=Qn[e].puzzle.tiles,o=ie.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=ie.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const Do=(e,t)=>{const n=Zn(e,t);return n?n.points:0},Eo=e=>Qn[e].puzzle.info.table.width,_o=e=>Qn[e].puzzle.info.table.height;var Mo={setGame:function(e,t){Qn[e]=t},exists:function(e){return!!Qn[e]||!1},playerExists:Xn,getActivePlayers:function(e,t){const n=t-30*N;return Jn(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*N;return Jn(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Xn(e,t)?ao(e,t,{ts:n}):Kn(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:lo,getPieceCount:eo,getImageUrl:function(e){return Qn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Qn[e].puzzle.info.imageUrl=t},get:function(e){return Qn[e]||null},getAllGames:function(){return Object.values(Qn).sort(((e,t)=>oo(e.id)===oo(t.id)?t.puzzle.data.started-e.puzzle.data.started:oo(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Zn(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Zn(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Zn(e,t);return n?n.name:null},getPlayerIndexById:qn,getPlayerIdByIndex:function(e,t){return Qn[e].players.length>t?ie.decodePlayer(Qn[e].players[t]).id:null},changePlayer:ao,setPlayer:Kn,setPiece:function(e,t,n){Qn[e].puzzle.tiles[t]=ie.encodePiece(n)},setPuzzleData:function(e,t){Qn[e].puzzle.data=t},getTableWidth:Eo,getTableHeight:_o,getPuzzle:e=>Qn[e].puzzle,getRng:e=>Qn[e].rng.obj,getPuzzleWidth:e=>Qn[e].puzzle.info.width,getPuzzleHeight:e=>Qn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Qn[e].puzzle.tiles.map(ie.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=fo(e,t);return n<0?null:Qn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Qn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:vo,getFinalPiecePos:po,getStartTs:e=>Qn[e].puzzle.data.started,getFinishTs:e=>Qn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Qn[e].puzzle,i=function(e,t){return t in Qn[e].evtInfos?Qn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],r=()=>{s.push([Ht,a.data])},d=t=>{s.push([Yt,ie.encodePiece(ro(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=Zn(e,t);n&&s.push([Qt,ie.encodePlayer(n)])},p=n[0];if(p===Rt){const l=n[1];ao(e,t,{bgcolor:l,ts:o}),u()}else if(p===Vt){const l=n[1];ao(e,t,{color:l,ts:o}),u()}else if(p===$t){const l=`${n[1]}`.substr(0,16);ao(e,t,{name:l,ts:o}),u()}else if(p===_t){const l=n[1],a=n[2],i=Zn(e,t);if(i){const n=i.x-l,s=i.y-a;ao(e,t,{ts:o,x:n,y:s}),u()}}else if(p===Mt){const l={x:n[1],y:n[2]};ao(e,t,{d:1,ts:o}),u(),i._last_mouse_down=l;const a=((e,t)=>{const n=Qn[e].puzzle.info,o=Qn[e].puzzle.tiles;let l=-1,a=-1;for(let i=0;il)&&(l=e.z,a=i)}return a})(e,l);if(a>=0){const n=ko(e)+1;io(e,{maxZ:n}),r();const o=Io(e,a);Co(e,o,ko(e)),zo(e,o,t),c(o)}i._last_mouse=l}else if(p===Ot){const l=n[1],a=n[2],s={x:l,y:a};if(null===i._last_mouse_down)ao(e,t,{x:l,y:a,ts:o}),u();else{const n=fo(e,t);if(n>=0){ao(e,t,{x:l,y:a,ts:o}),u();const r=Io(e,n);let d=jn.pointInBounds(s,ho(e))&&jn.pointInBounds(i._last_mouse_down,ho(e));for(const t of r){const n=mo(e,t);if(jn.pointInBounds(s,n)){d=!0;break}}if(d){const t=l-i._last_mouse_down.x,n=a-i._last_mouse_down.y;So(e,r,{x:t,y:n}),c(r)}}else ao(e,t,{ts:o}),u();i._last_mouse_down=s}i._last_mouse=s}else if(p===Nt){const s={x:n[1],y:n[2]},p=0;i._last_mouse_down=null;const g=fo(e,t);if(g>=0){const n=Io(e,g);zo(e,n,0),c(n);const i=go(e,g),s=po(e,g);let h=!1;if(no(e)===ee.REAL){for(const t of n)if(uo(e,t)){h=!0;break}}else h=!0;if(h&&jn.pointDistance(s,i){const l=Qn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=co(e,t),l=co(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=go(e,t),i=jn.pointAdd(go(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(jn.pointDistance(a,i){const o=Qn[e].puzzle.tiles,l=co(e,t),a=co(e,n);let i;const s=[];l&&s.push(l),a&&s.push(a),l?i=l:a?i=a:(io(e,{maxGroup:bo(e)+1}),r(),i=bo(e));if(so(e,t,{group:i}),d(t),so(e,n,{group:i}),d(n),s.length>0)for(const r of o){const t=ie.decodePiece(r);s.includes(t.group)&&(so(e,t.idx,{group:i}),d(t.idx))}})(e,t,n),l=Io(e,t),((e,t)=>-1===To(e,t))(e,n))Po(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=yo(e,o);t>n&&(n=t)}return n})(e,l);Co(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Io(e,g)){const o=xo(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])){a=!0;break}}if(a&&to(e)===Z.ANY){const n=Do(e,t)+1;ao(e,t,{d:p,ts:o,points:n}),u()}else ao(e,t,{d:p,ts:o}),u();a&&no(e)===ee.REAL&&lo(e)===eo(e)&&(io(e,{finished:o}),r()),a&&l&&l(t)}}else ao(e,t,{d:p,ts:o}),u();i._last_mouse=s}else if(p===Bt){const l=n[1],a=n[2];ao(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else if(p===Ut){const l=n[1],a=n[2];ao(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else ao(e,t,{ts:o}),u();return function(e,t,n){Qn[e].evtInfos[t]=n}(e,t,i),s}};let No=-10,Oo=20,Bo=2,Uo=15;class Ro{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=No+Math.random()*Oo,this.vy=-1*(Bo+Math.random()*Uo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Bo=t/2,Uo=t-Bo;const n=1/4*this.canvas.width/(t/2);No=-n,Oo=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Ro(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Ro(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(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Bn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Wo=!0})),t}(l,Bn.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};sn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await sn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let k=()=>0;const x=async()=>{if("play"===o){const o=await sn.connect(n,e,t),l=ie.decodeGame(o);Mo.setGame(l.id,l),k=()=>O()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=ie.decodeGame(t.game);Mo.setGame(n.id,n),w.lastRealTs=O(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,k=()=>w.lastGameTs}}Wo=!0};await x();const C=Mo.getPieceDrawOffset(e),A=Mo.getPieceDrawSize(e),S=Mo.getPuzzleWidth(e),T=Mo.getPuzzleHeight(e),P=Mo.getTableWidth(e),z=Mo.getTableHeight(e),I={x:(P-S)/2,y:(z-T)/2},D={w:S,h:T},E={w:A,h:A},_=await Yn.loadPuzzleBitmaps(Mo.getPuzzle(e)),N=new $o(v,Mo.getRng(e));N.init();const B=v.getContext("2d");v.classList.add("loaded");const U=Nn();U.move(-(P-v.width)/2,-(z-v.height)/2);const R=function(e,t,n,o){let l=[],a=!0,i=!1,s=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("Shift"===t.key?p=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?r=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?d=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?u=e:"e"===t.key&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Mt,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Nt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([Ot,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Bt:Ut;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&(" "===e.key&&v([Gt]),"replay"===o&&("I"!==e.key&&"i"!==e.key||v([jt]),"O"!==e.key&&"o"!==e.key||v([Wt]),"P"!==e.key&&"p"!==e.key||v([Lt])),"F"!==e.key&&"f"!==e.key||(Lo=!Lo,Wo=!0),"G"!==e.key&&"g"!==e.key||(jo=!jo,Wo=!0),"M"!==e.key&&"m"!==e.key||v([Ft]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(i?1:0)-(s?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});v([_t,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Bt,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([Ut,...e])}},setHotkeys:e=>{a=e}}}(v,window,U,o),V=Mo.getImageUrl(e),$=()=>{const t=Mo.getStartTs(e),n=Mo.getFinishTs(e),o=k();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(Mo.getFinishedPiecesCount(e)),a.setPiecesTotal(Mo.getPieceCount(e));const G=k();a.setActivePlayers(Mo.getActivePlayers(e,G)),a.setIdlePlayers(Mo.getIdlePlayers(e,G));const F=!!Mo.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},H=()=>Mo.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Y=()=>Mo.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let Q="",q="",Z=!1;const K=e=>{Z=e;const[t,n]=e?[Q,"grab"]:[q,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},X=e=>{Q=Bn.colorizedCanvas(r,c,e).toDataURL(),q=Bn.colorizedCanvas(d,u,e).toDataURL(),K(Z)};X(Y());const J=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ee=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,J())},ne=()=>{w.paused=!w.paused,J()},oe=[];let le;let ae;if("play"===o?oe.push(setInterval((()=>{$()}),1e3)):"replay"===o&&J(),"play"===o)sn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Qt:{const n=ie.decodePlayer(a);n.id!==t&&(Mo.setPlayer(e,n.id,n),Wo=!0)}break;case Yt:{const t=ie.decodePiece(a);Mo.setPiece(e,t.idx,t),Wo=!0}break;case Ht:Mo.setPuzzleData(e,a),Wo=!0}L=!!Mo.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===It){const t=o[1];return Mo.addPlayer(e,t,n),!0}if(o[0]===Dt){const t=Mo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Mo.addPlayer(e,t,n),!0}if(o[0]===Et){const t=Mo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Mo.handleInput(e,t,l,n),!0}return!1},n=async()=>{w.logPointer+1>=w.log.length&&await b(e);const o=O();if(w.paused)return w.lastRealTs=o,void(le=setTimeout(n,50));const l=(o-w.lastRealTs)*w.speeds[w.speedIdx];let a=w.lastGameTs+l;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const n=w.log[w.logPointer],o=w.gameStartTs+n[n.length-1],l=w.log[e],i=w.gameStartTs+l[l.length-1];if(i>a){if(w.skipNonActionPhases&&a+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,i=window.requestAnimationFrame,s=1/n,r=o*s;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(s);a(c/o),u=d,t||i(p)};return i(p),{stop:()=>{t=!0}}})({update:()=>{R.createKeyEvents();for(const n of R.consumeAll())if("play"===o){const o=n[0];if(o===_t){const e=n[1],t=n[2],o=U.worldDimToViewport({w:e,h:t});Wo=!0,U.move(o.w,o.h)}else if(o===Ot){if(se&&!Mo.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-se.x),l=Math.round(t.y-se.y);Wo=!0,U.move(o,l),se=t}}else if(o===Vt)X(n[1]);else if(o===Mt){const e={x:n[1],y:n[2]};se=U.worldToViewport(e),K(!0)}else if(o===Nt)se=null,K(!1);else if(o===Bt){const e={x:n[1],y:n[2]};Wo=!0,U.zoom("in",U.worldToViewport(e))}else if(o===Ut){const e={x:n[1],y:n[2]};Wo=!0,U.zoom("out",U.worldToViewport(e))}else o===Gt?a.togglePreview():o===Ft&&a.toggleSoundsEnabled();const l=k();Mo.handleInput(e,t,n,l,(e=>{W()&&s.play()})).length>0&&(Wo=!0),sn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===Lt)ne();else if(e===Wt)te();else if(e===jt)ee();else if(e===_t){const e=n[1],t=n[2];Wo=!0,U.move(e,t)}else if(e===Ot){if(se){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-se.x),l=Math.round(t.y-se.y);Wo=!0,U.move(o,l),se=t}}else if(e===Mt){const e={x:n[1],y:n[2]};se=U.worldToViewport(e)}else if(e===Nt)se=null;else if(e===Bt){const e={x:n[1],y:n[2]};Wo=!0,U.zoom("in",U.worldToViewport(e))}else if(e===Ut){const e={x:n[1],y:n[2]};Wo=!0,U.zoom("out",U.worldToViewport(e))}else e===Gt&&a.togglePreview()}L=!!Mo.getFinishTs(e),j()&&(N.update(),Wo=!0)},render:async()=>{if(!Wo)return;const n=k();let l,i,s;window.DEBUG&&$n(0),B.fillStyle=H(),B.fillRect(0,0,v.width,v.height),window.DEBUG&&Gn("clear done"),l=U.worldToViewportRaw(I),i=U.worldDimToViewportRaw(D),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(l.x,l.y,i.w,i.h),window.DEBUG&&Gn("board done");const r=Mo.getPiecesSortedByZIndex(e);window.DEBUG&&Gn("get tiles done"),i=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?Lo:jo)&&(s=_[e.idx],l=U.worldToViewportRaw({x:C+e.pos.x,y:C+e.pos.y}),B.drawImage(s,0,0,s.width,s.height,l.x,l.y,i.w,i.h));window.DEBUG&&Gn("tiles done");const d=[];for(const a of Mo.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(s=await f(a),l=U.worldToViewport(a),B.drawImage(s,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,o]of d)B.fillText(e,t,o);window.DEBUG&&Gn("players done"),a.setActivePlayers(Mo.getActivePlayers(e,n)),a.setIdlePlayers(Mo.getIdlePlayers(e,n)),a.setPiecesDone(Mo.getFinishedPiecesCount(e)),window.DEBUG&&Gn("HUD done"),j()&&N.render(),Wo=!1}}),{setHotkeys:e=>{R.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),R.addEvent([Rt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),R.addEvent([Vt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),R.addEvent([$t,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},replayOnSpeedUp:ee,replayOnSpeedDown:te,replayOnPauseToggle:ne,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:V,player:{background:H(),color:Y(),name:Mo.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:W()},disconnect:sn.disconnect,connect:x,unload:()=>{oe.forEach((e=>{clearInterval(e)})),le&&clearTimeout(le),ae&&ae.stop()}}}var Yo=e({name:"game",components:{PuzzleStatus:yt,Scores:ut,SettingsOverlay:vt,PreviewOverlay:Ct,ConnectionOverlay:rn,HelpOverlay:hn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Ho(`${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)},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 Qo={id:"game"},qo={class:"menu"},Zo={class:"tabs"},Ko=s("🧩 Puzzles");Yo.render=function(e,l,s,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Qo,[p(n(u,{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"]),[[x,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",qo,[n("div",Zo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Ko])),_: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 Xo=e({name:"replay",components:{PuzzleStatus:yt,Scores:ut,SettingsOverlay:vt,PreviewOverlay:Ct,HelpOverlay:hn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Ho(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 Jo={id:"replay"},el=s("Skip no action phases: "),tl={class:"menu"},nl={class:"tabs"},ol=s("🧩 Puzzles");Xo.render=function(e,l,s,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Jo,[p(n(g,{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"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[el,p(n("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[k,e.skipNoAction]])])]),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",tl,[n("div",nl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[ol])),_:1}),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:l[12]||(l[12]=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=C({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:tt},{name:"game",path:"/g/:id",component:Yo},{name:"replay",path:"/replay/:id",component:Xo}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=S(T);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=ie.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 0c8ec16..8390c33 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,7 +4,7 @@ 🧩 jigsaw.hyottoko.club - + diff --git a/build/server/main.js b/build/server/main.js index 0f3b103..4300097 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -1173,6 +1173,12 @@ function handleInput$1(gameId, playerId, input, ts, onSnap) { changePlayer(gameId, playerId, { d, ts }); _playerChange(); } + if (snapped && getSnapMode(gameId) === SnapMode.REAL) { + if (getFinishedPiecesCount(gameId) === getPieceCount(gameId)) { + changeData(gameId, { finished: ts }); + _dataChange(); + } + } if (snapped && onSnap) { onSnap(playerId); } @@ -1309,7 +1315,7 @@ const get = (gameId, offset = 0) => { return []; } const log = fs.readFileSync(file, 'utf-8').split("\n"); - return log.map(line => { + return log.filter(line => !!line).map(line => { return JSON.parse(line); }); }; diff --git a/src/common/GameCommon.ts b/src/common/GameCommon.ts index f1aee84..c2793aa 100644 --- a/src/common/GameCommon.ts +++ b/src/common/GameCommon.ts @@ -848,6 +848,13 @@ function handleInput( changePlayer(gameId, playerId, { d, ts }) _playerChange() } + + if (snapped && getSnapMode(gameId) === SnapMode.REAL) { + if (getFinishedPiecesCount(gameId) === getPieceCount(gameId)) { + changeData(gameId, { finished: ts }) + _dataChange() + } + } if (snapped && onSnap) { onSnap(playerId) } diff --git a/src/server/GameLog.ts b/src/server/GameLog.ts index 0c016ad..0259a5a 100644 --- a/src/server/GameLog.ts +++ b/src/server/GameLog.ts @@ -78,7 +78,7 @@ const get = ( } const log = fs.readFileSync(file, 'utf-8').split("\n") - return log.map(line => { + return log.filter(line => !!line).map(line => { return JSON.parse(line) }) } From 60ae6e8a0829e96bceaa5405f87186529c184363 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 5 Jun 2021 23:02:04 +0200 Subject: [PATCH 05/45] smaller logs --- .../{index.87048674.js => index.b2021c0c.js} | 2 +- build/public/index.html | 2 +- build/server/main.js | 21 ++-- scripts/rewrite_logs.ts | 82 ------------ scripts/split_logs.ts | 117 +++++++++--------- src/frontend/game.ts | 11 +- src/server/Game.ts | 8 +- src/server/GameLog.ts | 18 ++- 8 files changed, 97 insertions(+), 164 deletions(-) rename build/public/assets/{index.87048674.js => index.b2021c0c.js} (79%) delete mode 100644 scripts/rewrite_logs.ts diff --git a/build/public/assets/index.87048674.js b/build/public/assets/index.b2021c0c.js similarity index 79% rename from build/public/assets/index.87048674.js rename to build/public/assets/index.b2021c0c.js index a83fb53..51ea48e 100644 --- a/build/public/assets/index.87048674.js +++ b/build/public/assets/index.b2021c0c.js @@ -1 +1 @@ -import{d as e,c as t,a as n,w as o,b as l,r as a,o as i,e as s,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as k,s as x,u as C,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const P={id:"app"},z={key:0,class:"nav"},I=s("Index"),D=s("New game");T.render=function(e,s,r,d,c,u){const p=a("router-link"),g=a("router-view");return i(),t("div",P,[e.showNav?(i(),t("ul",z,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[D])),_:1})])])):l("",!0),n(g)])};const E=864e5,_=e=>{const t=Math.floor(e/E);e%=E;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 M=1,N=1e3,O=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>_(t-e),U=_,R=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||O();return`${n} ${B(o,l)}`}}});const V={class:"game-info-text"},$=n("br",null,null,-1),G=n("br",null,null,-1),F=n("br",null,null,-1),L=s(" ↪️ Watch replay ");R.render=function(e,d,c,u,p,g){const h=a("router-link");return i(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",V,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),$,s(" 👥 "+r(e.game.players),1),G,s(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(i(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[L])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:R},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 W=n("h1",null,"Running games",-1),H=n("h1",null,"Finished games",-1);j.render=function(e,o,l,s,r,u){const p=a("game-teaser");return i(),t("div",null,[W,(i(!0),t(d,null,c(e.gamesRunning,((e,o)=>(i(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128)),H,(i(!0),t(d,null,c(e.gamesFinished,((e,o)=>(i(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128))])};var Y=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});Y.render=function(e,o,l,a,s,r){return i(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Q,q,Z,K,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:Y},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,s,r){const u=a("image-teaser");return i(),t("div",null,[(i(!0),t(d,null,c(e.images,((n,o)=>(i(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(q=Q||(Q={}))[q.Flat=0]="Flat",q[q.Out=1]="Out",q[q.In=-1]="In",(K=Z||(Z={}))[K.FINAL=0]="FINAL",K[K.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),i=le(o.getSeconds(),"00");console[t](`${l}:${a}:${i}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var ie={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Z.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const se={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};se.render=function(e,n,o,l,a,s){return i(),t("div",{style:s.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.key&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-39ed99c7");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,s,u,m)=>(i(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(i(),t("div",ce,[n("ul",null,[(i(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(i(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(i(!0),t(d,null,c(e.values,((n,o)=>(i(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-39ed99c7";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he={key:0,class:"has-image"},me={key:1},ye={class:"upload"},fe=n("span",{class:"btn"},"Upload File",-1),ve={class:"area-settings"},we=n("td",null,[n("label",null,"Title")],-1),be=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ke=n("td",null,[n("label",null,"Tags")],-1),xe={class:"area-buttons"},Ce=s("🧩 Post to gallery "),Ae=n("br",null,null,-1),Se=s(" + set up game");ge.render=function(e,o,l,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[e.previewUrl?(i(),t("div",he,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(i(),t("div",me,[n("label",ye,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),fe])]))],34),n("div",ve,[n("table",null,[n("tr",null,[we,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),be,n("tr",null,[ke,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",xe,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ce,Ae,Se],8,["disabled"])])])])};var Te=e({name:"edit-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Pe={class:"area-image"},ze={class:"has-image"},Ie={class:"area-settings"},De=n("td",null,[n("label",null,"Title")],-1),Ee=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),_e=n("td",null,[n("label",null,"Tags")],-1),Me={class:"area-buttons"};Te.render=function(e,o,l,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Pe,[n("div",ze,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ie,[n("table",null,[n("tr",null,[De,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ee,n("tr",null,[_e,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Me,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Ne=e({name:"new-game-dialog",components:{ResponsiveImage:se},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Z.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Oe={class:"area-image"},Be={class:"has-image"},Ue={key:0,class:"image-title"},Re={class:"area-settings"},Ve=n("td",null,[n("label",null,"Pieces")],-1),$e=n("td",null,[n("label",null,"Scoring: ")],-1),Ge=s(" Any (Score when pieces are connected to each other or on final location)"),Fe=n("br",null,null,-1),Le=s(" Final (Score when pieces are put to their final location)"),je=n("td",null,[n("label",null,"Shapes: ")],-1),We=s(" Normal"),He=n("br",null,null,-1),Ye=s(" Any (flat pieces can occur anywhere)"),Qe=n("br",null,null,-1),qe=s(" Flat (all pieces flat on all sides)"),Ze=n("td",null,[n("label",null,"Snapping: ")],-1),Ke=s(" Normal (pieces snap to final destination and to each other)"),Xe=n("br",null,null,-1),Je=s(" Real (pieces snap only to corners, already snapped pieces and to each other)"),et={class:"area-buttons"};Ne.render=function(e,o,s,d,c,h){const m=a("responsive-image");return i(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Oe,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(i(),t("div",Ue,'"'+r(e.image.title)+'"',1)):l("",!0)]),n("div",Re,[n("table",null,[n("tr",null,[Ve,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[$e,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Ge]),Fe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),Le])])]),n("tr",null,[je,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),We]),He,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ye]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),qe])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Ke]),Xe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),Je])])])])]),n("div",et,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var tt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Te,NewGameDialog:Ne},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${ie.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const nt={class:"upload-image-teaser"},ot=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),lt={key:0},at=s(" Tags: "),it=s(" Sort by: "),st=n("option",{value:"date_desc"},"Newest first",-1),rt=n("option",{value:"date_asc"},"Oldest first",-1),dt=n("option",{value:"alpha_asc"},"A-Z",-1),ct=n("option",{value:"alpha_desc"},"Z-A",-1);tt.render=function(e,o,s,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return i(),t("div",null,[n("div",nt,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),ot]),n("div",null,[e.tags.length>0?(i(),t("label",lt,[at,(i(!0),t(d,null,c(e.relevantTags,((n,o)=>(i(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[it,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[st,rt,dt,ct],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(i(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(i(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(i(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ut=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 pt={class:"scores"},gt=n("div",null,"Scores",-1),ht=n("td",null,"⚡",-1),mt=n("td",null,"💤",-1);ut.render=function(e,o,l,a,s,u){return i(),t("div",pt,[gt,n("table",null,[(i(!0),t(d,null,c(e.actives,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[ht,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(i(!0),t(d,null,c(e.idles,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[mt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var yt=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 U(this.duration)}}});const ft={class:"timer"};yt.render=function(e,o,l,a,s,d){return i(),t("div",ft,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var vt=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 wt=n("td",null,[n("label",null,"Background: ")],-1),bt=n("td",null,[n("label",null,"Color: ")],-1),kt=n("td",null,[n("label",null,"Name: ")],-1),xt=n("td",null,[n("label",null,"Sounds: ")],-1);vt.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:o[6]||(o[6]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[5]||(o[5]=u((()=>{}),["stop"]))},[n("tr",null,[wt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[bt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[kt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[xt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[k,e.modelValue.soundsEnabled]])])])])])};var Ct=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const At={class:"preview"};Ct.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",At,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var St=1,Tt=4,Pt=2,zt=3,It=2,Dt=4,Et=3,_t=9,Mt=1,Nt=2,Ot=3,Bt=4,Ut=5,Rt=6,Vt=7,$t=8,Gt=10,Ft=11,Lt=12,jt=13,Wt=14,Ht=1,Yt=2,Qt=3;const qt=ae("Communication.js");let Zt,Kt=[],Xt=e=>{Kt.push(e)},Jt=[],en=e=>{Jt.push(e)};let tn=0;const nn=e=>{tn!==e&&(tn=e,en(e))};function on(e){if(2===tn)try{Zt.send(JSON.stringify(e))}catch(t){qt.info("unable to send message.. maybe because ws is invalid?")}}let ln,an;var sn={connect:function(e,t,n){return ln=0,an={},nn(3),new Promise((o=>{Zt=new WebSocket(e,n+"|"+t),Zt.onopen=()=>{nn(2),on([zt])},Zt.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Tt){const e=t[1];o(e)}else{if(l!==St)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&an[o])return void delete an[o];Xt(t)}}},Zt.onerror=()=>{throw nn(1),"[ 2021-05-15 onerror ]"},Zt.onclose=e=>{4e3===e.code||1001===e.code?nn(4):nn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${ie.asQueryArgs(n)}`);return await o.json()},disconnect:function(){Zt&&Zt.close(4e3),ln=0,an={}},sendClientEvent:function(e){ln++,an[ln]=e,on([Pt,ln,an[ln]])},onServerChange:function(e){Xt=e;for(const t of Kt)Xt(t);Kt=[]},onConnectionStateChange:function(e){en=e;for(const t of Jt)en(t);Jt=[]},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},rn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===sn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===sn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const dn={key:0,class:"overlay connection-lost"},cn={key:0,class:"overlay-content"},un=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),pn={key:1,class:"overlay-content"},gn=n("div",null,"Connecting...",-1);rn.render=function(e,o,a,s,r,d){return e.show?(i(),t("div",dn,[e.lostConnection?(i(),t("div",cn,[un,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(i(),t("div",pn,[gn])):l("",!0)])):l("",!0)};var hn=e({name:"help-overlay",emits:{bgclick:null}});const mn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),yn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),fn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),vn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),wn=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),bn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),kn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),xn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Cn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),An=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Sn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),Tn=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),Pn=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),zn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);hn.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:o[2]||(o[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:o[1]||(o[1]=u((()=>{}),["stop"]))},[mn,yn,fn,vn,wn,bn,kn,xn,Cn,An,Sn,Tn,Pn,zn])])};var In=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Dn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),En=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),_n=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Mn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Nn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),i=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function On(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Bn={createCanvas:On,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=On(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=On(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(),o}};const Un=ae("Debug.js");let Rn=0,Vn=0;var $n=e=>{Rn=performance.now(),Vn=e},Gn=e=>{const t=performance.now(),n=t-Rn;n>Vn&&Un.log(e+": "+n),Rn=t};function Fn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function Ln(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var jn={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:Fn,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:Ln,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 Fn(Ln(e),Ln(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const Wn=ae("PuzzleGraphics.js");function Hn(e,t){const n=ie.coordByPieceIdx(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 Bn.loadImageToBitmap(e.info.imageUrl),n=await Bn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Wn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,i=o/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=jn.pointAdd(a,{x:o,y:0}),c=jn.pointAdd(r,{x:0,y:o}),u=jn.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;oie.decodePiece(Qn[e].puzzle.tiles[t]),co=(e,t)=>ro(e,t).group,uo=(e,t)=>{const n=Qn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},po=(e,t)=>{const n=Qn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Qn[e].puzzle.info,o=ie.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return jn.pointAdd(o,l)},go=(e,t)=>ro(e,t).pos,ho=e=>{const t=Eo(e),n=_o(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}},mo=(e,t)=>{const n=wo(e),o=ro(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},yo=(e,t)=>ro(e,t).z,fo=(e,t)=>{for(const n of Qn[e].puzzle.tiles){const e=ie.decodePiece(n);if(e.owner===t)return e.idx}return-1},vo=e=>Qn[e].puzzle.info.tileDrawSize,wo=e=>Qn[e].puzzle.info.tileSize,bo=e=>Qn[e].puzzle.data.maxGroup,ko=e=>Qn[e].puzzle.data.maxZ;function xo(e,t){const n=Qn[e].puzzle.info,o=ie.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Co=(e,t,n)=>{for(const o of t)so(e,o,{z:n})},Ao=(e,t,n)=>{const o=go(e,t);so(e,t,{pos:jn.pointAdd(o,n)})},So=(e,t,n)=>{const o=vo(e),l=ho(e),a=n;for(const i of t){const t=ro(e,i);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const i of t)Ao(e,i,a)},To=(e,t)=>ro(e,t).owner,Po=(e,t)=>{for(const n of t)so(e,n,{owner:-1,z:1})},zo=(e,t,n)=>{for(const o of t)so(e,o,{owner:n})};function Io(e,t){const n=Qn[e].puzzle.tiles,o=ie.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=ie.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const Do=(e,t)=>{const n=Zn(e,t);return n?n.points:0},Eo=e=>Qn[e].puzzle.info.table.width,_o=e=>Qn[e].puzzle.info.table.height;var Mo={setGame:function(e,t){Qn[e]=t},exists:function(e){return!!Qn[e]||!1},playerExists:Xn,getActivePlayers:function(e,t){const n=t-30*N;return Jn(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*N;return Jn(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Xn(e,t)?ao(e,t,{ts:n}):Kn(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:lo,getPieceCount:eo,getImageUrl:function(e){return Qn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Qn[e].puzzle.info.imageUrl=t},get:function(e){return Qn[e]||null},getAllGames:function(){return Object.values(Qn).sort(((e,t)=>oo(e.id)===oo(t.id)?t.puzzle.data.started-e.puzzle.data.started:oo(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Zn(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Zn(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Zn(e,t);return n?n.name:null},getPlayerIndexById:qn,getPlayerIdByIndex:function(e,t){return Qn[e].players.length>t?ie.decodePlayer(Qn[e].players[t]).id:null},changePlayer:ao,setPlayer:Kn,setPiece:function(e,t,n){Qn[e].puzzle.tiles[t]=ie.encodePiece(n)},setPuzzleData:function(e,t){Qn[e].puzzle.data=t},getTableWidth:Eo,getTableHeight:_o,getPuzzle:e=>Qn[e].puzzle,getRng:e=>Qn[e].rng.obj,getPuzzleWidth:e=>Qn[e].puzzle.info.width,getPuzzleHeight:e=>Qn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Qn[e].puzzle.tiles.map(ie.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=fo(e,t);return n<0?null:Qn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Qn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:vo,getFinalPiecePos:po,getStartTs:e=>Qn[e].puzzle.data.started,getFinishTs:e=>Qn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Qn[e].puzzle,i=function(e,t){return t in Qn[e].evtInfos?Qn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],r=()=>{s.push([Ht,a.data])},d=t=>{s.push([Yt,ie.encodePiece(ro(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=Zn(e,t);n&&s.push([Qt,ie.encodePlayer(n)])},p=n[0];if(p===Rt){const l=n[1];ao(e,t,{bgcolor:l,ts:o}),u()}else if(p===Vt){const l=n[1];ao(e,t,{color:l,ts:o}),u()}else if(p===$t){const l=`${n[1]}`.substr(0,16);ao(e,t,{name:l,ts:o}),u()}else if(p===_t){const l=n[1],a=n[2],i=Zn(e,t);if(i){const n=i.x-l,s=i.y-a;ao(e,t,{ts:o,x:n,y:s}),u()}}else if(p===Mt){const l={x:n[1],y:n[2]};ao(e,t,{d:1,ts:o}),u(),i._last_mouse_down=l;const a=((e,t)=>{const n=Qn[e].puzzle.info,o=Qn[e].puzzle.tiles;let l=-1,a=-1;for(let i=0;il)&&(l=e.z,a=i)}return a})(e,l);if(a>=0){const n=ko(e)+1;io(e,{maxZ:n}),r();const o=Io(e,a);Co(e,o,ko(e)),zo(e,o,t),c(o)}i._last_mouse=l}else if(p===Ot){const l=n[1],a=n[2],s={x:l,y:a};if(null===i._last_mouse_down)ao(e,t,{x:l,y:a,ts:o}),u();else{const n=fo(e,t);if(n>=0){ao(e,t,{x:l,y:a,ts:o}),u();const r=Io(e,n);let d=jn.pointInBounds(s,ho(e))&&jn.pointInBounds(i._last_mouse_down,ho(e));for(const t of r){const n=mo(e,t);if(jn.pointInBounds(s,n)){d=!0;break}}if(d){const t=l-i._last_mouse_down.x,n=a-i._last_mouse_down.y;So(e,r,{x:t,y:n}),c(r)}}else ao(e,t,{ts:o}),u();i._last_mouse_down=s}i._last_mouse=s}else if(p===Nt){const s={x:n[1],y:n[2]},p=0;i._last_mouse_down=null;const g=fo(e,t);if(g>=0){const n=Io(e,g);zo(e,n,0),c(n);const i=go(e,g),s=po(e,g);let h=!1;if(no(e)===ee.REAL){for(const t of n)if(uo(e,t)){h=!0;break}}else h=!0;if(h&&jn.pointDistance(s,i){const l=Qn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=co(e,t),l=co(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=go(e,t),i=jn.pointAdd(go(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(jn.pointDistance(a,i){const o=Qn[e].puzzle.tiles,l=co(e,t),a=co(e,n);let i;const s=[];l&&s.push(l),a&&s.push(a),l?i=l:a?i=a:(io(e,{maxGroup:bo(e)+1}),r(),i=bo(e));if(so(e,t,{group:i}),d(t),so(e,n,{group:i}),d(n),s.length>0)for(const r of o){const t=ie.decodePiece(r);s.includes(t.group)&&(so(e,t.idx,{group:i}),d(t.idx))}})(e,t,n),l=Io(e,t),((e,t)=>-1===To(e,t))(e,n))Po(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=yo(e,o);t>n&&(n=t)}return n})(e,l);Co(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Io(e,g)){const o=xo(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])){a=!0;break}}if(a&&to(e)===Z.ANY){const n=Do(e,t)+1;ao(e,t,{d:p,ts:o,points:n}),u()}else ao(e,t,{d:p,ts:o}),u();a&&no(e)===ee.REAL&&lo(e)===eo(e)&&(io(e,{finished:o}),r()),a&&l&&l(t)}}else ao(e,t,{d:p,ts:o}),u();i._last_mouse=s}else if(p===Bt){const l=n[1],a=n[2];ao(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else if(p===Ut){const l=n[1],a=n[2];ao(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else ao(e,t,{ts:o}),u();return function(e,t,n){Qn[e].evtInfos[t]=n}(e,t,i),s}};let No=-10,Oo=20,Bo=2,Uo=15;class Ro{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=No+Math.random()*Oo,this.vy=-1*(Bo+Math.random()*Uo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Bo=t/2,Uo=t-Bo;const n=1/4*this.canvas.width/(t/2);No=-n,Oo=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Ro(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Ro(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(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Bn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Wo=!0})),t}(l,Bn.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};sn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await sn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let k=()=>0;const x=async()=>{if("play"===o){const o=await sn.connect(n,e,t),l=ie.decodeGame(o);Mo.setGame(l.id,l),k=()=>O()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=ie.decodeGame(t.game);Mo.setGame(n.id,n),w.lastRealTs=O(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,k=()=>w.lastGameTs}}Wo=!0};await x();const C=Mo.getPieceDrawOffset(e),A=Mo.getPieceDrawSize(e),S=Mo.getPuzzleWidth(e),T=Mo.getPuzzleHeight(e),P=Mo.getTableWidth(e),z=Mo.getTableHeight(e),I={x:(P-S)/2,y:(z-T)/2},D={w:S,h:T},E={w:A,h:A},_=await Yn.loadPuzzleBitmaps(Mo.getPuzzle(e)),N=new $o(v,Mo.getRng(e));N.init();const B=v.getContext("2d");v.classList.add("loaded");const U=Nn();U.move(-(P-v.width)/2,-(z-v.height)/2);const R=function(e,t,n,o){let l=[],a=!0,i=!1,s=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("Shift"===t.key?p=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?r=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?d=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?u=e:"e"===t.key&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Mt,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Nt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([Ot,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Bt:Ut;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&(" "===e.key&&v([Gt]),"replay"===o&&("I"!==e.key&&"i"!==e.key||v([jt]),"O"!==e.key&&"o"!==e.key||v([Wt]),"P"!==e.key&&"p"!==e.key||v([Lt])),"F"!==e.key&&"f"!==e.key||(Lo=!Lo,Wo=!0),"G"!==e.key&&"g"!==e.key||(jo=!jo,Wo=!0),"M"!==e.key&&"m"!==e.key||v([Ft]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(i?1:0)-(s?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});v([_t,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Bt,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([Ut,...e])}},setHotkeys:e=>{a=e}}}(v,window,U,o),V=Mo.getImageUrl(e),$=()=>{const t=Mo.getStartTs(e),n=Mo.getFinishTs(e),o=k();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(Mo.getFinishedPiecesCount(e)),a.setPiecesTotal(Mo.getPieceCount(e));const G=k();a.setActivePlayers(Mo.getActivePlayers(e,G)),a.setIdlePlayers(Mo.getIdlePlayers(e,G));const F=!!Mo.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},H=()=>Mo.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Y=()=>Mo.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let Q="",q="",Z=!1;const K=e=>{Z=e;const[t,n]=e?[Q,"grab"]:[q,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},X=e=>{Q=Bn.colorizedCanvas(r,c,e).toDataURL(),q=Bn.colorizedCanvas(d,u,e).toDataURL(),K(Z)};X(Y());const J=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ee=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,J())},ne=()=>{w.paused=!w.paused,J()},oe=[];let le;let ae;if("play"===o?oe.push(setInterval((()=>{$()}),1e3)):"replay"===o&&J(),"play"===o)sn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Qt:{const n=ie.decodePlayer(a);n.id!==t&&(Mo.setPlayer(e,n.id,n),Wo=!0)}break;case Yt:{const t=ie.decodePiece(a);Mo.setPiece(e,t.idx,t),Wo=!0}break;case Ht:Mo.setPuzzleData(e,a),Wo=!0}L=!!Mo.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===It){const t=o[1];return Mo.addPlayer(e,t,n),!0}if(o[0]===Dt){const t=Mo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Mo.addPlayer(e,t,n),!0}if(o[0]===Et){const t=Mo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Mo.handleInput(e,t,l,n),!0}return!1},n=async()=>{w.logPointer+1>=w.log.length&&await b(e);const o=O();if(w.paused)return w.lastRealTs=o,void(le=setTimeout(n,50));const l=(o-w.lastRealTs)*w.speeds[w.speedIdx];let a=w.lastGameTs+l;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const n=w.log[w.logPointer],o=w.gameStartTs+n[n.length-1],l=w.log[e],i=w.gameStartTs+l[l.length-1];if(i>a){if(w.skipNonActionPhases&&a+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,i=window.requestAnimationFrame,s=1/n,r=o*s;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(s);a(c/o),u=d,t||i(p)};return i(p),{stop:()=>{t=!0}}})({update:()=>{R.createKeyEvents();for(const n of R.consumeAll())if("play"===o){const o=n[0];if(o===_t){const e=n[1],t=n[2],o=U.worldDimToViewport({w:e,h:t});Wo=!0,U.move(o.w,o.h)}else if(o===Ot){if(se&&!Mo.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-se.x),l=Math.round(t.y-se.y);Wo=!0,U.move(o,l),se=t}}else if(o===Vt)X(n[1]);else if(o===Mt){const e={x:n[1],y:n[2]};se=U.worldToViewport(e),K(!0)}else if(o===Nt)se=null,K(!1);else if(o===Bt){const e={x:n[1],y:n[2]};Wo=!0,U.zoom("in",U.worldToViewport(e))}else if(o===Ut){const e={x:n[1],y:n[2]};Wo=!0,U.zoom("out",U.worldToViewport(e))}else o===Gt?a.togglePreview():o===Ft&&a.toggleSoundsEnabled();const l=k();Mo.handleInput(e,t,n,l,(e=>{W()&&s.play()})).length>0&&(Wo=!0),sn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===Lt)ne();else if(e===Wt)te();else if(e===jt)ee();else if(e===_t){const e=n[1],t=n[2];Wo=!0,U.move(e,t)}else if(e===Ot){if(se){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-se.x),l=Math.round(t.y-se.y);Wo=!0,U.move(o,l),se=t}}else if(e===Mt){const e={x:n[1],y:n[2]};se=U.worldToViewport(e)}else if(e===Nt)se=null;else if(e===Bt){const e={x:n[1],y:n[2]};Wo=!0,U.zoom("in",U.worldToViewport(e))}else if(e===Ut){const e={x:n[1],y:n[2]};Wo=!0,U.zoom("out",U.worldToViewport(e))}else e===Gt&&a.togglePreview()}L=!!Mo.getFinishTs(e),j()&&(N.update(),Wo=!0)},render:async()=>{if(!Wo)return;const n=k();let l,i,s;window.DEBUG&&$n(0),B.fillStyle=H(),B.fillRect(0,0,v.width,v.height),window.DEBUG&&Gn("clear done"),l=U.worldToViewportRaw(I),i=U.worldDimToViewportRaw(D),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(l.x,l.y,i.w,i.h),window.DEBUG&&Gn("board done");const r=Mo.getPiecesSortedByZIndex(e);window.DEBUG&&Gn("get tiles done"),i=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?Lo:jo)&&(s=_[e.idx],l=U.worldToViewportRaw({x:C+e.pos.x,y:C+e.pos.y}),B.drawImage(s,0,0,s.width,s.height,l.x,l.y,i.w,i.h));window.DEBUG&&Gn("tiles done");const d=[];for(const a of Mo.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(s=await f(a),l=U.worldToViewport(a),B.drawImage(s,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,o]of d)B.fillText(e,t,o);window.DEBUG&&Gn("players done"),a.setActivePlayers(Mo.getActivePlayers(e,n)),a.setIdlePlayers(Mo.getIdlePlayers(e,n)),a.setPiecesDone(Mo.getFinishedPiecesCount(e)),window.DEBUG&&Gn("HUD done"),j()&&N.render(),Wo=!1}}),{setHotkeys:e=>{R.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),R.addEvent([Rt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),R.addEvent([Vt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),R.addEvent([$t,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},replayOnSpeedUp:ee,replayOnSpeedDown:te,replayOnPauseToggle:ne,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:V,player:{background:H(),color:Y(),name:Mo.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:W()},disconnect:sn.disconnect,connect:x,unload:()=>{oe.forEach((e=>{clearInterval(e)})),le&&clearTimeout(le),ae&&ae.stop()}}}var Yo=e({name:"game",components:{PuzzleStatus:yt,Scores:ut,SettingsOverlay:vt,PreviewOverlay:Ct,ConnectionOverlay:rn,HelpOverlay:hn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Ho(`${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)},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 Qo={id:"game"},qo={class:"menu"},Zo={class:"tabs"},Ko=s("🧩 Puzzles");Yo.render=function(e,l,s,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Qo,[p(n(u,{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"]),[[x,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",qo,[n("div",Zo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Ko])),_: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 Xo=e({name:"replay",components:{PuzzleStatus:yt,Scores:ut,SettingsOverlay:vt,PreviewOverlay:Ct,HelpOverlay:hn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Ho(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 Jo={id:"replay"},el=s("Skip no action phases: "),tl={class:"menu"},nl={class:"tabs"},ol=s("🧩 Puzzles");Xo.render=function(e,l,s,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Jo,[p(n(g,{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"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[el,p(n("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[k,e.skipNoAction]])])]),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",tl,[n("div",nl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[ol])),_:1}),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:l[12]||(l[12]=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=C({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:tt},{name:"game",path:"/g/:id",component:Yo},{name:"replay",path:"/replay/:id",component:Xo}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=S(T);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=ie.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); +import{d as e,c as t,a as n,w as o,b as l,r as a,o as i,e as s,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as k,s as x,u as C,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const P={id:"app"},z={key:0,class:"nav"},I=s("Index"),D=s("New game");T.render=function(e,s,r,d,c,u){const p=a("router-link"),g=a("router-view");return i(),t("div",P,[e.showNav?(i(),t("ul",z,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[D])),_:1})])])):l("",!0),n(g)])};const E=864e5,_=e=>{const t=Math.floor(e/E);e%=E;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 M=1,N=1e3,O=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>_(t-e),U=_,R=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||O();return`${n} ${B(o,l)}`}}});const V={class:"game-info-text"},G=n("br",null,null,-1),$=n("br",null,null,-1),F=n("br",null,null,-1),L=s(" ↪️ Watch replay ");R.render=function(e,d,c,u,p,g){const h=a("router-link");return i(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",V,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),G,s(" 👥 "+r(e.game.players),1),$,s(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(i(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[L])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:R},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 W=n("h1",null,"Running games",-1),H=n("h1",null,"Finished games",-1);j.render=function(e,o,l,s,r,u){const p=a("game-teaser");return i(),t("div",null,[W,(i(!0),t(d,null,c(e.gamesRunning,((e,o)=>(i(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128)),H,(i(!0),t(d,null,c(e.gamesFinished,((e,o)=>(i(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128))])};var Y=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});Y.render=function(e,o,l,a,s,r){return i(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Q,q,Z,K,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:Y},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,s,r){const u=a("image-teaser");return i(),t("div",null,[(i(!0),t(d,null,c(e.images,((n,o)=>(i(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(q=Q||(Q={}))[q.Flat=0]="Flat",q[q.Out=1]="Out",q[q.In=-1]="In",(K=Z||(Z={}))[K.FINAL=0]="FINAL",K[K.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),i=le(o.getSeconds(),"00");console[t](`${l}:${a}:${i}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var ie={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Z.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const se={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};se.render=function(e,n,o,l,a,s){return i(),t("div",{style:s.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.key&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-39ed99c7");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,s,u,m)=>(i(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(i(),t("div",ce,[n("ul",null,[(i(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(i(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(i(!0),t(d,null,c(e.values,((n,o)=>(i(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-39ed99c7";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he={key:0,class:"has-image"},me={key:1},ye={class:"upload"},fe=n("span",{class:"btn"},"Upload File",-1),ve={class:"area-settings"},we=n("td",null,[n("label",null,"Title")],-1),be=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ke=n("td",null,[n("label",null,"Tags")],-1),xe={class:"area-buttons"},Ce=s("🧩 Post to gallery "),Ae=n("br",null,null,-1),Se=s(" + set up game");ge.render=function(e,o,l,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[e.previewUrl?(i(),t("div",he,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(i(),t("div",me,[n("label",ye,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),fe])]))],34),n("div",ve,[n("table",null,[n("tr",null,[we,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),be,n("tr",null,[ke,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",xe,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ce,Ae,Se],8,["disabled"])])])])};var Te=e({name:"edit-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Pe={class:"area-image"},ze={class:"has-image"},Ie={class:"area-settings"},De=n("td",null,[n("label",null,"Title")],-1),Ee=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),_e=n("td",null,[n("label",null,"Tags")],-1),Me={class:"area-buttons"};Te.render=function(e,o,l,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Pe,[n("div",ze,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ie,[n("table",null,[n("tr",null,[De,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ee,n("tr",null,[_e,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Me,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Ne=e({name:"new-game-dialog",components:{ResponsiveImage:se},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Z.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Oe={class:"area-image"},Be={class:"has-image"},Ue={key:0,class:"image-title"},Re={class:"area-settings"},Ve=n("td",null,[n("label",null,"Pieces")],-1),Ge=n("td",null,[n("label",null,"Scoring: ")],-1),$e=s(" Any (Score when pieces are connected to each other or on final location)"),Fe=n("br",null,null,-1),Le=s(" Final (Score when pieces are put to their final location)"),je=n("td",null,[n("label",null,"Shapes: ")],-1),We=s(" Normal"),He=n("br",null,null,-1),Ye=s(" Any (flat pieces can occur anywhere)"),Qe=n("br",null,null,-1),qe=s(" Flat (all pieces flat on all sides)"),Ze=n("td",null,[n("label",null,"Snapping: ")],-1),Ke=s(" Normal (pieces snap to final destination and to each other)"),Xe=n("br",null,null,-1),Je=s(" Real (pieces snap only to corners, already snapped pieces and to each other)"),et={class:"area-buttons"};Ne.render=function(e,o,s,d,c,h){const m=a("responsive-image");return i(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Oe,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(i(),t("div",Ue,'"'+r(e.image.title)+'"',1)):l("",!0)]),n("div",Re,[n("table",null,[n("tr",null,[Ve,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[Ge,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),$e]),Fe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),Le])])]),n("tr",null,[je,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),We]),He,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ye]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),qe])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Ke]),Xe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),Je])])])])]),n("div",et,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var tt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Te,NewGameDialog:Ne},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${ie.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const nt={class:"upload-image-teaser"},ot=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),lt={key:0},at=s(" Tags: "),it=s(" Sort by: "),st=n("option",{value:"date_desc"},"Newest first",-1),rt=n("option",{value:"date_asc"},"Oldest first",-1),dt=n("option",{value:"alpha_asc"},"A-Z",-1),ct=n("option",{value:"alpha_desc"},"Z-A",-1);tt.render=function(e,o,s,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return i(),t("div",null,[n("div",nt,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),ot]),n("div",null,[e.tags.length>0?(i(),t("label",lt,[at,(i(!0),t(d,null,c(e.relevantTags,((n,o)=>(i(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[it,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[st,rt,dt,ct],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(i(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(i(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(i(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ut=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 pt={class:"scores"},gt=n("div",null,"Scores",-1),ht=n("td",null,"⚡",-1),mt=n("td",null,"💤",-1);ut.render=function(e,o,l,a,s,u){return i(),t("div",pt,[gt,n("table",null,[(i(!0),t(d,null,c(e.actives,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[ht,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(i(!0),t(d,null,c(e.idles,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[mt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var yt=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 U(this.duration)}}});const ft={class:"timer"};yt.render=function(e,o,l,a,s,d){return i(),t("div",ft,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var vt=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 wt=n("td",null,[n("label",null,"Background: ")],-1),bt=n("td",null,[n("label",null,"Color: ")],-1),kt=n("td",null,[n("label",null,"Name: ")],-1),xt=n("td",null,[n("label",null,"Sounds: ")],-1);vt.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:o[6]||(o[6]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[5]||(o[5]=u((()=>{}),["stop"]))},[n("tr",null,[wt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[bt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[kt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[xt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[k,e.modelValue.soundsEnabled]])])])])])};var Ct=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const At={class:"preview"};Ct.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",At,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var St=1,Tt=4,Pt=2,zt=3,It=2,Dt=4,Et=3,_t=9,Mt=1,Nt=2,Ot=3,Bt=4,Ut=5,Rt=6,Vt=7,Gt=8,$t=10,Ft=11,Lt=12,jt=13,Wt=14,Ht=1,Yt=2,Qt=3;const qt=ae("Communication.js");let Zt,Kt=[],Xt=e=>{Kt.push(e)},Jt=[],en=e=>{Jt.push(e)};let tn=0;const nn=e=>{tn!==e&&(tn=e,en(e))};function on(e){if(2===tn)try{Zt.send(JSON.stringify(e))}catch(t){qt.info("unable to send message.. maybe because ws is invalid?")}}let ln,an;var sn={connect:function(e,t,n){return ln=0,an={},nn(3),new Promise((o=>{Zt=new WebSocket(e,n+"|"+t),Zt.onopen=()=>{nn(2),on([zt])},Zt.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Tt){const e=t[1];o(e)}else{if(l!==St)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&an[o])return void delete an[o];Xt(t)}}},Zt.onerror=()=>{throw nn(1),"[ 2021-05-15 onerror ]"},Zt.onclose=e=>{4e3===e.code||1001===e.code?nn(4):nn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${ie.asQueryArgs(n)}`);return await o.json()},disconnect:function(){Zt&&Zt.close(4e3),ln=0,an={}},sendClientEvent:function(e){ln++,an[ln]=e,on([Pt,ln,an[ln]])},onServerChange:function(e){Xt=e;for(const t of Kt)Xt(t);Kt=[]},onConnectionStateChange:function(e){en=e;for(const t of Jt)en(t);Jt=[]},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},rn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===sn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===sn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const dn={key:0,class:"overlay connection-lost"},cn={key:0,class:"overlay-content"},un=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),pn={key:1,class:"overlay-content"},gn=n("div",null,"Connecting...",-1);rn.render=function(e,o,a,s,r,d){return e.show?(i(),t("div",dn,[e.lostConnection?(i(),t("div",cn,[un,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(i(),t("div",pn,[gn])):l("",!0)])):l("",!0)};var hn=e({name:"help-overlay",emits:{bgclick:null}});const mn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),yn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),fn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),vn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),wn=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),bn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),kn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),xn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Cn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),An=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Sn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),Tn=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),Pn=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),zn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);hn.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:o[2]||(o[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:o[1]||(o[1]=u((()=>{}),["stop"]))},[mn,yn,fn,vn,wn,bn,kn,xn,Cn,An,Sn,Tn,Pn,zn])])};var In=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Dn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),En=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),_n=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Mn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Nn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),i=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function On(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Bn={createCanvas:On,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=On(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=On(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(),o}};const Un=ae("Debug.js");let Rn=0,Vn=0;var Gn=e=>{Rn=performance.now(),Vn=e},$n=e=>{const t=performance.now(),n=t-Rn;n>Vn&&Un.log(e+": "+n),Rn=t};function Fn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function Ln(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var jn={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:Fn,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:Ln,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 Fn(Ln(e),Ln(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const Wn=ae("PuzzleGraphics.js");function Hn(e,t){const n=ie.coordByPieceIdx(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 Bn.loadImageToBitmap(e.info.imageUrl),n=await Bn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Wn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,i=o/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=jn.pointAdd(a,{x:o,y:0}),c=jn.pointAdd(r,{x:0,y:o}),u=jn.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;oie.decodePiece(Qn[e].puzzle.tiles[t]),co=(e,t)=>ro(e,t).group,uo=(e,t)=>{const n=Qn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},po=(e,t)=>{const n=Qn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Qn[e].puzzle.info,o=ie.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return jn.pointAdd(o,l)},go=(e,t)=>ro(e,t).pos,ho=e=>{const t=Eo(e),n=_o(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}},mo=(e,t)=>{const n=wo(e),o=ro(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},yo=(e,t)=>ro(e,t).z,fo=(e,t)=>{for(const n of Qn[e].puzzle.tiles){const e=ie.decodePiece(n);if(e.owner===t)return e.idx}return-1},vo=e=>Qn[e].puzzle.info.tileDrawSize,wo=e=>Qn[e].puzzle.info.tileSize,bo=e=>Qn[e].puzzle.data.maxGroup,ko=e=>Qn[e].puzzle.data.maxZ;function xo(e,t){const n=Qn[e].puzzle.info,o=ie.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Co=(e,t,n)=>{for(const o of t)so(e,o,{z:n})},Ao=(e,t,n)=>{const o=go(e,t);so(e,t,{pos:jn.pointAdd(o,n)})},So=(e,t,n)=>{const o=vo(e),l=ho(e),a=n;for(const i of t){const t=ro(e,i);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const i of t)Ao(e,i,a)},To=(e,t)=>ro(e,t).owner,Po=(e,t)=>{for(const n of t)so(e,n,{owner:-1,z:1})},zo=(e,t,n)=>{for(const o of t)so(e,o,{owner:n})};function Io(e,t){const n=Qn[e].puzzle.tiles,o=ie.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=ie.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const Do=(e,t)=>{const n=Zn(e,t);return n?n.points:0},Eo=e=>Qn[e].puzzle.info.table.width,_o=e=>Qn[e].puzzle.info.table.height;var Mo={setGame:function(e,t){Qn[e]=t},exists:function(e){return!!Qn[e]||!1},playerExists:Xn,getActivePlayers:function(e,t){const n=t-30*N;return Jn(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*N;return Jn(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Xn(e,t)?ao(e,t,{ts:n}):Kn(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:lo,getPieceCount:eo,getImageUrl:function(e){return Qn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Qn[e].puzzle.info.imageUrl=t},get:function(e){return Qn[e]||null},getAllGames:function(){return Object.values(Qn).sort(((e,t)=>oo(e.id)===oo(t.id)?t.puzzle.data.started-e.puzzle.data.started:oo(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Zn(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Zn(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Zn(e,t);return n?n.name:null},getPlayerIndexById:qn,getPlayerIdByIndex:function(e,t){return Qn[e].players.length>t?ie.decodePlayer(Qn[e].players[t]).id:null},changePlayer:ao,setPlayer:Kn,setPiece:function(e,t,n){Qn[e].puzzle.tiles[t]=ie.encodePiece(n)},setPuzzleData:function(e,t){Qn[e].puzzle.data=t},getTableWidth:Eo,getTableHeight:_o,getPuzzle:e=>Qn[e].puzzle,getRng:e=>Qn[e].rng.obj,getPuzzleWidth:e=>Qn[e].puzzle.info.width,getPuzzleHeight:e=>Qn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Qn[e].puzzle.tiles.map(ie.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=fo(e,t);return n<0?null:Qn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Qn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:vo,getFinalPiecePos:po,getStartTs:e=>Qn[e].puzzle.data.started,getFinishTs:e=>Qn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Qn[e].puzzle,i=function(e,t){return t in Qn[e].evtInfos?Qn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],r=()=>{s.push([Ht,a.data])},d=t=>{s.push([Yt,ie.encodePiece(ro(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=Zn(e,t);n&&s.push([Qt,ie.encodePlayer(n)])},p=n[0];if(p===Rt){const l=n[1];ao(e,t,{bgcolor:l,ts:o}),u()}else if(p===Vt){const l=n[1];ao(e,t,{color:l,ts:o}),u()}else if(p===Gt){const l=`${n[1]}`.substr(0,16);ao(e,t,{name:l,ts:o}),u()}else if(p===_t){const l=n[1],a=n[2],i=Zn(e,t);if(i){const n=i.x-l,s=i.y-a;ao(e,t,{ts:o,x:n,y:s}),u()}}else if(p===Mt){const l={x:n[1],y:n[2]};ao(e,t,{d:1,ts:o}),u(),i._last_mouse_down=l;const a=((e,t)=>{const n=Qn[e].puzzle.info,o=Qn[e].puzzle.tiles;let l=-1,a=-1;for(let i=0;il)&&(l=e.z,a=i)}return a})(e,l);if(a>=0){const n=ko(e)+1;io(e,{maxZ:n}),r();const o=Io(e,a);Co(e,o,ko(e)),zo(e,o,t),c(o)}i._last_mouse=l}else if(p===Ot){const l=n[1],a=n[2],s={x:l,y:a};if(null===i._last_mouse_down)ao(e,t,{x:l,y:a,ts:o}),u();else{const n=fo(e,t);if(n>=0){ao(e,t,{x:l,y:a,ts:o}),u();const r=Io(e,n);let d=jn.pointInBounds(s,ho(e))&&jn.pointInBounds(i._last_mouse_down,ho(e));for(const t of r){const n=mo(e,t);if(jn.pointInBounds(s,n)){d=!0;break}}if(d){const t=l-i._last_mouse_down.x,n=a-i._last_mouse_down.y;So(e,r,{x:t,y:n}),c(r)}}else ao(e,t,{ts:o}),u();i._last_mouse_down=s}i._last_mouse=s}else if(p===Nt){const s={x:n[1],y:n[2]},p=0;i._last_mouse_down=null;const g=fo(e,t);if(g>=0){const n=Io(e,g);zo(e,n,0),c(n);const i=go(e,g),s=po(e,g);let h=!1;if(no(e)===ee.REAL){for(const t of n)if(uo(e,t)){h=!0;break}}else h=!0;if(h&&jn.pointDistance(s,i){const l=Qn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=co(e,t),l=co(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=go(e,t),i=jn.pointAdd(go(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(jn.pointDistance(a,i){const o=Qn[e].puzzle.tiles,l=co(e,t),a=co(e,n);let i;const s=[];l&&s.push(l),a&&s.push(a),l?i=l:a?i=a:(io(e,{maxGroup:bo(e)+1}),r(),i=bo(e));if(so(e,t,{group:i}),d(t),so(e,n,{group:i}),d(n),s.length>0)for(const r of o){const t=ie.decodePiece(r);s.includes(t.group)&&(so(e,t.idx,{group:i}),d(t.idx))}})(e,t,n),l=Io(e,t),((e,t)=>-1===To(e,t))(e,n))Po(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=yo(e,o);t>n&&(n=t)}return n})(e,l);Co(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Io(e,g)){const o=xo(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])){a=!0;break}}if(a&&to(e)===Z.ANY){const n=Do(e,t)+1;ao(e,t,{d:p,ts:o,points:n}),u()}else ao(e,t,{d:p,ts:o}),u();a&&no(e)===ee.REAL&&lo(e)===eo(e)&&(io(e,{finished:o}),r()),a&&l&&l(t)}}else ao(e,t,{d:p,ts:o}),u();i._last_mouse=s}else if(p===Bt){const l=n[1],a=n[2];ao(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else if(p===Ut){const l=n[1],a=n[2];ao(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else ao(e,t,{ts:o}),u();return function(e,t,n){Qn[e].evtInfos[t]=n}(e,t,i),s}};let No=-10,Oo=20,Bo=2,Uo=15;class Ro{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=No+Math.random()*Oo,this.vy=-1*(Bo+Math.random()*Uo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Bo=t/2,Uo=t-Bo;const n=1/4*this.canvas.width/(t/2);No=-n,Oo=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Ro(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Ro(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(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Bn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Wo=!0})),t}(l,Bn.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};sn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await sn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let k=()=>0;const x=async()=>{if("play"===o){const o=await sn.connect(n,e,t),l=ie.decodeGame(o);Mo.setGame(l.id,l),k=()=>O()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=ie.decodeGame(t.game);Mo.setGame(n.id,n),w.lastRealTs=O(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,k=()=>w.lastGameTs}}Wo=!0};await x();const C=Mo.getPieceDrawOffset(e),A=Mo.getPieceDrawSize(e),S=Mo.getPuzzleWidth(e),T=Mo.getPuzzleHeight(e),P=Mo.getTableWidth(e),z=Mo.getTableHeight(e),I={x:(P-S)/2,y:(z-T)/2},D={w:S,h:T},E={w:A,h:A},_=await Yn.loadPuzzleBitmaps(Mo.getPuzzle(e)),N=new Go(v,Mo.getRng(e));N.init();const B=v.getContext("2d");v.classList.add("loaded");const U=Nn();U.move(-(P-v.width)/2,-(z-v.height)/2);const R=function(e,t,n,o){let l=[],a=!0,i=!1,s=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("Shift"===t.key?p=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?r=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?d=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?u=e:"e"===t.key&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Mt,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Nt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([Ot,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Bt:Ut;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&(" "===e.key&&v([$t]),"replay"===o&&("I"!==e.key&&"i"!==e.key||v([jt]),"O"!==e.key&&"o"!==e.key||v([Wt]),"P"!==e.key&&"p"!==e.key||v([Lt])),"F"!==e.key&&"f"!==e.key||(Lo=!Lo,Wo=!0),"G"!==e.key&&"g"!==e.key||(jo=!jo,Wo=!0),"M"!==e.key&&"m"!==e.key||v([Ft]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(i?1:0)-(s?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});v([_t,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Bt,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([Ut,...e])}},setHotkeys:e=>{a=e}}}(v,window,U,o),V=Mo.getImageUrl(e),G=()=>{const t=Mo.getStartTs(e),n=Mo.getFinishTs(e),o=k();a.setFinished(!!n),a.setDuration((n||o)-t)};G(),a.setPiecesDone(Mo.getFinishedPiecesCount(e)),a.setPiecesTotal(Mo.getPieceCount(e));const $=k();a.setActivePlayers(Mo.getActivePlayers(e,$)),a.setIdlePlayers(Mo.getIdlePlayers(e,$));const F=!!Mo.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},H=()=>Mo.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Y=()=>Mo.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let Q="",q="",Z=!1;const K=e=>{Z=e;const[t,n]=e?[Q,"grab"]:[q,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},X=e=>{Q=Bn.colorizedCanvas(r,c,e).toDataURL(),q=Bn.colorizedCanvas(d,u,e).toDataURL(),K(Z)};X(Y());const J=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ee=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,J())},ne=()=>{w.paused=!w.paused,J()},oe=[];let le;let ae;if("play"===o?oe.push(setInterval((()=>{G()}),1e3)):"replay"===o&&J(),"play"===o)sn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Qt:{const n=ie.decodePlayer(a);n.id!==t&&(Mo.setPlayer(e,n.id,n),Wo=!0)}break;case Yt:{const t=ie.decodePiece(a);Mo.setPiece(e,t.idx,t),Wo=!0}break;case Ht:Mo.setPuzzleData(e,a),Wo=!0}L=!!Mo.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===It){const t=o[1];return Mo.addPlayer(e,t,n),!0}if(o[0]===Dt){const t=Mo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Mo.addPlayer(e,t,n),!0}if(o[0]===Et){const t=Mo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Mo.handleInput(e,t,l,n),!0}return!1};let n=w.lastGameTs;const o=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=O();if(w.paused)return w.lastRealTs=l,void(le=setTimeout(o,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let i=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const o=w.log[w.logPointer],l=n+o[o.length-1],a=w.log[e],s=a[a.length-1],r=l+s;if(r>i){w.skipNonActionPhases&&i+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,i=window.requestAnimationFrame,s=1/n,r=o*s;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(s);a(c/o),u=d,t||i(p)};return i(p),{stop:()=>{t=!0}}})({update:()=>{R.createKeyEvents();for(const n of R.consumeAll())if("play"===o){const o=n[0];if(o===_t){const e=n[1],t=n[2],o=U.worldDimToViewport({w:e,h:t});Wo=!0,U.move(o.w,o.h)}else if(o===Ot){if(se&&!Mo.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-se.x),l=Math.round(t.y-se.y);Wo=!0,U.move(o,l),se=t}}else if(o===Vt)X(n[1]);else if(o===Mt){const e={x:n[1],y:n[2]};se=U.worldToViewport(e),K(!0)}else if(o===Nt)se=null,K(!1);else if(o===Bt){const e={x:n[1],y:n[2]};Wo=!0,U.zoom("in",U.worldToViewport(e))}else if(o===Ut){const e={x:n[1],y:n[2]};Wo=!0,U.zoom("out",U.worldToViewport(e))}else o===$t?a.togglePreview():o===Ft&&a.toggleSoundsEnabled();const l=k();Mo.handleInput(e,t,n,l,(e=>{W()&&s.play()})).length>0&&(Wo=!0),sn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===Lt)ne();else if(e===Wt)te();else if(e===jt)ee();else if(e===_t){const e=n[1],t=n[2];Wo=!0,U.move(e,t)}else if(e===Ot){if(se){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-se.x),l=Math.round(t.y-se.y);Wo=!0,U.move(o,l),se=t}}else if(e===Mt){const e={x:n[1],y:n[2]};se=U.worldToViewport(e)}else if(e===Nt)se=null;else if(e===Bt){const e={x:n[1],y:n[2]};Wo=!0,U.zoom("in",U.worldToViewport(e))}else if(e===Ut){const e={x:n[1],y:n[2]};Wo=!0,U.zoom("out",U.worldToViewport(e))}else e===$t&&a.togglePreview()}L=!!Mo.getFinishTs(e),j()&&(N.update(),Wo=!0)},render:async()=>{if(!Wo)return;const n=k();let l,i,s;window.DEBUG&&Gn(0),B.fillStyle=H(),B.fillRect(0,0,v.width,v.height),window.DEBUG&&$n("clear done"),l=U.worldToViewportRaw(I),i=U.worldDimToViewportRaw(D),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(l.x,l.y,i.w,i.h),window.DEBUG&&$n("board done");const r=Mo.getPiecesSortedByZIndex(e);window.DEBUG&&$n("get tiles done"),i=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?Lo:jo)&&(s=_[e.idx],l=U.worldToViewportRaw({x:C+e.pos.x,y:C+e.pos.y}),B.drawImage(s,0,0,s.width,s.height,l.x,l.y,i.w,i.h));window.DEBUG&&$n("tiles done");const d=[];for(const a of Mo.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(s=await f(a),l=U.worldToViewport(a),B.drawImage(s,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,o]of d)B.fillText(e,t,o);window.DEBUG&&$n("players done"),a.setActivePlayers(Mo.getActivePlayers(e,n)),a.setIdlePlayers(Mo.getIdlePlayers(e,n)),a.setPiecesDone(Mo.getFinishedPiecesCount(e)),window.DEBUG&&$n("HUD done"),j()&&N.render(),Wo=!1}}),{setHotkeys:e=>{R.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),R.addEvent([Rt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),R.addEvent([Vt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),R.addEvent([Gt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},replayOnSpeedUp:ee,replayOnSpeedDown:te,replayOnPauseToggle:ne,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:V,player:{background:H(),color:Y(),name:Mo.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:W()},disconnect:sn.disconnect,connect:x,unload:()=>{oe.forEach((e=>{clearInterval(e)})),le&&clearTimeout(le),ae&&ae.stop()}}}var Yo=e({name:"game",components:{PuzzleStatus:yt,Scores:ut,SettingsOverlay:vt,PreviewOverlay:Ct,ConnectionOverlay:rn,HelpOverlay:hn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Ho(`${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)},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 Qo={id:"game"},qo={class:"menu"},Zo={class:"tabs"},Ko=s("🧩 Puzzles");Yo.render=function(e,l,s,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Qo,[p(n(u,{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"]),[[x,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",qo,[n("div",Zo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Ko])),_: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 Xo=e({name:"replay",components:{PuzzleStatus:yt,Scores:ut,SettingsOverlay:vt,PreviewOverlay:Ct,HelpOverlay:hn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Ho(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 Jo={id:"replay"},el=s("Skip no action phases: "),tl={class:"menu"},nl={class:"tabs"},ol=s("🧩 Puzzles");Xo.render=function(e,l,s,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Jo,[p(n(g,{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"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[el,p(n("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[k,e.skipNoAction]])])]),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",tl,[n("div",nl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[ol])),_:1}),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:l[12]||(l[12]=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=C({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:tt},{name:"game",path:"/g/:id",component:Yo},{name:"replay",path:"/replay/:id",component:Xo}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=S(T);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=ie.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 8390c33..a838b0d 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,7 +4,7 @@ 🧩 jigsaw.hyottoko.club - + diff --git a/build/server/main.js b/build/server/main.js index 4300097..7b14e1f 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -1279,7 +1279,9 @@ const create = (gameId) => { const logfile = filename(gameId, 0); fs.appendFileSync(logfile, ""); fs.appendFileSync(idxfile, JSON.stringify({ + gameId: gameId, total: 0, + lastTs: 0, currentFile: logfile, perFile: LINES_PER_LOG_FILE, })); @@ -1289,14 +1291,19 @@ const exists = (gameId) => { const idxfile = idxname(gameId); return fs.existsSync(idxfile); }; -const _log = (gameId, ...args) => { +const _log = (gameId, type, ...args) => { const idxfile = idxname(gameId); if (!fs.existsSync(idxfile)) { return; } + const ts = args[args.length - 1]; + const otherArgs = args.slice(0, -1); const idx = JSON.parse(fs.readFileSync(idxfile, 'utf-8')); idx.total++; - fs.appendFileSync(idx.currentFile, JSON.stringify(args) + "\n"); + const diff = ts - idx.lastTs; + idx.lastTs = ts; + const line = JSON.stringify([type, ...otherArgs, diff]).slice(1, -1); + fs.appendFileSync(idx.currentFile, line + "\n"); // prepare next log file if (idx.total % idx.perFile === 0) { const logfile = filename(gameId, idx.total); @@ -1316,7 +1323,7 @@ const get = (gameId, offset = 0) => { } const log = fs.readFileSync(file, 'utf-8').split("\n"); return log.filter(line => !!line).map(line => { - return JSON.parse(line); + return JSON.parse(`[${line}]`); }); }; var GameLog = { @@ -1807,12 +1814,11 @@ async function createGame(gameId, targetTiles, image, ts, scoreMode, shapeMode, function addPlayer(gameId, playerId, ts) { if (GameLog.shouldLog(GameCommon.getFinishTs(gameId), ts)) { const idx = GameCommon.getPlayerIndexById(gameId, playerId); - const diff = ts - GameCommon.getStartTs(gameId); if (idx === -1) { - GameLog.log(gameId, Protocol.LOG_ADD_PLAYER, playerId, diff); + GameLog.log(gameId, Protocol.LOG_ADD_PLAYER, playerId, ts); } else { - GameLog.log(gameId, Protocol.LOG_UPDATE_PLAYER, idx, diff); + GameLog.log(gameId, Protocol.LOG_UPDATE_PLAYER, idx, ts); } } GameCommon.addPlayer(gameId, playerId, ts); @@ -1821,8 +1827,7 @@ function addPlayer(gameId, playerId, ts) { function handleInput(gameId, playerId, input, ts) { if (GameLog.shouldLog(GameCommon.getFinishTs(gameId), ts)) { const idx = GameCommon.getPlayerIndexById(gameId, playerId); - const diff = ts - GameCommon.getStartTs(gameId); - GameLog.log(gameId, Protocol.LOG_HANDLE_INPUT, idx, input, diff); + GameLog.log(gameId, Protocol.LOG_HANDLE_INPUT, idx, input, ts); } const ret = GameCommon.handleInput(gameId, playerId, input, ts); GameStorage.setDirty(gameId); diff --git a/scripts/rewrite_logs.ts b/scripts/rewrite_logs.ts deleted file mode 100644 index 0ac3c99..0000000 --- a/scripts/rewrite_logs.ts +++ /dev/null @@ -1,82 +0,0 @@ -import fs from 'fs' -import Protocol from '../src/common/Protocol' -import { logger } from '../src/common/Util' -import { DATA_DIR } from '../src/server/Dirs' - -const log = logger('rewrite_logs') - -const filename = (gameId) => `${DATA_DIR}/log_${gameId}.log` - -const rewrite = (gameId) => { - const file = filename(gameId) - log.log(file) - if (!fs.existsSync(file)) { - return [] - } - let playerIds = []; - let startTs = null - const lines = fs.readFileSync(file, 'utf-8').split("\n") - const linesNew = lines.filter(line => !!line).map((line) => { - const json = JSON.parse(line) - const m = { - createGame: Protocol.LOG_HEADER, - addPlayer: Protocol.LOG_ADD_PLAYER, - handleInput: Protocol.LOG_HANDLE_INPUT, - } - const action = json[0] - if (action in m) { - json[0] = m[action] - if (json[0] === Protocol.LOG_HANDLE_INPUT) { - const inputm = { - down: Protocol.INPUT_EV_MOUSE_DOWN, - up: Protocol.INPUT_EV_MOUSE_UP, - move: Protocol.INPUT_EV_MOUSE_MOVE, - zoomin: Protocol.INPUT_EV_ZOOM_IN, - zoomout: Protocol.INPUT_EV_ZOOM_OUT, - bg_color: Protocol.INPUT_EV_BG_COLOR, - player_color: Protocol.INPUT_EV_PLAYER_COLOR, - player_name: Protocol.INPUT_EV_PLAYER_NAME, - } - const inputa = json[2][0] - if (inputa in inputm) { - json[2][0] = inputm[inputa] - } else { - throw '[ invalid input log line: "' + line + '" ]' - } - } - } else { - throw '[ invalid general log line: "' + line + '" ]' - } - - if (json[0] === Protocol.LOG_ADD_PLAYER) { - if (playerIds.indexOf(json[1]) === -1) { - playerIds.push(json[1]) - } else { - json[0] = Protocol.LOG_UPDATE_PLAYER - json[1] = playerIds.indexOf(json[1]) - } - } - - if (json[0] === Protocol.LOG_HANDLE_INPUT) { - json[1] = playerIds.indexOf(json[1]) - if (json[1] === -1) { - throw '[ invalid player ... "' + line + '" ]' - } - } - - if (json[0] === Protocol.LOG_HEADER) { - startTs = json[json.length - 1] - json[4] = json[3] - json[3] = json[2] - json[2] = json[1] - json[1] = 1 - } else { - json[json.length - 1] = json[json.length - 1] - startTs - } - return JSON.stringify(json) - }) - - fs.writeFileSync(file, linesNew.join("\n") + "\n") -} - -rewrite(process.argv[2]) diff --git a/scripts/split_logs.ts b/scripts/split_logs.ts index 3da036f..5497096 100644 --- a/scripts/split_logs.ts +++ b/scripts/split_logs.ts @@ -1,72 +1,73 @@ import fs from 'fs' -import readline from 'readline' -import stream from 'stream' import { logger } from '../src/common/Util' import { DATA_DIR } from '../src/server/Dirs' +import { filename } from '../src/server/GameLog' const log = logger('rewrite_logs') -const doit = (file: string): Promise => { - const filename = (offset: number) => file.replace(/\.log$/, `-${offset}.log`) - const idxname = () => file.replace(/\.log$/, `.idx.log`) - - let perfile = 10000 - const idx = { - total: 0, - currentFile: '', - perFile: perfile, - } - - return new Promise((resolve) => { - const instream = fs.createReadStream(DATA_DIR + '/' + file) - const outstream = new stream.Writable() - const rl = readline.createInterface(instream, outstream) - - - let lines: any[] = [] - let offset = 0 - let count = 0 - rl.on('line', (line) => { - if (!line) { - // skip empty - return - } - count++ - lines.push(line) - if (count >= perfile) { - const fn = filename(offset) - idx.currentFile = fn - idx.total += count - fs.writeFileSync(DATA_DIR + '/' + fn, lines.join("\n")) - count = 0 - offset += perfile - lines = [] - } - }) - - rl.on('close', () => { - if (count > 0) { - const fn = filename(offset) - idx.currentFile = fn - idx.total += count - fs.writeFileSync(DATA_DIR + '/' + fn, lines.join("\n")) - count = 0 - offset += perfile - lines = [] - } - - fs.writeFileSync(DATA_DIR + '/' + idxname(), JSON.stringify(idx)) - resolve() - }) - }) +interface IdxOld { + total: number + currentFile: string + perFile: number } -let logs = fs.readdirSync(DATA_DIR) - .filter(f => f.toLowerCase().match(/^log_.*\.log$/)) +interface Idx { + gameId: string + total: number + lastTs: number + currentFile: string + perFile: number +} +const doit = (idxfile: string): void => { + const gameId: string = (idxfile.match(/^log_([a-z0-9]+)\.idx\.log$/) as any[])[1] + const idxOld: IdxOld = JSON.parse(fs.readFileSync(DATA_DIR + '/' + idxfile, 'utf-8')) + + let currentFile = filename(gameId, 0) + const idxNew: Idx = { + gameId: gameId, + total: 0, + lastTs: 0, + currentFile: currentFile, + perFile: idxOld.perFile + } + + let firstTs = 0 + while (fs.existsSync(currentFile)) { + idxNew.currentFile = currentFile + const log = fs.readFileSync(currentFile, 'utf-8').split("\n") + const newLines = [] + const lines = log.filter(line => !!line).map(line => { + return JSON.parse(line) + }) + for (const l of lines) { + if (idxNew.total === 0) { + firstTs = l[4] + idxNew.lastTs = l[4] + newLines.push(JSON.stringify(l).slice(1, -1)) + } else { + const ts = firstTs + l[l.length - 1] + const diff = ts - idxNew.lastTs + idxNew.lastTs = ts + const newL = l.slice(0, -1) + newL.push(diff) + newLines.push(JSON.stringify(newL).slice(1, -1)) + } + idxNew.total++ + } + fs.writeFileSync(idxNew.currentFile, newLines.join("\n") + "\n") + currentFile = filename(gameId, idxNew.total) + } + + fs.writeFileSync(DATA_DIR + '/' + idxfile, JSON.stringify(idxNew)) + console.log('done: ' + gameId) +} + +let indexfiles = fs.readdirSync(DATA_DIR) + .filter(f => f.toLowerCase().match(/^log_[a-z0-9]+\.idx\.log$/)) ;(async () => { - for (const file of logs) { + for (const file of indexfiles) { await doit(file) } })() diff --git a/src/frontend/game.ts b/src/frontend/game.ts index 7b14d4d..d83cf27 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -590,6 +590,7 @@ export async function main( return false } + let GAME_TS = REPLAY.lastGameTs const next = async () => { if (REPLAY.logPointer + 1 >= REPLAY.log.length) { await queryNextReplayBatch(gameId) @@ -614,18 +615,20 @@ export async function main( } const currLogEntry = REPLAY.log[REPLAY.logPointer] - const currTs: Timestamp = REPLAY.gameStartTs + currLogEntry[currLogEntry.length - 1] + const currTs: Timestamp = GAME_TS + currLogEntry[currLogEntry.length - 1] + const nextLogEntry = REPLAY.log[nextIdx] - const nextTs: Timestamp = REPLAY.gameStartTs + nextLogEntry[nextLogEntry.length - 1] + const diffToNext = nextLogEntry[nextLogEntry.length - 1] + const nextTs: Timestamp = currTs + diffToNext if (nextTs > maxGameTs) { // next log entry is too far into the future if (REPLAY.skipNonActionPhases && (maxGameTs + 500 * Time.MS < nextTs)) { - const skipInterval = nextTs - currTs - maxGameTs += skipInterval + maxGameTs += diffToNext } break } + GAME_TS = currTs if (handleLogEntry(nextLogEntry, nextTs)) { RERENDER = true } diff --git a/src/server/Game.ts b/src/server/Game.ts index 7b583d4..df4b29e 100644 --- a/src/server/Game.ts +++ b/src/server/Game.ts @@ -71,11 +71,10 @@ async function createGame( function addPlayer(gameId: string, playerId: string, ts: Timestamp): void { if (GameLog.shouldLog(GameCommon.getFinishTs(gameId), ts)) { const idx = GameCommon.getPlayerIndexById(gameId, playerId) - const diff = ts - GameCommon.getStartTs(gameId) if (idx === -1) { - GameLog.log(gameId, Protocol.LOG_ADD_PLAYER, playerId, diff) + GameLog.log(gameId, Protocol.LOG_ADD_PLAYER, playerId, ts) } else { - GameLog.log(gameId, Protocol.LOG_UPDATE_PLAYER, idx, diff) + GameLog.log(gameId, Protocol.LOG_UPDATE_PLAYER, idx, ts) } } @@ -91,8 +90,7 @@ function handleInput( ): Array { if (GameLog.shouldLog(GameCommon.getFinishTs(gameId), ts)) { const idx = GameCommon.getPlayerIndexById(gameId, playerId) - const diff = ts - GameCommon.getStartTs(gameId) - GameLog.log(gameId, Protocol.LOG_HANDLE_INPUT, idx, input, diff) + GameLog.log(gameId, Protocol.LOG_HANDLE_INPUT, idx, input, ts) } const ret = GameCommon.handleInput(gameId, playerId, input, ts) diff --git a/src/server/GameLog.ts b/src/server/GameLog.ts index 0259a5a..e2960d3 100644 --- a/src/server/GameLog.ts +++ b/src/server/GameLog.ts @@ -23,8 +23,8 @@ const shouldLog = (finishTs: Timestamp, currentTs: Timestamp): boolean => { return timeSinceGameEnd <= POST_GAME_LOG_DURATION } -const filename = (gameId: string, offset: number) => `${DATA_DIR}/log_${gameId}-${offset}.log` -const idxname = (gameId: string) => `${DATA_DIR}/log_${gameId}.idx.log` +export const filename = (gameId: string, offset: number) => `${DATA_DIR}/log_${gameId}-${offset}.log` +export const idxname = (gameId: string) => `${DATA_DIR}/log_${gameId}.idx.log` const create = (gameId: string): void => { const idxfile = idxname(gameId) @@ -32,7 +32,9 @@ const create = (gameId: string): void => { const logfile = filename(gameId, 0) fs.appendFileSync(logfile, "") fs.appendFileSync(idxfile, JSON.stringify({ + gameId: gameId, total: 0, + lastTs: 0, currentFile: logfile, perFile: LINES_PER_LOG_FILE, })) @@ -44,15 +46,21 @@ const exists = (gameId: string): boolean => { return fs.existsSync(idxfile) } -const _log = (gameId: string, ...args: Array): void => { +const _log = (gameId: string, type: number, ...args: Array): void => { const idxfile = idxname(gameId) if (!fs.existsSync(idxfile)) { return } + const ts: Timestamp = args[args.length - 1] + const otherArgs: any[] = args.slice(0, -1) + const idx = JSON.parse(fs.readFileSync(idxfile, 'utf-8')) idx.total++ - fs.appendFileSync(idx.currentFile, JSON.stringify(args) + "\n") + const diff = ts - idx.lastTs + idx.lastTs = ts + const line = JSON.stringify([type, ...otherArgs, diff]).slice(1, -1) + fs.appendFileSync(idx.currentFile, line + "\n") // prepare next log file if (idx.total % idx.perFile === 0) { @@ -79,7 +87,7 @@ const get = ( const log = fs.readFileSync(file, 'utf-8').split("\n") return log.filter(line => !!line).map(line => { - return JSON.parse(line) + return JSON.parse(`[${line}]`) }) } From 86ceca4bead66de305a92ab51ffc87c0beeec6f1 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 5 Jun 2021 23:11:55 +0200 Subject: [PATCH 06/45] max old space when running ts --- scripts/ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ts b/scripts/ts index 176a61c..a0d512d 100755 --- a/scripts/ts +++ b/scripts/ts @@ -1,3 +1,3 @@ #!/bin/sh -e -node --experimental-specifier-resolution=node --loader ts-node/esm $@ +node --max-old-space-size=64 --experimental-specifier-resolution=node --loader ts-node/esm $@ From cdb02da14db82f781d6533bad2457ff4d3ed5b27 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 5 Jun 2021 23:42:59 +0200 Subject: [PATCH 07/45] remove readline and stream import --- rollup.server.config.js | 2 -- src/server/GameLog.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/rollup.server.config.js b/rollup.server.config.js index 091ff79..484815c 100644 --- a/rollup.server.config.js +++ b/rollup.server.config.js @@ -16,9 +16,7 @@ export default { "image-size", "multer", "path", - "readline", "sharp", - "stream", "url", "v8", "ws", diff --git a/src/server/GameLog.ts b/src/server/GameLog.ts index e2960d3..208384f 100644 --- a/src/server/GameLog.ts +++ b/src/server/GameLog.ts @@ -1,6 +1,4 @@ import fs from 'fs' -import readline from 'readline' -import stream from 'stream' import Time from '../common/Time' import { Timestamp } from '../common/Types' import { logger } from './../common/Util' From 19301cfc8136ab233d46be12a984f3ef805b3ea8 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sun, 6 Jun 2021 08:57:42 +0200 Subject: [PATCH 08/45] fix log time --- build/server/main.js | 6 +++--- src/server/Game.ts | 2 +- src/server/GameLog.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build/server/main.js b/build/server/main.js index 7b14e1f..a2e2880 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -1273,7 +1273,7 @@ const shouldLog = (finishTs, currentTs) => { }; const filename = (gameId, offset) => `${DATA_DIR}/log_${gameId}-${offset}.log`; const idxname = (gameId) => `${DATA_DIR}/log_${gameId}.idx.log`; -const create = (gameId) => { +const create = (gameId, ts) => { const idxfile = idxname(gameId); if (!fs.existsSync(idxfile)) { const logfile = filename(gameId, 0); @@ -1281,7 +1281,7 @@ const create = (gameId) => { fs.appendFileSync(idxfile, JSON.stringify({ gameId: gameId, total: 0, - lastTs: 0, + lastTs: ts, currentFile: logfile, perFile: LINES_PER_LOG_FILE, })); @@ -1806,7 +1806,7 @@ async function createGameObject(gameId, targetTiles, image, ts, scoreMode, shape } async function createGame(gameId, targetTiles, image, ts, scoreMode, shapeMode, snapMode) { const gameObject = await createGameObject(gameId, targetTiles, image, ts, scoreMode, shapeMode, snapMode); - GameLog.create(gameId); + GameLog.create(gameId, ts); GameLog.log(gameId, Protocol.LOG_HEADER, 1, targetTiles, image, ts, scoreMode, shapeMode, snapMode); GameCommon.setGame(gameObject.id, gameObject); GameStorage.setDirty(gameId); diff --git a/src/server/Game.ts b/src/server/Game.ts index df4b29e..2c4c123 100644 --- a/src/server/Game.ts +++ b/src/server/Game.ts @@ -51,7 +51,7 @@ async function createGame( snapMode ) - GameLog.create(gameId) + GameLog.create(gameId, ts) GameLog.log( gameId, Protocol.LOG_HEADER, diff --git a/src/server/GameLog.ts b/src/server/GameLog.ts index 208384f..b71e3db 100644 --- a/src/server/GameLog.ts +++ b/src/server/GameLog.ts @@ -24,7 +24,7 @@ const shouldLog = (finishTs: Timestamp, currentTs: Timestamp): boolean => { export const filename = (gameId: string, offset: number) => `${DATA_DIR}/log_${gameId}-${offset}.log` export const idxname = (gameId: string) => `${DATA_DIR}/log_${gameId}.idx.log` -const create = (gameId: string): void => { +const create = (gameId: string, ts: Timestamp): void => { const idxfile = idxname(gameId) if (!fs.existsSync(idxfile)) { const logfile = filename(gameId, 0) @@ -32,7 +32,7 @@ const create = (gameId: string): void => { fs.appendFileSync(idxfile, JSON.stringify({ gameId: gameId, total: 0, - lastTs: 0, + lastTs: ts, currentFile: logfile, perFile: LINES_PER_LOG_FILE, })) From d9ab766e1867435be2dba5f065eb95d22a38fe56 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sun, 6 Jun 2021 16:12:20 +0200 Subject: [PATCH 09/45] fix click to upload --- .../{index.8f0efd0f.css => index.84d14088.css} | 2 +- build/public/assets/index.99efb0e9.js | 1 + build/public/assets/index.b2021c0c.js | 1 - build/public/index.html | 4 ++-- src/frontend/components/NewImageDialog.vue | 16 +++++++++++++--- 5 files changed, 17 insertions(+), 7 deletions(-) rename build/public/assets/{index.8f0efd0f.css => index.84d14088.css} (66%) create mode 100644 build/public/assets/index.99efb0e9.js delete mode 100644 build/public/assets/index.b2021c0c.js diff --git a/build/public/assets/index.8f0efd0f.css b/build/public/assets/index.84d14088.css similarity index 66% rename from build/public/assets/index.8f0efd0f.css rename to build/public/assets/index.84d14088.css index 8d26cf8..6bff45b 100644 --- a/build/public/assets/index.8f0efd0f.css +++ b/build/public/assets/index.84d14088.css @@ -1 +1 @@ -:root{--main-color:#c1b19f;--main-darker-color:#4f4e4c;--link-color:#808db0;--link-hover-color:#c5cfeb;--highlight-color:#dd7e7e;--positive-color:#64a756;--input-bg-color:#262523;--bg-color:rgba(0,0,0,.7)}body,html{margin:0;background:#2b2b2b;color:var(--main-color);height:100%}*{font-family:monospace;font-size:15px}h1,h2,h3,h4{font-size:20px}a{color:var(--link-color);text-decoration:none}a:hover{color:var(--link-hover-color)}td,th{vertical-align:top}.btn{display:inline-block;background:var(--input-bg-color);color:var(--link-color);border:solid 1px #000;padding:5px 10px;box-shadow:1px 1px 2px rgba(0,0,0,.5),0 0 1px rgba(150,150,150,.4) inset;border-radius:4px;user-select:none}.btn-big{font-size:1.5em;padding:10px 20px}.btn:hover{background:#2f2e2c;color:var(--link-hover-color);border:solid 1px #111;box-shadow:0 0 1px rgba(150,150,150,.4) inset;cursor:pointer}.btn:disabled{background:#2f2e2c;color:#8c4747!important;border:solid 1px #111;box-shadow:0 0 1px rgba(150,150,150,.4) inset;cursor:not-allowed}input{background:#333230;border-radius:4px;color:var(--main-color);padding:6px 10px;border:solid 1px #000;box-shadow:0 0 3px rgba(0,0,0,.3) inset}input:focus{border:solid 1px #686767;background:var(--input-bg-color)}.scores{position:absolute;right:0;top:0;background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7)}.timer{position:absolute;left:0;top:0;background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7)}.menu{position:absolute;top:0;left:50%;transform:translateX(-50%);background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7);z-index:2}.closed{display:none}.overlay{position:fixed;top:0;left:0;right:0;bottom:0;z-index:10;background:var(--bg-color)}.overlay.transparent{background:0 0}.overlay-content{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7);z-index:1}.connection-lost .overlay-content{padding:20px;text-align:center}.preview{position:absolute;top:20px;left:20px;bottom:20px;right:20px}.preview .img{height:100%;width:100%;position:absolute;background-repeat:no-repeat;background-position:center;background-size:contain}.menu .opener{display:inline-block;margin-right:10px;color:var(--link-color)}.menu .opener:last-child{margin-right:0}.menu .opener:hover{color:var(--link-hover-color);cursor:pointer}kbd{background-color:#eee;border-radius:3px;border:1px solid #b4b4b4;box-shadow:0 1px 1px rgba(0,0,0,.2),0 2px 0 0 rgba(255,255,255,.7) inset;color:#333;display:inline-block;font-size:.85em;font-weight:700;line-height:1;padding:2px 4px;white-space:nowrap}.hint{color:var(--main-darker-color)}.bit{background:#3b3737;border-radius:.5em;padding:.25em .5em;display:inline-block;margin:0 .25em .25em 0;cursor:pointer}.bit.on{color:var(--positive-color)}.upload-image-teaser{text-align:center}.upload-image-teaser .btn{margin-bottom:.5em}table label{line-height:32px}.nav{list-style:none;padding:0}.nav li{display:inline-block;margin-right:1em}.image-list{overflow:scroll}.image-list-inner{white-space:nowrap}.imageteaser{width:150px;height:100px;display:inline-block;margin:5px;background-size:contain;background-position:center;background-repeat:no-repeat;background-color:#222;cursor:pointer}.game-teaser-wrap{display:inline-block;width:20%;padding:5px;box-sizing:border-box}.game-teaser{display:block;background-repeat:no-repeat;background-position:center;background-size:contain;position:relative;padding-top:56.25%;width:100%;background-color:#222}.game-info{position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%}.game-info-text{position:absolute;top:0;background:var(--bg-color);padding:5px}.game-replay{position:absolute;top:0;right:0}html.view-game{overflow:hidden}html.view-game body{overflow:hidden}html.view-replay{overflow:hidden}html.view-replay body{overflow:hidden}.imageteaser{position:relative}.imageteaser .edit{display:none;position:absolute}.imageteaser:hover .edit{display:inline-block}.input[data-v-39ed99c7]{margin-bottom:.5em}.autocomplete[data-v-39ed99c7]{position:relative}.autocomplete ul[data-v-39ed99c7]{list-style:none;padding:0;margin:0;position:absolute;left:0;right:0;background:#333230;top:-.5em}.autocomplete ul li[data-v-39ed99c7]{position:relative;padding:.5em .5em .5em 1.5em;cursor:pointer}.autocomplete ul li.active[data-v-39ed99c7]{color:var(--link-hover-color);background:var(--input-bg-color)}.autocomplete ul li.active[data-v-39ed99c7]:before{content:'▶';display:block;position:absolute;left:.5em}.new-image-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}@media (max-width:1400px){.new-image-dialog .overlay-content{grid-template-columns:auto;grid-template-rows:1fr min-content min-content;grid-template-areas:"image" "settings" "buttons"}.new-image-dialog .overlay-content .area-buttons .btn br{display:none}}.new-image-dialog .area-image{grid-area:image;margin:.5em;border:solid 6px transparent}.new-image-dialog .area-image.no-image{align-content:center;display:grid;text-align:center;border:solid 6px;position:relative}.new-image-dialog .area-image.droppable{border:dashed 6px}.area-image *{pointer-events:none}.new-image-dialog .area-image .has-image{position:relative;width:100%;height:100%}.new-image-dialog .area-image .has-image .remove{position:absolute;top:.5em;left:.5em}.new-image-dialog .area-settings{grid-area:settings}.new-image-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.new-image-dialog .area-buttons{align-self:end;grid-area:buttons}.new-image-dialog .area-buttons button{width:100%;margin-top:.5em}.new-image-dialog .upload{position:absolute;top:0;left:0;right:0;bottom:0;cursor:pointer}.new-image-dialog .upload .btn{position:absolute;top:50%;transform:translate(-50%,-50%)}.edit-image-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}.edit-image-dialog .area-image{grid-area:image;margin:20px}.edit-image-dialog .area-image.no-image{align-content:center;display:grid;text-align:center;border:dashed 6px;position:relative}.edit-image-dialog .area-image .has-image{position:relative;width:100%;height:100%}.edit-image-dialog .area-image .has-image .remove{position:absolute;top:.5em;left:.5em}.edit-image-dialog .area-settings{grid-area:settings}.edit-image-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.edit-image-dialog .area-buttons{align-self:end;grid-area:buttons}.edit-image-dialog .area-buttons button{width:100%;margin-top:.5em}.new-game-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}.new-game-dialog .area-image{grid-area:image;display:grid;grid-template-rows:1fr min-content;grid-template-areas:"image" "image-title";margin-right:1em}@media (max-width:1400px){.new-game-dialog .overlay-content{grid-template-columns:auto;grid-template-rows:1fr min-content min-content;grid-template-areas:"image" "settings" "buttons"}.new-game-dialog .area-image{margin-right:0}}.new-game-dialog .area-settings{grid-area:settings}.new-game-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.new-game-dialog .area-buttons{align-self:end;grid-area:buttons}.new-game-dialog .area-buttons button{width:100%}.new-game-dialog .has-image{box-sizing:border-box;grid-area:image;position:relative;width:100%;height:100%;border:solid 1px}.new-game-dialog .image-title{grid-area:image-title;text-align:center;padding:.5em 0;background:var(--main-color);color:#262523}.new-game-dialog .has-image .remove{position:absolute;top:.5em;left:.5em} \ No newline at end of file +:root{--main-color:#c1b19f;--main-darker-color:#4f4e4c;--link-color:#808db0;--link-hover-color:#c5cfeb;--highlight-color:#dd7e7e;--positive-color:#64a756;--input-bg-color:#262523;--bg-color:rgba(0,0,0,.7)}body,html{margin:0;background:#2b2b2b;color:var(--main-color);height:100%}*{font-family:monospace;font-size:15px}h1,h2,h3,h4{font-size:20px}a{color:var(--link-color);text-decoration:none}a:hover{color:var(--link-hover-color)}td,th{vertical-align:top}.btn{display:inline-block;background:var(--input-bg-color);color:var(--link-color);border:solid 1px #000;padding:5px 10px;box-shadow:1px 1px 2px rgba(0,0,0,.5),0 0 1px rgba(150,150,150,.4) inset;border-radius:4px;user-select:none}.btn-big{font-size:1.5em;padding:10px 20px}.btn:hover{background:#2f2e2c;color:var(--link-hover-color);border:solid 1px #111;box-shadow:0 0 1px rgba(150,150,150,.4) inset;cursor:pointer}.btn:disabled{background:#2f2e2c;color:#8c4747!important;border:solid 1px #111;box-shadow:0 0 1px rgba(150,150,150,.4) inset;cursor:not-allowed}input{background:#333230;border-radius:4px;color:var(--main-color);padding:6px 10px;border:solid 1px #000;box-shadow:0 0 3px rgba(0,0,0,.3) inset}input:focus{border:solid 1px #686767;background:var(--input-bg-color)}.scores{position:absolute;right:0;top:0;background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7)}.timer{position:absolute;left:0;top:0;background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7)}.menu{position:absolute;top:0;left:50%;transform:translateX(-50%);background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7);z-index:2}.closed{display:none}.overlay{position:fixed;top:0;left:0;right:0;bottom:0;z-index:10;background:var(--bg-color)}.overlay.transparent{background:0 0}.overlay-content{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7);z-index:1}.connection-lost .overlay-content{padding:20px;text-align:center}.preview{position:absolute;top:20px;left:20px;bottom:20px;right:20px}.preview .img{height:100%;width:100%;position:absolute;background-repeat:no-repeat;background-position:center;background-size:contain}.menu .opener{display:inline-block;margin-right:10px;color:var(--link-color)}.menu .opener:last-child{margin-right:0}.menu .opener:hover{color:var(--link-hover-color);cursor:pointer}kbd{background-color:#eee;border-radius:3px;border:1px solid #b4b4b4;box-shadow:0 1px 1px rgba(0,0,0,.2),0 2px 0 0 rgba(255,255,255,.7) inset;color:#333;display:inline-block;font-size:.85em;font-weight:700;line-height:1;padding:2px 4px;white-space:nowrap}.hint{color:var(--main-darker-color)}.bit{background:#3b3737;border-radius:.5em;padding:.25em .5em;display:inline-block;margin:0 .25em .25em 0;cursor:pointer}.bit.on{color:var(--positive-color)}.upload-image-teaser{text-align:center}.upload-image-teaser .btn{margin-bottom:.5em}table label{line-height:32px}.nav{list-style:none;padding:0}.nav li{display:inline-block;margin-right:1em}.image-list{overflow:scroll}.image-list-inner{white-space:nowrap}.imageteaser{width:150px;height:100px;display:inline-block;margin:5px;background-size:contain;background-position:center;background-repeat:no-repeat;background-color:#222;cursor:pointer}.game-teaser-wrap{display:inline-block;width:20%;padding:5px;box-sizing:border-box}.game-teaser{display:block;background-repeat:no-repeat;background-position:center;background-size:contain;position:relative;padding-top:56.25%;width:100%;background-color:#222}.game-info{position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%}.game-info-text{position:absolute;top:0;background:var(--bg-color);padding:5px}.game-replay{position:absolute;top:0;right:0}html.view-game{overflow:hidden}html.view-game body{overflow:hidden}html.view-replay{overflow:hidden}html.view-replay body{overflow:hidden}.imageteaser{position:relative}.imageteaser .edit{display:none;position:absolute}.imageteaser:hover .edit{display:inline-block}.input[data-v-39ed99c7]{margin-bottom:.5em}.autocomplete[data-v-39ed99c7]{position:relative}.autocomplete ul[data-v-39ed99c7]{list-style:none;padding:0;margin:0;position:absolute;left:0;right:0;background:#333230;top:-.5em}.autocomplete ul li[data-v-39ed99c7]{position:relative;padding:.5em .5em .5em 1.5em;cursor:pointer}.autocomplete ul li.active[data-v-39ed99c7]{color:var(--link-hover-color);background:var(--input-bg-color)}.autocomplete ul li.active[data-v-39ed99c7]:before{content:'▶';display:block;position:absolute;left:.5em}.new-image-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}@media (max-width:1400px){.new-image-dialog .overlay-content{grid-template-columns:auto;grid-template-rows:1fr min-content min-content;grid-template-areas:"image" "settings" "buttons"}.new-image-dialog .overlay-content .area-buttons .btn br{display:none}}.new-image-dialog .area-image{grid-area:image;margin:.5em;border:solid 6px transparent}.new-image-dialog .area-image.no-image{align-content:center;display:grid;text-align:center;border:solid 6px;position:relative}.new-image-dialog .area-image.droppable{border:dashed 6px}.new-image-dialog .area-image .has-image{position:relative;width:100%;height:100%}.new-image-dialog .area-image .has-image .remove{position:absolute;top:.5em;left:.5em}.new-image-dialog .area-settings{grid-area:settings}.new-image-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.new-image-dialog .area-buttons{align-self:end;grid-area:buttons}.new-image-dialog .area-buttons button{width:100%;margin-top:.5em}.new-image-dialog .upload{position:absolute;top:0;left:0;right:0;bottom:0;cursor:pointer}.new-image-dialog .upload .btn{position:absolute;top:50%;transform:translate(-50%,-50%)}.area-image .drop-target{display:none}.area-image.droppable .drop-target{pointer-events:none;position:absolute;top:0;left:0;right:0;bottom:0;z-index:3}.edit-image-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}.edit-image-dialog .area-image{grid-area:image;margin:20px}.edit-image-dialog .area-image.no-image{align-content:center;display:grid;text-align:center;border:dashed 6px;position:relative}.edit-image-dialog .area-image .has-image{position:relative;width:100%;height:100%}.edit-image-dialog .area-image .has-image .remove{position:absolute;top:.5em;left:.5em}.edit-image-dialog .area-settings{grid-area:settings}.edit-image-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.edit-image-dialog .area-buttons{align-self:end;grid-area:buttons}.edit-image-dialog .area-buttons button{width:100%;margin-top:.5em}.new-game-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}.new-game-dialog .area-image{grid-area:image;display:grid;grid-template-rows:1fr min-content;grid-template-areas:"image" "image-title";margin-right:1em}@media (max-width:1400px){.new-game-dialog .overlay-content{grid-template-columns:auto;grid-template-rows:1fr min-content min-content;grid-template-areas:"image" "settings" "buttons"}.new-game-dialog .area-image{margin-right:0}}.new-game-dialog .area-settings{grid-area:settings}.new-game-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.new-game-dialog .area-buttons{align-self:end;grid-area:buttons}.new-game-dialog .area-buttons button{width:100%}.new-game-dialog .has-image{box-sizing:border-box;grid-area:image;position:relative;width:100%;height:100%;border:solid 1px}.new-game-dialog .image-title{grid-area:image-title;text-align:center;padding:.5em 0;background:var(--main-color);color:#262523}.new-game-dialog .has-image .remove{position:absolute;top:.5em;left:.5em} \ No newline at end of file diff --git a/build/public/assets/index.99efb0e9.js b/build/public/assets/index.99efb0e9.js new file mode 100644 index 0000000..ca43163 --- /dev/null +++ b/build/public/assets/index.99efb0e9.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as l,b as o,r as a,o as i,e as s,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as k,s as x,u as C,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const P={id:"app"},z={key:0,class:"nav"},I=s("Index"),D=s("New game");T.render=function(e,s,r,d,c,u){const p=a("router-link"),g=a("router-view");return i(),t("div",P,[e.showNav?(i(),t("ul",z,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:l((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:l((()=>[D])),_:1})])])):o("",!0),n(g)])};const E=864e5,_=e=>{const t=Math.floor(e/E);e%=E;const n=Math.floor(e/36e5);e%=36e5;const l=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${l}m ${Math.floor(e/1e3)}s`};var M=1,N=1e3,O=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>_(t-e),U=_,R=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",l=e,o=t||O();return`${n} ${B(l,o)}`}}});const V={class:"game-info-text"},G=n("br",null,null,-1),$=n("br",null,null,-1),F=n("br",null,null,-1),L=s(" ↪️ Watch replay ");R.render=function(e,d,c,u,p,g){const h=a("router-link");return i(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:l((()=>[n("span",V,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),G,s(" 👥 "+r(e.game.players),1),$,s(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(i(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:l((()=>[L])),_:1},8,["to"])):o("",!0)],4)};var j=e({components:{GameTeaser:R},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 W=n("h1",null,"Running games",-1),H=n("h1",null,"Finished games",-1);j.render=function(e,l,o,s,r,u){const p=a("game-teaser");return i(),t("div",null,[W,(i(!0),t(d,null,c(e.gamesRunning,((e,l)=>(i(),t("div",{class:"game-teaser-wrap",key:l},[n(p,{game:e},null,8,["game"])])))),128)),H,(i(!0),t(d,null,c(e.gamesFinished,((e,l)=>(i(),t("div",{class:"game-teaser-wrap",key:l},[n(p,{game:e},null,8,["game"])])))),128))])};var Y=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});Y.render=function(e,l,o,a,s,r){return i(),t("div",{class:"imageteaser",style:e.style,onClick:l[2]||(l[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:l[1]||(l[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Q,q,Z,K,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:Y},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,l,o,s,r){const u=a("image-teaser");return i(),t("div",null,[(i(!0),t(d,null,c(e.images,((n,l)=>(i(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:l},null,8,["image","onClick","onEditClick"])))),128))])},(q=Q||(Q={}))[q.Flat=0]="Flat",q[q.Out=1]="Out",q[q.In=-1]="In",(K=Z||(Z={}))[K.FINAL=0]="FINAL",K[K.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class le{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),l=t[n];t[n]=t[e],t[e]=l}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new le(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const oe=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const l=new Date,o=oe(l.getHours(),"00"),a=oe(l.getMinutes(),"00"),i=oe(l.getSeconds(),"00");console[t](`${o}:${a}:${i}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var ie={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",le.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Z.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:le.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const l=[n,e[n]].map(encodeURIComponent);t.push(l.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const se={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};se.render=function(e,n,l,o,a,s){return i(),t("div",{style:s.style,title:l.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.key&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-39ed99c7");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,l,a,s,u,m)=>(i(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.input=t),placeholder:"Plants, People",onChange:l[2]||(l[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:l[3]||(l[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:l[4]||(l[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(i(),t("div",ce,[n("ul",null,[(i(!0),t(d,null,c(e.autocomplete.values,((n,l)=>(i(),t("li",{key:l,class:{active:l===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):o("",!0),(i(!0),t(d,null,c(e.values,((n,l)=>(i(),t("span",{key:l,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-39ed99c7";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const l=n[0];return l.type.startsWith("image/")?l:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=n("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},ve=n("span",{class:"btn"},"Upload File",-1),we={class:"area-settings"},be=n("td",null,[n("label",null,"Title")],-1),ke=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),xe=n("td",null,[n("label",null,"Tags")],-1),Ce={class:"area-buttons"},Ae=s("🧩 Post to gallery "),Se=n("br",null,null,-1),Te=s(" + set up game");ge.render=function(e,l,o,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay new-image-dialog",onClick:l[11]||(l[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[10]||(l[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:l[3]||(l[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:l[4]||(l[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:l[5]||(l[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(i(),t("div",me,[n("span",{class:"remove btn",onClick:l[1]||(l[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(i(),t("div",ye,[n("label",fe,[n("input",{type:"file",style:{display:"none"},onChange:l[2]||(l[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ve])]))],34),n("div",we,[n("table",null,[n("tr",null,[be,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":l[6]||(l[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),ke,n("tr",null,[xe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[7]||(l[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Ce,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:l[8]||(l[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:l[9]||(l[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ae,Se,Te],8,["disabled"])])])])};var Pe=e({name:"edit-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const ze={class:"area-image"},Ie={class:"has-image"},De={class:"area-settings"},Ee=n("td",null,[n("label",null,"Title")],-1),_e=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Me=n("td",null,[n("label",null,"Tags")],-1),Ne={class:"area-buttons"};Pe.render=function(e,l,o,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay edit-image-dialog",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("div",ze,[n("div",Ie,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",De,[n("table",null,[n("tr",null,[Ee,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),_e,n("tr",null,[Me,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[2]||(l[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Ne,[n("button",{class:"btn",onClick:l[3]||(l[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Oe=e({name:"new-game-dialog",components:{ResponsiveImage:se},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Z.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Be={class:"area-image"},Ue={class:"has-image"},Re={key:0,class:"image-title"},Ve={class:"area-settings"},Ge=n("td",null,[n("label",null,"Pieces")],-1),$e=n("td",null,[n("label",null,"Scoring: ")],-1),Fe=s(" Any (Score when pieces are connected to each other or on final location)"),Le=n("br",null,null,-1),je=s(" Final (Score when pieces are put to their final location)"),We=n("td",null,[n("label",null,"Shapes: ")],-1),He=s(" Normal"),Ye=n("br",null,null,-1),Qe=s(" Any (flat pieces can occur anywhere)"),qe=n("br",null,null,-1),Ze=s(" Flat (all pieces flat on all sides)"),Ke=n("td",null,[n("label",null,"Snapping: ")],-1),Xe=s(" Normal (pieces snap to final destination and to each other)"),Je=n("br",null,null,-1),et=s(" Real (pieces snap only to corners, already snapped pieces and to each other)"),tt={class:"area-buttons"};Oe.render=function(e,l,s,d,c,h){const m=a("responsive-image");return i(),t("div",{class:"overlay new-game-dialog",onClick:l[11]||(l[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[10]||(l[10]=u((()=>{}),["stop"]))},[n("div",Be,[n("div",Ue,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(i(),t("div",Re,'"'+r(e.image.title)+'"',1)):o("",!0)]),n("div",Ve,[n("table",null,[n("tr",null,[Ge,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[$e,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[2]||(l[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Fe]),Le,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[3]||(l[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),je])])]),n("tr",null,[We,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[4]||(l[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),He]),Ye,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[5]||(l[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Qe]),qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[6]||(l[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),Ze])])]),n("tr",null,[Ke,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[7]||(l[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Xe]),Je,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[8]||(l[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),et])])])])]),n("div",tt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:l[9]||(l[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var nt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Pe,NewGameDialog:Oe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${ie.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const lt={class:"upload-image-teaser"},ot=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),at={key:0},it=s(" Tags: "),st=s(" Sort by: "),rt=n("option",{value:"date_desc"},"Newest first",-1),dt=n("option",{value:"date_asc"},"Oldest first",-1),ct=n("option",{value:"alpha_asc"},"A-Z",-1),ut=n("option",{value:"alpha_desc"},"Z-A",-1);nt.render=function(e,l,s,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return i(),t("div",null,[n("div",lt,[n("div",{class:"btn btn-big",onClick:l[1]||(l[1]=t=>e.dialog="new-image")},"Upload your image"),ot]),n("div",null,[e.tags.length>0?(i(),t("label",at,[it,(i(!0),t(d,null,c(e.relevantTags,((n,l)=>(i(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:l,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):o("",!0),n("label",null,[st,p(n("select",{"onUpdate:modelValue":l[2]||(l[2]=t=>e.filters.sort=t),onChange:l[3]||(l[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[rt,dt,ct,ut],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(i(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:l[4]||(l[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):o("",!0),"edit-image"===e.dialog?(i(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:l[5]||(l[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):o("",!0),e.image&&"new-game"===e.dialog?(i(),t(v,{key:2,onBgclick:l[6]||(l[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):o("",!0)])};var pt=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 gt={class:"scores"},ht=n("div",null,"Scores",-1),mt=n("td",null,"⚡",-1),yt=n("td",null,"💤",-1);pt.render=function(e,l,o,a,s,u){return i(),t("div",gt,[ht,n("table",null,[(i(!0),t(d,null,c(e.actives,((e,l)=>(i(),t("tr",{key:l,style:{color:e.color}},[mt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(i(!0),t(d,null,c(e.idles,((e,l)=>(i(),t("tr",{key:l,style:{color:e.color}},[yt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var ft=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 U(this.duration)}}});const vt={class:"timer"};ft.render=function(e,l,o,a,s,d){return i(),t("div",vt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var wt=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 bt=n("td",null,[n("label",null,"Background: ")],-1),kt=n("td",null,[n("label",null,"Color: ")],-1),xt=n("td",null,[n("label",null,"Name: ")],-1),Ct=n("td",null,[n("label",null,"Sounds: ")],-1);wt.render=function(e,l,o,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:l[6]||(l[6]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:l[5]||(l[5]=u((()=>{}),["stop"]))},[n("tr",null,[bt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":l[1]||(l[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[kt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":l[2]||(l[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[xt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":l[3]||(l[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[Ct,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":l[4]||(l[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[k,e.modelValue.soundsEnabled]])])])])])};var At=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const St={class:"preview"};At.render=function(e,l,o,a,s,r){return i(),t("div",{class:"overlay",onClick:l[1]||(l[1]=t=>e.$emit("bgclick"))},[n("div",St,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Tt=1,Pt=4,zt=2,It=3,Dt=2,Et=4,_t=3,Mt=9,Nt=1,Ot=2,Bt=3,Ut=4,Rt=5,Vt=6,Gt=7,$t=8,Ft=10,Lt=11,jt=12,Wt=13,Ht=14,Yt=1,Qt=2,qt=3;const Zt=ae("Communication.js");let Kt,Xt=[],Jt=e=>{Xt.push(e)},en=[],tn=e=>{en.push(e)};let nn=0;const ln=e=>{nn!==e&&(nn=e,tn(e))};function on(e){if(2===nn)try{Kt.send(JSON.stringify(e))}catch(t){Zt.info("unable to send message.. maybe because ws is invalid?")}}let an,sn;var rn={connect:function(e,t,n){return an=0,sn={},ln(3),new Promise((l=>{Kt=new WebSocket(e,n+"|"+t),Kt.onopen=()=>{ln(2),on([It])},Kt.onmessage=e=>{const t=JSON.parse(e.data),o=t[0];if(o===Pt){const e=t[1];l(e)}else{if(o!==Tt)throw`[ 2021-05-09 invalid connect msgType ${o} ]`;{const e=t[1],l=t[2];if(e===n&&sn[l])return void delete sn[l];Jt(t)}}},Kt.onerror=()=>{throw ln(1),"[ 2021-05-15 onerror ]"},Kt.onclose=e=>{4e3===e.code||1001===e.code?ln(4):ln(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},l=await fetch(`/api/replay-data${ie.asQueryArgs(n)}`);return await l.json()},disconnect:function(){Kt&&Kt.close(4e3),an=0,sn={}},sendClientEvent:function(e){an++,sn[an]=e,on([zt,an,sn[an]])},onServerChange:function(e){Jt=e;for(const t of Xt)Jt(t);Xt=[]},onConnectionStateChange:function(e){tn=e;for(const t of en)tn(t);en=[]},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},dn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===rn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===rn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const cn={key:0,class:"overlay connection-lost"},un={key:0,class:"overlay-content"},pn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),gn={key:1,class:"overlay-content"},hn=n("div",null,"Connecting...",-1);dn.render=function(e,l,a,s,r,d){return e.show?(i(),t("div",cn,[e.lostConnection?(i(),t("div",un,[pn,n("span",{class:"btn",onClick:l[1]||(l[1]=t=>e.$emit("reconnect"))},"Reconnect")])):o("",!0),e.connecting?(i(),t("div",gn,[hn])):o("",!0)])):o("",!0)};var mn=e({name:"help-overlay",emits:{bgclick:null}});const yn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),fn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),vn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),wn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),bn=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),kn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),xn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),Cn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),An=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Sn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Tn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),Pn=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),zn=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),In=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);mn.render=function(e,l,o,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[yn,fn,vn,wn,bn,kn,xn,Cn,An,Sn,Tn,Pn,zn,In])])};var Dn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),En=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),_n=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Mn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function On(){let e=0,t=0,n=1;const l=(l,o)=>{e+=l/n,t+=o/n},o=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},a=l=>({x:l.x/n-e,y:l.y/n-t}),i=l=>({x:(l.x+e)*n,y:(l.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,move:l,canZoom:e=>n!=o(e),zoom:(e,t)=>((e,t)=>{if(n==e)return!1;const o=1-n/e;return l(-t.x*o,-t.y*o),n=e,!0})(o(e),t),worldToViewport:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function Bn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Un={createCanvas:Bn,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const l=Bn(t,n);return l.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(l)},colorizedCanvas:function(e,t,n){const l=Bn(e.width,e.height),o=l.getContext("2d");return o.save(),o.drawImage(t,0,0),o.fillStyle=n,o.globalCompositeOperation="source-in",o.fillRect(0,0,t.width,t.height),o.restore(),o.save(),o.globalCompositeOperation="destination-over",o.drawImage(e,0,0),o.restore(),l}};const Rn=ae("Debug.js");let Vn=0,Gn=0;var $n=e=>{Vn=performance.now(),Gn=e},Fn=e=>{const t=performance.now(),n=t-Vn;n>Gn&&Rn.log(e+": "+n),Vn=t};function Ln(e,t){const n=e.x-t.x,l=e.y-t.y;return Math.sqrt(n*n+l*l)}function jn(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var Wn={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:Ln,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:jn,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 Ln(jn(e),jn(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const Hn=ae("PuzzleGraphics.js");function Yn(e,t){const n=ie.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Qn={loadPuzzleBitmaps:async function(e){const t=await Un.loadImageToBitmap(e.info.imageUrl),n=await Un.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Hn.log("start createPuzzleTileBitmaps");const l=n.tileSize,o=n.tileMarginWidth,a=n.tileDrawSize,i=l/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],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,a={x:o,y:o},r=Wn.pointAdd(a,{x:l,y:0}),c=Wn.pointAdd(r,{x:0,y:l}),u=Wn.pointSub(c,{x:l,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let l=0;lie.decodePiece(qn[e].puzzle.tiles[t]),cl=(e,t)=>dl(e,t).group,ul=(e,t)=>{const n=qn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},pl=(e,t)=>{const n=qn[e].puzzle.info,l={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},o=function(e,t){const n=qn[e].puzzle.info,l=ie.coordByPieceIdx(n,t),o=l.x*n.tileSize,a=l.y*n.tileSize;return{x:o,y:a}}(e,t);return Wn.pointAdd(l,o)},gl=(e,t)=>dl(e,t).pos,hl=e=>{const t=El(e),n=_l(e),l=Math.round(t/4),o=Math.round(n/4);return{x:0-l,y:0-o,w:t+2*l,h:n+2*o}},ml=(e,t)=>{const n=wl(e),l=dl(e,t);return{x:l.pos.x,y:l.pos.y,w:n,h:n}},yl=(e,t)=>dl(e,t).z,fl=(e,t)=>{for(const n of qn[e].puzzle.tiles){const e=ie.decodePiece(n);if(e.owner===t)return e.idx}return-1},vl=e=>qn[e].puzzle.info.tileDrawSize,wl=e=>qn[e].puzzle.info.tileSize,bl=e=>qn[e].puzzle.data.maxGroup,kl=e=>qn[e].puzzle.data.maxZ;function xl(e,t){const n=qn[e].puzzle.info,l=ie.coordByPieceIdx(n,t);return[l.y>0?t-n.tilesX:-1,l.x0?t-1:-1]}const Cl=(e,t,n)=>{for(const l of t)rl(e,l,{z:n})},Al=(e,t,n)=>{const l=gl(e,t);rl(e,t,{pos:Wn.pointAdd(l,n)})},Sl=(e,t,n)=>{const l=vl(e),o=hl(e),a=n;for(const i of t){const t=dl(e,i);t.pos.x+n.xo.x+o.w&&(a.x=Math.min(o.x+o.w-t.pos.x+l,a.x)),t.pos.y+n.yo.y+o.h&&(a.y=Math.min(o.y+o.h-t.pos.y+l,a.y))}for(const i of t)Al(e,i,a)},Tl=(e,t)=>dl(e,t).owner,Pl=(e,t)=>{for(const n of t)rl(e,n,{owner:-1,z:1})},zl=(e,t,n)=>{for(const l of t)rl(e,l,{owner:n})};function Il(e,t){const n=qn[e].puzzle.tiles,l=ie.decodePiece(n[t]),o=[];if(l.group)for(const a of n){const e=ie.decodePiece(a);e.group===l.group&&o.push(e.idx)}else o.push(l.idx);return o}const Dl=(e,t)=>{const n=Kn(e,t);return n?n.points:0},El=e=>qn[e].puzzle.info.table.width,_l=e=>qn[e].puzzle.info.table.height;var Ml={setGame:function(e,t){qn[e]=t},exists:function(e){return!!qn[e]||!1},playerExists:Jn,getActivePlayers:function(e,t){const n=t-30*N;return el(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*N;return el(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Jn(e,t)?il(e,t,{ts:n}):Xn(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:al,getPieceCount:tl,getImageUrl:function(e){return qn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){qn[e].puzzle.info.imageUrl=t},get:function(e){return qn[e]||null},getAllGames:function(){return Object.values(qn).sort(((e,t)=>ol(e.id)===ol(t.id)?t.puzzle.data.started-e.puzzle.data.started:ol(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Kn(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Kn(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Kn(e,t);return n?n.name:null},getPlayerIndexById:Zn,getPlayerIdByIndex:function(e,t){return qn[e].players.length>t?ie.decodePlayer(qn[e].players[t]).id:null},changePlayer:il,setPlayer:Xn,setPiece:function(e,t,n){qn[e].puzzle.tiles[t]=ie.encodePiece(n)},setPuzzleData:function(e,t){qn[e].puzzle.data=t},getTableWidth:El,getTableHeight:_l,getPuzzle:e=>qn[e].puzzle,getRng:e=>qn[e].rng.obj,getPuzzleWidth:e=>qn[e].puzzle.info.width,getPuzzleHeight:e=>qn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return qn[e].puzzle.tiles.map(ie.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=fl(e,t);return n<0?null:qn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>qn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:vl,getFinalPiecePos:pl,getStartTs:e=>qn[e].puzzle.data.started,getFinishTs:e=>qn[e].puzzle.data.finished,handleInput:function(e,t,n,l,o){const a=qn[e].puzzle,i=function(e,t){return t in qn[e].evtInfos?qn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],r=()=>{s.push([Yt,a.data])},d=t=>{s.push([Qt,ie.encodePiece(dl(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=Kn(e,t);n&&s.push([qt,ie.encodePlayer(n)])},p=n[0];if(p===Vt){const o=n[1];il(e,t,{bgcolor:o,ts:l}),u()}else if(p===Gt){const o=n[1];il(e,t,{color:o,ts:l}),u()}else if(p===$t){const o=`${n[1]}`.substr(0,16);il(e,t,{name:o,ts:l}),u()}else if(p===Mt){const o=n[1],a=n[2],i=Kn(e,t);if(i){const n=i.x-o,s=i.y-a;il(e,t,{ts:l,x:n,y:s}),u()}}else if(p===Nt){const o={x:n[1],y:n[2]};il(e,t,{d:1,ts:l}),u(),i._last_mouse_down=o;const a=((e,t)=>{const n=qn[e].puzzle.info,l=qn[e].puzzle.tiles;let o=-1,a=-1;for(let i=0;io)&&(o=e.z,a=i)}return a})(e,o);if(a>=0){const n=kl(e)+1;sl(e,{maxZ:n}),r();const l=Il(e,a);Cl(e,l,kl(e)),zl(e,l,t),c(l)}i._last_mouse=o}else if(p===Bt){const o=n[1],a=n[2],s={x:o,y:a};if(null===i._last_mouse_down)il(e,t,{x:o,y:a,ts:l}),u();else{const n=fl(e,t);if(n>=0){il(e,t,{x:o,y:a,ts:l}),u();const r=Il(e,n);let d=Wn.pointInBounds(s,hl(e))&&Wn.pointInBounds(i._last_mouse_down,hl(e));for(const t of r){const n=ml(e,t);if(Wn.pointInBounds(s,n)){d=!0;break}}if(d){const t=o-i._last_mouse_down.x,n=a-i._last_mouse_down.y;Sl(e,r,{x:t,y:n}),c(r)}}else il(e,t,{ts:l}),u();i._last_mouse_down=s}i._last_mouse=s}else if(p===Ot){const s={x:n[1],y:n[2]},p=0;i._last_mouse_down=null;const g=fl(e,t);if(g>=0){const n=Il(e,g);zl(e,n,0),c(n);const i=gl(e,g),s=pl(e,g);let h=!1;if(ll(e)===ee.REAL){for(const t of n)if(ul(e,t)){h=!0;break}}else h=!0;if(h&&Wn.pointDistance(s,i){const o=qn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const l=cl(e,t),o=cl(e,n);return!(!l||l!==o)})(e,t,n))return!1;const a=gl(e,t),i=Wn.pointAdd(gl(e,n),{x:l[0]*o.tileSize,y:l[1]*o.tileSize});if(Wn.pointDistance(a,i){const l=qn[e].puzzle.tiles,o=cl(e,t),a=cl(e,n);let i;const s=[];o&&s.push(o),a&&s.push(a),o?i=o:a?i=a:(sl(e,{maxGroup:bl(e)+1}),r(),i=bl(e));if(rl(e,t,{group:i}),d(t),rl(e,n,{group:i}),d(n),s.length>0)for(const r of l){const t=ie.decodePiece(r);s.includes(t.group)&&(rl(e,t.idx,{group:i}),d(t.idx))}})(e,t,n),o=Il(e,t),((e,t)=>-1===Tl(e,t))(e,n))Pl(e,o);else{const t=((e,t)=>{let n=0;for(const l of t){const t=yl(e,l);t>n&&(n=t)}return n})(e,o);Cl(e,o,t)}return c(o),!0}return!1};let a=!1;for(const t of Il(e,g)){const l=xl(e,t);if(n(e,t,l[0],[0,1])||n(e,t,l[1],[-1,0])||n(e,t,l[2],[0,-1])||n(e,t,l[3],[1,0])){a=!0;break}}if(a&&nl(e)===Z.ANY){const n=Dl(e,t)+1;il(e,t,{d:p,ts:l,points:n}),u()}else il(e,t,{d:p,ts:l}),u();a&&ll(e)===ee.REAL&&al(e)===tl(e)&&(sl(e,{finished:l}),r()),a&&o&&o(t)}}else il(e,t,{d:p,ts:l}),u();i._last_mouse=s}else if(p===Ut){const o=n[1],a=n[2];il(e,t,{x:o,y:a,ts:l}),u(),i._last_mouse={x:o,y:a}}else if(p===Rt){const o=n[1],a=n[2];il(e,t,{x:o,y:a,ts:l}),u(),i._last_mouse={x:o,y:a}}else il(e,t,{ts:l}),u();return function(e,t,n){qn[e].evtInfos[t]=n}(e,t,i),s}};let Nl=-10,Ol=20,Bl=2,Ul=15;class Rl{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Nl+Math.random()*Ol,this.vy=-1*(Bl+Math.random()*Ul),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Bl=t/2,Ul=t-Bl;const n=1/4*this.canvas.width/(t/2);Nl=-n,Ol=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Rl(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Rl(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(!y[t]){const n=e.d?r:d;if(e.color){const l=e.d?c:u;y[t]=await createImageBitmap(Un.colorizedCanvas(n,l,e.color))}else y[t]=n}return y[t]},v=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,Wl=!0})),t}(o,Un.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};rn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await rn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let k=()=>0;const x=async()=>{if("play"===l){const l=await rn.connect(n,e,t),o=ie.decodeGame(l);Ml.setGame(o.id,o),k=()=>O()}else{if("replay"!==l)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=ie.decodeGame(t.game);Ml.setGame(n.id,n),w.lastRealTs=O(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,k=()=>w.lastGameTs}}Wl=!0};await x();const C=Ml.getPieceDrawOffset(e),A=Ml.getPieceDrawSize(e),S=Ml.getPuzzleWidth(e),T=Ml.getPuzzleHeight(e),P=Ml.getTableWidth(e),z=Ml.getTableHeight(e),I={x:(P-S)/2,y:(z-T)/2},D={w:S,h:T},E={w:A,h:A},_=await Qn.loadPuzzleBitmaps(Ml.getPuzzle(e)),N=new Gl(v,Ml.getRng(e));N.init();const B=v.getContext("2d");v.classList.add("loaded");const U=On();U.move(-(P-v.width)/2,-(z-v.height)/2);const R=function(e,t,n,l){let o=[],a=!0,i=!1,s=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const l=n.viewportToWorld({x:e,y:t});return[l.x,l.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("Shift"===t.key?p=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?r=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?d=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?u=e:"e"===t.key&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Nt,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Ot,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([Bt,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Ut:Rt;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&(" "===e.key&&v([Ft]),"replay"===l&&("I"!==e.key&&"i"!==e.key||v([Wt]),"O"!==e.key&&"o"!==e.key||v([Ht]),"P"!==e.key&&"p"!==e.key||v([jt])),"F"!==e.key&&"f"!==e.key||(Ll=!Ll,Wl=!0),"G"!==e.key&&"g"!==e.key||(jl=!jl,Wl=!0),"M"!==e.key&&"m"!==e.key||v([Lt]))}));const v=e=>{o.push(e)};return{addEvent:v,consumeAll:()=>{if(0===o.length)return[];const e=o.slice();return o=[],e},createKeyEvents:()=>{const e=(i?1:0)-(s?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const l=(p?24:12)*Math.sqrt(n.getCurrentZoom()),o=n.viewportDimToWorld({w:e*l,h:t*l});v([Mt,o.w,o.h]),f&&(f[0]-=o.w,f[1]-=o.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Ut,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([Rt,...e])}},setHotkeys:e=>{a=e}}}(v,window,U,l),V=Ml.getImageUrl(e),G=()=>{const t=Ml.getStartTs(e),n=Ml.getFinishTs(e),l=k();a.setFinished(!!n),a.setDuration((n||l)-t)};G(),a.setPiecesDone(Ml.getFinishedPiecesCount(e)),a.setPiecesTotal(Ml.getPieceCount(e));const $=k();a.setActivePlayers(Ml.getActivePlayers(e,$)),a.setIdlePlayers(Ml.getIdlePlayers(e,$));const F=!!Ml.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},H=()=>Ml.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Y=()=>Ml.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let Q="",q="",Z=!1;const K=e=>{Z=e;const[t,n]=e?[Q,"grab"]:[q,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},X=e=>{Q=Un.colorizedCanvas(r,c,e).toDataURL(),q=Un.colorizedCanvas(d,u,e).toDataURL(),K(Z)};X(Y());const J=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ee=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,J())},ne=()=>{w.paused=!w.paused,J()},le=[];let oe;let ae;if("play"===l?le.push(setInterval((()=>{G()}),1e3)):"replay"===l&&J(),"play"===l)rn.onServerChange((n=>{n[0],n[1],n[2];const l=n[3];for(const[o,a]of l)switch(o){case qt:{const n=ie.decodePlayer(a);n.id!==t&&(Ml.setPlayer(e,n.id,n),Wl=!0)}break;case Qt:{const t=ie.decodePiece(a);Ml.setPiece(e,t.idx,t),Wl=!0}break;case Yt:Ml.setPuzzleData(e,a),Wl=!0}L=!!Ml.getFinishTs(e)}));else if("replay"===l){const t=(t,n)=>{const l=t;if(l[0]===Dt){const t=l[1];return Ml.addPlayer(e,t,n),!0}if(l[0]===Et){const t=Ml.getPlayerIdByIndex(e,l[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Ml.addPlayer(e,t,n),!0}if(l[0]===_t){const t=Ml.getPlayerIdByIndex(e,l[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const o=l[2];return Ml.handleInput(e,t,o,n),!0}return!1};let n=w.lastGameTs;const l=async()=>{w.logPointer+1>=w.log.length&&await b(e);const o=O();if(w.paused)return w.lastRealTs=o,void(oe=setTimeout(l,50));const a=(o-w.lastRealTs)*w.speeds[w.speedIdx];let i=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const l=w.log[w.logPointer],o=n+l[l.length-1],a=w.log[e],s=a[a.length-1],r=o+s;if(r>i){w.skipNonActionPhases&&i+500*M{let t=!1;const n=e.fps||60,l=e.slow||1,o=e.update,a=e.render,i=window.requestAnimationFrame,s=1/n,r=l*s;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,o(s);a(c/l),u=d,t||i(p)};return i(p),{stop:()=>{t=!0}}})({update:()=>{R.createKeyEvents();for(const n of R.consumeAll())if("play"===l){const l=n[0];if(l===Mt){const e=n[1],t=n[2],l=U.worldDimToViewport({w:e,h:t});Wl=!0,U.move(l.w,l.h)}else if(l===Bt){if(se&&!Ml.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),l=Math.round(t.x-se.x),o=Math.round(t.y-se.y);Wl=!0,U.move(l,o),se=t}}else if(l===Gt)X(n[1]);else if(l===Nt){const e={x:n[1],y:n[2]};se=U.worldToViewport(e),K(!0)}else if(l===Ot)se=null,K(!1);else if(l===Ut){const e={x:n[1],y:n[2]};Wl=!0,U.zoom("in",U.worldToViewport(e))}else if(l===Rt){const e={x:n[1],y:n[2]};Wl=!0,U.zoom("out",U.worldToViewport(e))}else l===Ft?a.togglePreview():l===Lt&&a.toggleSoundsEnabled();const o=k();Ml.handleInput(e,t,n,o,(e=>{W()&&s.play()})).length>0&&(Wl=!0),rn.sendClientEvent(n)}else if("replay"===l){const e=n[0];if(e===jt)ne();else if(e===Ht)te();else if(e===Wt)ee();else if(e===Mt){const e=n[1],t=n[2];Wl=!0,U.move(e,t)}else if(e===Bt){if(se){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),l=Math.round(t.x-se.x),o=Math.round(t.y-se.y);Wl=!0,U.move(l,o),se=t}}else if(e===Nt){const e={x:n[1],y:n[2]};se=U.worldToViewport(e)}else if(e===Ot)se=null;else if(e===Ut){const e={x:n[1],y:n[2]};Wl=!0,U.zoom("in",U.worldToViewport(e))}else if(e===Rt){const e={x:n[1],y:n[2]};Wl=!0,U.zoom("out",U.worldToViewport(e))}else e===Ft&&a.togglePreview()}L=!!Ml.getFinishTs(e),j()&&(N.update(),Wl=!0)},render:async()=>{if(!Wl)return;const n=k();let o,i,s;window.DEBUG&&$n(0),B.fillStyle=H(),B.fillRect(0,0,v.width,v.height),window.DEBUG&&Fn("clear done"),o=U.worldToViewportRaw(I),i=U.worldDimToViewportRaw(D),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(o.x,o.y,i.w,i.h),window.DEBUG&&Fn("board done");const r=Ml.getPiecesSortedByZIndex(e);window.DEBUG&&Fn("get tiles done"),i=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?Ll:jl)&&(s=_[e.idx],o=U.worldToViewportRaw({x:C+e.pos.x,y:C+e.pos.y}),B.drawImage(s,0,0,s.width,s.height,o.x,o.y,i.w,i.h));window.DEBUG&&Fn("tiles done");const d=[];for(const a of Ml.getActivePlayers(e,n))c=a,("replay"===l||c.id!==t)&&(s=await f(a),o=U.worldToViewport(a),B.drawImage(s,o.x-g,o.y-m),d.push([`${a.name} (${a.points})`,o.x,o.y+h]));var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,l]of d)B.fillText(e,t,l);window.DEBUG&&Fn("players done"),a.setActivePlayers(Ml.getActivePlayers(e,n)),a.setIdlePlayers(Ml.getIdlePlayers(e,n)),a.setPiecesDone(Ml.getFinishedPiecesCount(e)),window.DEBUG&&Fn("HUD done"),j()&&N.render(),Wl=!1}}),{setHotkeys:e=>{R.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),R.addEvent([Vt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),R.addEvent([Gt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),R.addEvent([$t,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},replayOnSpeedUp:ee,replayOnSpeedDown:te,replayOnPauseToggle:ne,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:V,player:{background:H(),color:Y(),name:Ml.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:W()},disconnect:rn.disconnect,connect:x,unload:()=>{le.forEach((e=>{clearInterval(e)})),oe&&clearTimeout(oe),ae&&ae.stop()}}}var Yl=e({name:"game",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:At,ConnectionOverlay:dn,HelpOverlay:mn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Hl(`${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)},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 Ql={id:"game"},ql={class:"menu"},Zl={class:"tabs"},Kl=s("🧩 Puzzles");Yl.render=function(e,o,s,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Ql,[p(n(u,{onBgclick:o[1]||(o[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":o[2]||(o[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(g,{onBgclick:o[3]||(o[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(h,{onBgclick:o[4]||(o[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",ql,[n("div",Zl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[Kl])),_:1}),n("div",{class:"opener",onClick:o[5]||(o[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:o[6]||(o[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:o[7]||(o[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var Xl=e({name:"replay",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:At,HelpOverlay:mn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Hl(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 Jl={id:"replay"},eo=s("Skip no action phases: "),to={class:"menu"},no={class:"tabs"},lo=s("🧩 Puzzles");Xl.render=function(e,o,s,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Jl,[p(n(g,{onBgclick:o[1]||(o[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":o[2]||(o[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:o[3]||(o[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(m,{onBgclick:o[4]||(o[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:l((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[eo,p(n("input",{type:"checkbox","onUpdate:modelValue":o[5]||(o[5]=t=>e.skipNoAction=t),onChange:o[6]||(o[6]=t=>e.g.replayOnSkipToggle())},null,544),[[k,e.skipNoAction]])])]),n("button",{class:"btn",onClick:o[7]||(o[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:o[8]||(o[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:o[9]||(o[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",to,[n("div",no,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[lo])),_:1}),n("div",{class:"opener",onClick:o[10]||(o[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:o[11]||(o[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:o[12]||(o[12]=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=C({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:nt},{name:"game",path:"/g/:id",component:Yl},{name:"replay",path:"/replay/:id",component:Xl}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const l=S(T);l.config.globalProperties.$config=t,l.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=ie.uniqId(),localStorage.setItem("ID",e)),e}(),l.use(n),l.mount("#app")})(); diff --git a/build/public/assets/index.b2021c0c.js b/build/public/assets/index.b2021c0c.js deleted file mode 100644 index 51ea48e..0000000 --- a/build/public/assets/index.b2021c0c.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as n,w as o,b as l,r as a,o as i,e as s,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as k,s as x,u as C,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const P={id:"app"},z={key:0,class:"nav"},I=s("Index"),D=s("New game");T.render=function(e,s,r,d,c,u){const p=a("router-link"),g=a("router-view");return i(),t("div",P,[e.showNav?(i(),t("ul",z,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[D])),_:1})])])):l("",!0),n(g)])};const E=864e5,_=e=>{const t=Math.floor(e/E);e%=E;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 M=1,N=1e3,O=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>_(t-e),U=_,R=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||O();return`${n} ${B(o,l)}`}}});const V={class:"game-info-text"},G=n("br",null,null,-1),$=n("br",null,null,-1),F=n("br",null,null,-1),L=s(" ↪️ Watch replay ");R.render=function(e,d,c,u,p,g){const h=a("router-link");return i(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",V,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),G,s(" 👥 "+r(e.game.players),1),$,s(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(i(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[L])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:R},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 W=n("h1",null,"Running games",-1),H=n("h1",null,"Finished games",-1);j.render=function(e,o,l,s,r,u){const p=a("game-teaser");return i(),t("div",null,[W,(i(!0),t(d,null,c(e.gamesRunning,((e,o)=>(i(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128)),H,(i(!0),t(d,null,c(e.gamesFinished,((e,o)=>(i(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128))])};var Y=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});Y.render=function(e,o,l,a,s,r){return i(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Q,q,Z,K,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:Y},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,s,r){const u=a("image-teaser");return i(),t("div",null,[(i(!0),t(d,null,c(e.images,((n,o)=>(i(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(q=Q||(Q={}))[q.Flat=0]="Flat",q[q.Out=1]="Out",q[q.In=-1]="In",(K=Z||(Z={}))[K.FINAL=0]="FINAL",K[K.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),i=le(o.getSeconds(),"00");console[t](`${l}:${a}:${i}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var ie={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Z.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const se={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};se.render=function(e,n,o,l,a,s){return i(),t("div",{style:s.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.key&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-39ed99c7");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,s,u,m)=>(i(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(i(),t("div",ce,[n("ul",null,[(i(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(i(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(i(!0),t(d,null,c(e.values,((n,o)=>(i(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-39ed99c7";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he={key:0,class:"has-image"},me={key:1},ye={class:"upload"},fe=n("span",{class:"btn"},"Upload File",-1),ve={class:"area-settings"},we=n("td",null,[n("label",null,"Title")],-1),be=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ke=n("td",null,[n("label",null,"Tags")],-1),xe={class:"area-buttons"},Ce=s("🧩 Post to gallery "),Ae=n("br",null,null,-1),Se=s(" + set up game");ge.render=function(e,o,l,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[e.previewUrl?(i(),t("div",he,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(i(),t("div",me,[n("label",ye,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),fe])]))],34),n("div",ve,[n("table",null,[n("tr",null,[we,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),be,n("tr",null,[ke,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",xe,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ce,Ae,Se],8,["disabled"])])])])};var Te=e({name:"edit-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Pe={class:"area-image"},ze={class:"has-image"},Ie={class:"area-settings"},De=n("td",null,[n("label",null,"Title")],-1),Ee=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),_e=n("td",null,[n("label",null,"Tags")],-1),Me={class:"area-buttons"};Te.render=function(e,o,l,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Pe,[n("div",ze,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ie,[n("table",null,[n("tr",null,[De,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ee,n("tr",null,[_e,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Me,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Ne=e({name:"new-game-dialog",components:{ResponsiveImage:se},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Z.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Oe={class:"area-image"},Be={class:"has-image"},Ue={key:0,class:"image-title"},Re={class:"area-settings"},Ve=n("td",null,[n("label",null,"Pieces")],-1),Ge=n("td",null,[n("label",null,"Scoring: ")],-1),$e=s(" Any (Score when pieces are connected to each other or on final location)"),Fe=n("br",null,null,-1),Le=s(" Final (Score when pieces are put to their final location)"),je=n("td",null,[n("label",null,"Shapes: ")],-1),We=s(" Normal"),He=n("br",null,null,-1),Ye=s(" Any (flat pieces can occur anywhere)"),Qe=n("br",null,null,-1),qe=s(" Flat (all pieces flat on all sides)"),Ze=n("td",null,[n("label",null,"Snapping: ")],-1),Ke=s(" Normal (pieces snap to final destination and to each other)"),Xe=n("br",null,null,-1),Je=s(" Real (pieces snap only to corners, already snapped pieces and to each other)"),et={class:"area-buttons"};Ne.render=function(e,o,s,d,c,h){const m=a("responsive-image");return i(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Oe,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(i(),t("div",Ue,'"'+r(e.image.title)+'"',1)):l("",!0)]),n("div",Re,[n("table",null,[n("tr",null,[Ve,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[Ge,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),$e]),Fe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),Le])])]),n("tr",null,[je,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),We]),He,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ye]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),qe])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Ke]),Xe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),Je])])])])]),n("div",et,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var tt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Te,NewGameDialog:Ne},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${ie.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const nt={class:"upload-image-teaser"},ot=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),lt={key:0},at=s(" Tags: "),it=s(" Sort by: "),st=n("option",{value:"date_desc"},"Newest first",-1),rt=n("option",{value:"date_asc"},"Oldest first",-1),dt=n("option",{value:"alpha_asc"},"A-Z",-1),ct=n("option",{value:"alpha_desc"},"Z-A",-1);tt.render=function(e,o,s,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return i(),t("div",null,[n("div",nt,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),ot]),n("div",null,[e.tags.length>0?(i(),t("label",lt,[at,(i(!0),t(d,null,c(e.relevantTags,((n,o)=>(i(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[it,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[st,rt,dt,ct],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(i(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(i(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(i(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ut=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 pt={class:"scores"},gt=n("div",null,"Scores",-1),ht=n("td",null,"⚡",-1),mt=n("td",null,"💤",-1);ut.render=function(e,o,l,a,s,u){return i(),t("div",pt,[gt,n("table",null,[(i(!0),t(d,null,c(e.actives,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[ht,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(i(!0),t(d,null,c(e.idles,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[mt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var yt=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 U(this.duration)}}});const ft={class:"timer"};yt.render=function(e,o,l,a,s,d){return i(),t("div",ft,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var vt=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 wt=n("td",null,[n("label",null,"Background: ")],-1),bt=n("td",null,[n("label",null,"Color: ")],-1),kt=n("td",null,[n("label",null,"Name: ")],-1),xt=n("td",null,[n("label",null,"Sounds: ")],-1);vt.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:o[6]||(o[6]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[5]||(o[5]=u((()=>{}),["stop"]))},[n("tr",null,[wt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[bt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[kt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[xt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[k,e.modelValue.soundsEnabled]])])])])])};var Ct=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const At={class:"preview"};Ct.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",At,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var St=1,Tt=4,Pt=2,zt=3,It=2,Dt=4,Et=3,_t=9,Mt=1,Nt=2,Ot=3,Bt=4,Ut=5,Rt=6,Vt=7,Gt=8,$t=10,Ft=11,Lt=12,jt=13,Wt=14,Ht=1,Yt=2,Qt=3;const qt=ae("Communication.js");let Zt,Kt=[],Xt=e=>{Kt.push(e)},Jt=[],en=e=>{Jt.push(e)};let tn=0;const nn=e=>{tn!==e&&(tn=e,en(e))};function on(e){if(2===tn)try{Zt.send(JSON.stringify(e))}catch(t){qt.info("unable to send message.. maybe because ws is invalid?")}}let ln,an;var sn={connect:function(e,t,n){return ln=0,an={},nn(3),new Promise((o=>{Zt=new WebSocket(e,n+"|"+t),Zt.onopen=()=>{nn(2),on([zt])},Zt.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Tt){const e=t[1];o(e)}else{if(l!==St)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&an[o])return void delete an[o];Xt(t)}}},Zt.onerror=()=>{throw nn(1),"[ 2021-05-15 onerror ]"},Zt.onclose=e=>{4e3===e.code||1001===e.code?nn(4):nn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${ie.asQueryArgs(n)}`);return await o.json()},disconnect:function(){Zt&&Zt.close(4e3),ln=0,an={}},sendClientEvent:function(e){ln++,an[ln]=e,on([Pt,ln,an[ln]])},onServerChange:function(e){Xt=e;for(const t of Kt)Xt(t);Kt=[]},onConnectionStateChange:function(e){en=e;for(const t of Jt)en(t);Jt=[]},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},rn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===sn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===sn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const dn={key:0,class:"overlay connection-lost"},cn={key:0,class:"overlay-content"},un=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),pn={key:1,class:"overlay-content"},gn=n("div",null,"Connecting...",-1);rn.render=function(e,o,a,s,r,d){return e.show?(i(),t("div",dn,[e.lostConnection?(i(),t("div",cn,[un,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(i(),t("div",pn,[gn])):l("",!0)])):l("",!0)};var hn=e({name:"help-overlay",emits:{bgclick:null}});const mn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),yn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),fn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),vn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),wn=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),bn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),kn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),xn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Cn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),An=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Sn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),Tn=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),Pn=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),zn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);hn.render=function(e,o,l,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:o[2]||(o[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:o[1]||(o[1]=u((()=>{}),["stop"]))},[mn,yn,fn,vn,wn,bn,kn,xn,Cn,An,Sn,Tn,Pn,zn])])};var In=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Dn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),En=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),_n=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Mn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Nn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),i=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function On(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Bn={createCanvas:On,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=On(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=On(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(),o}};const Un=ae("Debug.js");let Rn=0,Vn=0;var Gn=e=>{Rn=performance.now(),Vn=e},$n=e=>{const t=performance.now(),n=t-Rn;n>Vn&&Un.log(e+": "+n),Rn=t};function Fn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function Ln(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var jn={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:Fn,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:Ln,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 Fn(Ln(e),Ln(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const Wn=ae("PuzzleGraphics.js");function Hn(e,t){const n=ie.coordByPieceIdx(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 Bn.loadImageToBitmap(e.info.imageUrl),n=await Bn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Wn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,i=o/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=jn.pointAdd(a,{x:o,y:0}),c=jn.pointAdd(r,{x:0,y:o}),u=jn.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;oie.decodePiece(Qn[e].puzzle.tiles[t]),co=(e,t)=>ro(e,t).group,uo=(e,t)=>{const n=Qn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},po=(e,t)=>{const n=Qn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Qn[e].puzzle.info,o=ie.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return jn.pointAdd(o,l)},go=(e,t)=>ro(e,t).pos,ho=e=>{const t=Eo(e),n=_o(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}},mo=(e,t)=>{const n=wo(e),o=ro(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},yo=(e,t)=>ro(e,t).z,fo=(e,t)=>{for(const n of Qn[e].puzzle.tiles){const e=ie.decodePiece(n);if(e.owner===t)return e.idx}return-1},vo=e=>Qn[e].puzzle.info.tileDrawSize,wo=e=>Qn[e].puzzle.info.tileSize,bo=e=>Qn[e].puzzle.data.maxGroup,ko=e=>Qn[e].puzzle.data.maxZ;function xo(e,t){const n=Qn[e].puzzle.info,o=ie.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Co=(e,t,n)=>{for(const o of t)so(e,o,{z:n})},Ao=(e,t,n)=>{const o=go(e,t);so(e,t,{pos:jn.pointAdd(o,n)})},So=(e,t,n)=>{const o=vo(e),l=ho(e),a=n;for(const i of t){const t=ro(e,i);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const i of t)Ao(e,i,a)},To=(e,t)=>ro(e,t).owner,Po=(e,t)=>{for(const n of t)so(e,n,{owner:-1,z:1})},zo=(e,t,n)=>{for(const o of t)so(e,o,{owner:n})};function Io(e,t){const n=Qn[e].puzzle.tiles,o=ie.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=ie.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const Do=(e,t)=>{const n=Zn(e,t);return n?n.points:0},Eo=e=>Qn[e].puzzle.info.table.width,_o=e=>Qn[e].puzzle.info.table.height;var Mo={setGame:function(e,t){Qn[e]=t},exists:function(e){return!!Qn[e]||!1},playerExists:Xn,getActivePlayers:function(e,t){const n=t-30*N;return Jn(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*N;return Jn(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Xn(e,t)?ao(e,t,{ts:n}):Kn(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:lo,getPieceCount:eo,getImageUrl:function(e){return Qn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Qn[e].puzzle.info.imageUrl=t},get:function(e){return Qn[e]||null},getAllGames:function(){return Object.values(Qn).sort(((e,t)=>oo(e.id)===oo(t.id)?t.puzzle.data.started-e.puzzle.data.started:oo(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Zn(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Zn(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Zn(e,t);return n?n.name:null},getPlayerIndexById:qn,getPlayerIdByIndex:function(e,t){return Qn[e].players.length>t?ie.decodePlayer(Qn[e].players[t]).id:null},changePlayer:ao,setPlayer:Kn,setPiece:function(e,t,n){Qn[e].puzzle.tiles[t]=ie.encodePiece(n)},setPuzzleData:function(e,t){Qn[e].puzzle.data=t},getTableWidth:Eo,getTableHeight:_o,getPuzzle:e=>Qn[e].puzzle,getRng:e=>Qn[e].rng.obj,getPuzzleWidth:e=>Qn[e].puzzle.info.width,getPuzzleHeight:e=>Qn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Qn[e].puzzle.tiles.map(ie.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=fo(e,t);return n<0?null:Qn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Qn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:vo,getFinalPiecePos:po,getStartTs:e=>Qn[e].puzzle.data.started,getFinishTs:e=>Qn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Qn[e].puzzle,i=function(e,t){return t in Qn[e].evtInfos?Qn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],r=()=>{s.push([Ht,a.data])},d=t=>{s.push([Yt,ie.encodePiece(ro(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=Zn(e,t);n&&s.push([Qt,ie.encodePlayer(n)])},p=n[0];if(p===Rt){const l=n[1];ao(e,t,{bgcolor:l,ts:o}),u()}else if(p===Vt){const l=n[1];ao(e,t,{color:l,ts:o}),u()}else if(p===Gt){const l=`${n[1]}`.substr(0,16);ao(e,t,{name:l,ts:o}),u()}else if(p===_t){const l=n[1],a=n[2],i=Zn(e,t);if(i){const n=i.x-l,s=i.y-a;ao(e,t,{ts:o,x:n,y:s}),u()}}else if(p===Mt){const l={x:n[1],y:n[2]};ao(e,t,{d:1,ts:o}),u(),i._last_mouse_down=l;const a=((e,t)=>{const n=Qn[e].puzzle.info,o=Qn[e].puzzle.tiles;let l=-1,a=-1;for(let i=0;il)&&(l=e.z,a=i)}return a})(e,l);if(a>=0){const n=ko(e)+1;io(e,{maxZ:n}),r();const o=Io(e,a);Co(e,o,ko(e)),zo(e,o,t),c(o)}i._last_mouse=l}else if(p===Ot){const l=n[1],a=n[2],s={x:l,y:a};if(null===i._last_mouse_down)ao(e,t,{x:l,y:a,ts:o}),u();else{const n=fo(e,t);if(n>=0){ao(e,t,{x:l,y:a,ts:o}),u();const r=Io(e,n);let d=jn.pointInBounds(s,ho(e))&&jn.pointInBounds(i._last_mouse_down,ho(e));for(const t of r){const n=mo(e,t);if(jn.pointInBounds(s,n)){d=!0;break}}if(d){const t=l-i._last_mouse_down.x,n=a-i._last_mouse_down.y;So(e,r,{x:t,y:n}),c(r)}}else ao(e,t,{ts:o}),u();i._last_mouse_down=s}i._last_mouse=s}else if(p===Nt){const s={x:n[1],y:n[2]},p=0;i._last_mouse_down=null;const g=fo(e,t);if(g>=0){const n=Io(e,g);zo(e,n,0),c(n);const i=go(e,g),s=po(e,g);let h=!1;if(no(e)===ee.REAL){for(const t of n)if(uo(e,t)){h=!0;break}}else h=!0;if(h&&jn.pointDistance(s,i){const l=Qn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=co(e,t),l=co(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=go(e,t),i=jn.pointAdd(go(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(jn.pointDistance(a,i){const o=Qn[e].puzzle.tiles,l=co(e,t),a=co(e,n);let i;const s=[];l&&s.push(l),a&&s.push(a),l?i=l:a?i=a:(io(e,{maxGroup:bo(e)+1}),r(),i=bo(e));if(so(e,t,{group:i}),d(t),so(e,n,{group:i}),d(n),s.length>0)for(const r of o){const t=ie.decodePiece(r);s.includes(t.group)&&(so(e,t.idx,{group:i}),d(t.idx))}})(e,t,n),l=Io(e,t),((e,t)=>-1===To(e,t))(e,n))Po(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=yo(e,o);t>n&&(n=t)}return n})(e,l);Co(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Io(e,g)){const o=xo(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])){a=!0;break}}if(a&&to(e)===Z.ANY){const n=Do(e,t)+1;ao(e,t,{d:p,ts:o,points:n}),u()}else ao(e,t,{d:p,ts:o}),u();a&&no(e)===ee.REAL&&lo(e)===eo(e)&&(io(e,{finished:o}),r()),a&&l&&l(t)}}else ao(e,t,{d:p,ts:o}),u();i._last_mouse=s}else if(p===Bt){const l=n[1],a=n[2];ao(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else if(p===Ut){const l=n[1],a=n[2];ao(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else ao(e,t,{ts:o}),u();return function(e,t,n){Qn[e].evtInfos[t]=n}(e,t,i),s}};let No=-10,Oo=20,Bo=2,Uo=15;class Ro{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=No+Math.random()*Oo,this.vy=-1*(Bo+Math.random()*Uo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Bo=t/2,Uo=t-Bo;const n=1/4*this.canvas.width/(t/2);No=-n,Oo=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Ro(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Ro(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(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Bn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Wo=!0})),t}(l,Bn.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};sn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await sn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let k=()=>0;const x=async()=>{if("play"===o){const o=await sn.connect(n,e,t),l=ie.decodeGame(o);Mo.setGame(l.id,l),k=()=>O()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=ie.decodeGame(t.game);Mo.setGame(n.id,n),w.lastRealTs=O(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,k=()=>w.lastGameTs}}Wo=!0};await x();const C=Mo.getPieceDrawOffset(e),A=Mo.getPieceDrawSize(e),S=Mo.getPuzzleWidth(e),T=Mo.getPuzzleHeight(e),P=Mo.getTableWidth(e),z=Mo.getTableHeight(e),I={x:(P-S)/2,y:(z-T)/2},D={w:S,h:T},E={w:A,h:A},_=await Yn.loadPuzzleBitmaps(Mo.getPuzzle(e)),N=new Go(v,Mo.getRng(e));N.init();const B=v.getContext("2d");v.classList.add("loaded");const U=Nn();U.move(-(P-v.width)/2,-(z-v.height)/2);const R=function(e,t,n,o){let l=[],a=!0,i=!1,s=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("Shift"===t.key?p=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?r=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?d=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?u=e:"e"===t.key&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Mt,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Nt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([Ot,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Bt:Ut;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&(" "===e.key&&v([$t]),"replay"===o&&("I"!==e.key&&"i"!==e.key||v([jt]),"O"!==e.key&&"o"!==e.key||v([Wt]),"P"!==e.key&&"p"!==e.key||v([Lt])),"F"!==e.key&&"f"!==e.key||(Lo=!Lo,Wo=!0),"G"!==e.key&&"g"!==e.key||(jo=!jo,Wo=!0),"M"!==e.key&&"m"!==e.key||v([Ft]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(i?1:0)-(s?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});v([_t,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Bt,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([Ut,...e])}},setHotkeys:e=>{a=e}}}(v,window,U,o),V=Mo.getImageUrl(e),G=()=>{const t=Mo.getStartTs(e),n=Mo.getFinishTs(e),o=k();a.setFinished(!!n),a.setDuration((n||o)-t)};G(),a.setPiecesDone(Mo.getFinishedPiecesCount(e)),a.setPiecesTotal(Mo.getPieceCount(e));const $=k();a.setActivePlayers(Mo.getActivePlayers(e,$)),a.setIdlePlayers(Mo.getIdlePlayers(e,$));const F=!!Mo.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},H=()=>Mo.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Y=()=>Mo.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let Q="",q="",Z=!1;const K=e=>{Z=e;const[t,n]=e?[Q,"grab"]:[q,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},X=e=>{Q=Bn.colorizedCanvas(r,c,e).toDataURL(),q=Bn.colorizedCanvas(d,u,e).toDataURL(),K(Z)};X(Y());const J=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ee=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,J())},ne=()=>{w.paused=!w.paused,J()},oe=[];let le;let ae;if("play"===o?oe.push(setInterval((()=>{G()}),1e3)):"replay"===o&&J(),"play"===o)sn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Qt:{const n=ie.decodePlayer(a);n.id!==t&&(Mo.setPlayer(e,n.id,n),Wo=!0)}break;case Yt:{const t=ie.decodePiece(a);Mo.setPiece(e,t.idx,t),Wo=!0}break;case Ht:Mo.setPuzzleData(e,a),Wo=!0}L=!!Mo.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===It){const t=o[1];return Mo.addPlayer(e,t,n),!0}if(o[0]===Dt){const t=Mo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Mo.addPlayer(e,t,n),!0}if(o[0]===Et){const t=Mo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Mo.handleInput(e,t,l,n),!0}return!1};let n=w.lastGameTs;const o=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=O();if(w.paused)return w.lastRealTs=l,void(le=setTimeout(o,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let i=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const o=w.log[w.logPointer],l=n+o[o.length-1],a=w.log[e],s=a[a.length-1],r=l+s;if(r>i){w.skipNonActionPhases&&i+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,i=window.requestAnimationFrame,s=1/n,r=o*s;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(s);a(c/o),u=d,t||i(p)};return i(p),{stop:()=>{t=!0}}})({update:()=>{R.createKeyEvents();for(const n of R.consumeAll())if("play"===o){const o=n[0];if(o===_t){const e=n[1],t=n[2],o=U.worldDimToViewport({w:e,h:t});Wo=!0,U.move(o.w,o.h)}else if(o===Ot){if(se&&!Mo.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-se.x),l=Math.round(t.y-se.y);Wo=!0,U.move(o,l),se=t}}else if(o===Vt)X(n[1]);else if(o===Mt){const e={x:n[1],y:n[2]};se=U.worldToViewport(e),K(!0)}else if(o===Nt)se=null,K(!1);else if(o===Bt){const e={x:n[1],y:n[2]};Wo=!0,U.zoom("in",U.worldToViewport(e))}else if(o===Ut){const e={x:n[1],y:n[2]};Wo=!0,U.zoom("out",U.worldToViewport(e))}else o===$t?a.togglePreview():o===Ft&&a.toggleSoundsEnabled();const l=k();Mo.handleInput(e,t,n,l,(e=>{W()&&s.play()})).length>0&&(Wo=!0),sn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===Lt)ne();else if(e===Wt)te();else if(e===jt)ee();else if(e===_t){const e=n[1],t=n[2];Wo=!0,U.move(e,t)}else if(e===Ot){if(se){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-se.x),l=Math.round(t.y-se.y);Wo=!0,U.move(o,l),se=t}}else if(e===Mt){const e={x:n[1],y:n[2]};se=U.worldToViewport(e)}else if(e===Nt)se=null;else if(e===Bt){const e={x:n[1],y:n[2]};Wo=!0,U.zoom("in",U.worldToViewport(e))}else if(e===Ut){const e={x:n[1],y:n[2]};Wo=!0,U.zoom("out",U.worldToViewport(e))}else e===$t&&a.togglePreview()}L=!!Mo.getFinishTs(e),j()&&(N.update(),Wo=!0)},render:async()=>{if(!Wo)return;const n=k();let l,i,s;window.DEBUG&&Gn(0),B.fillStyle=H(),B.fillRect(0,0,v.width,v.height),window.DEBUG&&$n("clear done"),l=U.worldToViewportRaw(I),i=U.worldDimToViewportRaw(D),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(l.x,l.y,i.w,i.h),window.DEBUG&&$n("board done");const r=Mo.getPiecesSortedByZIndex(e);window.DEBUG&&$n("get tiles done"),i=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?Lo:jo)&&(s=_[e.idx],l=U.worldToViewportRaw({x:C+e.pos.x,y:C+e.pos.y}),B.drawImage(s,0,0,s.width,s.height,l.x,l.y,i.w,i.h));window.DEBUG&&$n("tiles done");const d=[];for(const a of Mo.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(s=await f(a),l=U.worldToViewport(a),B.drawImage(s,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,o]of d)B.fillText(e,t,o);window.DEBUG&&$n("players done"),a.setActivePlayers(Mo.getActivePlayers(e,n)),a.setIdlePlayers(Mo.getIdlePlayers(e,n)),a.setPiecesDone(Mo.getFinishedPiecesCount(e)),window.DEBUG&&$n("HUD done"),j()&&N.render(),Wo=!1}}),{setHotkeys:e=>{R.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),R.addEvent([Rt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),R.addEvent([Vt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),R.addEvent([Gt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},replayOnSpeedUp:ee,replayOnSpeedDown:te,replayOnPauseToggle:ne,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:V,player:{background:H(),color:Y(),name:Mo.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:W()},disconnect:sn.disconnect,connect:x,unload:()=>{oe.forEach((e=>{clearInterval(e)})),le&&clearTimeout(le),ae&&ae.stop()}}}var Yo=e({name:"game",components:{PuzzleStatus:yt,Scores:ut,SettingsOverlay:vt,PreviewOverlay:Ct,ConnectionOverlay:rn,HelpOverlay:hn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Ho(`${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)},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 Qo={id:"game"},qo={class:"menu"},Zo={class:"tabs"},Ko=s("🧩 Puzzles");Yo.render=function(e,l,s,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Qo,[p(n(u,{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"]),[[x,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",qo,[n("div",Zo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Ko])),_: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 Xo=e({name:"replay",components:{PuzzleStatus:yt,Scores:ut,SettingsOverlay:vt,PreviewOverlay:Ct,HelpOverlay:hn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Ho(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 Jo={id:"replay"},el=s("Skip no action phases: "),tl={class:"menu"},nl={class:"tabs"},ol=s("🧩 Puzzles");Xo.render=function(e,l,s,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Jo,[p(n(g,{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"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[el,p(n("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[k,e.skipNoAction]])])]),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",tl,[n("div",nl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[ol])),_:1}),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:l[12]||(l[12]=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=C({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:tt},{name:"game",path:"/g/:id",component:Yo},{name:"replay",path:"/replay/:id",component:Xo}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=S(T);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=ie.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 a838b0d..c5ade0f 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,9 +4,9 @@ 🧩 jigsaw.hyottoko.club - + - +
diff --git a/src/frontend/components/NewImageDialog.vue b/src/frontend/components/NewImageDialog.vue index da3d7b0..976b7b5 100644 --- a/src/frontend/components/NewImageDialog.vue +++ b/src/frontend/components/NewImageDialog.vue @@ -14,6 +14,7 @@ gallery", if possible! @dragover="onDragover" @dragleave="onDragleave"> +
X @@ -212,9 +213,6 @@ export default defineComponent({ .new-image-dialog .area-image.droppable { border: dashed 6px; } -.area-image * { - pointer-events: none; -} .new-image-dialog .area-image .has-image { position: relative; width: 100%; @@ -256,4 +254,16 @@ export default defineComponent({ top: 50%; transform: translate(-50%,-50%); } +.area-image .drop-target { + display: none; +} +.area-image.droppable .drop-target { + pointer-events: none; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 3; +} From 0882d3befde362146254cfbcf553968c99e5426a Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sun, 6 Jun 2021 17:05:10 +0200 Subject: [PATCH 10/45] add volume setting --- build/public/assets/index.6b00de5a.js | 1 + build/public/assets/index.99efb0e9.js | 1 - ...{index.84d14088.css => index.a9024809.css} | 2 +- build/public/index.html | 4 +-- src/frontend/components/SettingsOverlay.vue | 36 ++++++++++++++++++- src/frontend/game.ts | 23 +++++++++++- src/frontend/views/Game.vue | 5 +++ src/frontend/views/Replay.vue | 5 +++ 8 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 build/public/assets/index.6b00de5a.js delete mode 100644 build/public/assets/index.99efb0e9.js rename build/public/assets/{index.84d14088.css => index.a9024809.css} (97%) diff --git a/build/public/assets/index.6b00de5a.js b/build/public/assets/index.6b00de5a.js new file mode 100644 index 0000000..9cc0cea --- /dev/null +++ b/build/public/assets/index.6b00de5a.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as k,s as x,u as C,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const P={id:"app"},I={key:0,class:"nav"},z=i("Index"),D=i("New game");T.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",P,[e.showNav?(s(),t("ul",I,[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((()=>[D])),_:1})])])):l("",!0),n(g)])};const _=864e5,E=e=>{const t=Math.floor(e/_);e%=_;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 M=1,V=1e3,N=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>E(t-e),B=E,U=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||N();return`${n} ${O(o,l)}`}}});const R={class:"game-info-text"},$=n("br",null,null,-1),G=n("br",null,null,-1),F=n("br",null,null,-1),L=i(" ↪️ Watch replay ");U.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),$,i(" 👥 "+r(e.game.players),1),G,i(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[L])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:U},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 W=n("h1",null,"Running games",-1),H=n("h1",null,"Finished games",-1);j.render=function(e,o,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(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)),H,(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 Y=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});Y.render=function(e,o,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var q,Q,Z,K,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:Y},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(Q=q||(q={}))[Q.Flat=0]="Flat",Q[Q.Out=1]="Out",Q[Q.In=-1]="In",(K=Z||(Z={}))[K.FINAL=0]="FINAL",K[K.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),s=le(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var se={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Z.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const ie={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};ie.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.key&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-39ed99c7");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ce,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-39ed99c7";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=n("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},ve=n("span",{class:"btn"},"Upload File",-1),we={class:"area-settings"},be=n("td",null,[n("label",null,"Title")],-1),ke=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),xe=n("td",null,[n("label",null,"Tags")],-1),Ce={class:"area-buttons"},Ae=i("🧩 Post to gallery "),Se=n("br",null,null,-1),Te=i(" + set up game");ge.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(s(),t("div",me,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",ye,[n("label",fe,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ve])]))],34),n("div",we,[n("table",null,[n("tr",null,[be,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),ke,n("tr",null,[xe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Ce,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ae,Se,Te],8,["disabled"])])])])};var Pe=e({name:"edit-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Ie={class:"area-image"},ze={class:"has-image"},De={class:"area-settings"},_e=n("td",null,[n("label",null,"Title")],-1),Ee=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Me=n("td",null,[n("label",null,"Tags")],-1),Ve={class:"area-buttons"};Pe.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Ie,[n("div",ze,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",De,[n("table",null,[n("tr",null,[_e,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ee,n("tr",null,[Me,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Ve,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Ne=e({name:"new-game-dialog",components:{ResponsiveImage:ie},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Z.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Oe={class:"area-image"},Be={class:"has-image"},Ue={key:0,class:"image-title"},Re={class:"area-settings"},$e=n("td",null,[n("label",null,"Pieces")],-1),Ge=n("td",null,[n("label",null,"Scoring: ")],-1),Fe=i(" Any (Score when pieces are connected to each other or on final location)"),Le=n("br",null,null,-1),je=i(" Final (Score when pieces are put to their final location)"),We=n("td",null,[n("label",null,"Shapes: ")],-1),He=i(" Normal"),Ye=n("br",null,null,-1),qe=i(" Any (flat pieces can occur anywhere)"),Qe=n("br",null,null,-1),Ze=i(" Flat (all pieces flat on all sides)"),Ke=n("td",null,[n("label",null,"Snapping: ")],-1),Xe=i(" Normal (pieces snap to final destination and to each other)"),Je=n("br",null,null,-1),et=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),tt={class:"area-buttons"};Ne.render=function(e,o,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Oe,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(s(),t("div",Ue,'"'+r(e.image.title)+'"',1)):l("",!0)]),n("div",Re,[n("table",null,[n("tr",null,[$e,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[Ge,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Fe]),Le,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),je])])]),n("tr",null,[We,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),He]),Ye,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),qe]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),Ze])])]),n("tr",null,[Ke,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Xe]),Je,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),et])])])])]),n("div",tt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var nt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Pe,NewGameDialog:Ne},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${se.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const ot={class:"upload-image-teaser"},lt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),at={key:0},st=i(" Tags: "),it=i(" Sort by: "),rt=n("option",{value:"date_desc"},"Newest first",-1),dt=n("option",{value:"date_asc"},"Oldest first",-1),ct=n("option",{value:"alpha_asc"},"A-Z",-1),ut=n("option",{value:"alpha_desc"},"Z-A",-1);nt.render=function(e,o,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[n("div",ot,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),lt]),n("div",null,[e.tags.length>0?(s(),t("label",at,[st,(s(!0),t(d,null,c(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[it,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[rt,dt,ct,ut],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var pt=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 gt={class:"scores"},ht=n("div",null,"Scores",-1),mt=n("td",null,"⚡",-1),yt=n("td",null,"💤",-1);pt.render=function(e,o,l,a,i,u){return s(),t("div",gt,[ht,n("table",null,[(s(!0),t(d,null,c(e.actives,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[mt,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}},[yt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var ft=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 B(this.duration)}}});const vt={class:"timer"};ft.render=function(e,o,l,a,i,d){return s(),t("div",vt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var wt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const bt=m();y("data-v-a1d1c822");const kt=n("td",null,[n("label",null,"Background: ")],-1),xt=n("td",null,[n("label",null,"Color: ")],-1),Ct=n("td",null,[n("label",null,"Name: ")],-1),At=n("td",null,[n("label",null,"Sounds: ")],-1),St=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Tt={class:"sound-volume"};f();const Pt=bt(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[9]||(o[9]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[8]||(o[8]=u((()=>{}),["stop"]))},[n("tr",null,[kt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[xt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[Ct,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[At,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[k,e.modelValue.soundsEnabled]])])]),n("tr",null,[St,n("td",Tt,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])])])]))));wt.render=Pt,wt.__scopeId="data-v-a1d1c822";var It=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const zt={class:"preview"};It.render=function(e,o,l,a,i,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",zt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Dt=1,_t=4,Et=2,Mt=3,Vt=2,Nt=4,Ot=3,Bt=9,Ut=1,Rt=2,$t=3,Gt=4,Ft=5,Lt=6,jt=7,Wt=8,Ht=10,Yt=11,qt=12,Qt=13,Zt=14,Kt=1,Xt=2,Jt=3;const en=ae("Communication.js");let tn,nn=[],on=e=>{nn.push(e)},ln=[],an=e=>{ln.push(e)};let sn=0;const rn=e=>{sn!==e&&(sn=e,an(e))};function dn(e){if(2===sn)try{tn.send(JSON.stringify(e))}catch(t){en.info("unable to send message.. maybe because ws is invalid?")}}let cn,un;var pn={connect:function(e,t,n){return cn=0,un={},rn(3),new Promise((o=>{tn=new WebSocket(e,n+"|"+t),tn.onopen=()=>{rn(2),dn([Mt])},tn.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===_t){const e=t[1];o(e)}else{if(l!==Dt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&un[o])return void delete un[o];on(t)}}},tn.onerror=()=>{throw rn(1),"[ 2021-05-15 onerror ]"},tn.onclose=e=>{4e3===e.code||1001===e.code?rn(4):rn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${se.asQueryArgs(n)}`);return await o.json()},disconnect:function(){tn&&tn.close(4e3),cn=0,un={}},sendClientEvent:function(e){cn++,un[cn]=e,dn([Et,cn,un[cn]])},onServerChange:function(e){on=e;for(const t of nn)on(t);nn=[]},onConnectionStateChange:function(e){an=e;for(const t of ln)an(t);ln=[]},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},gn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===pn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===pn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const hn={key:0,class:"overlay connection-lost"},mn={key:0,class:"overlay-content"},yn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),fn={key:1,class:"overlay-content"},vn=n("div",null,"Connecting...",-1);gn.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",hn,[e.lostConnection?(s(),t("div",mn,[yn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",fn,[vn])):l("",!0)])):l("",!0)};var wn=e({name:"help-overlay",emits:{bgclick:null}});const bn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),kn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),xn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),Cn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),An=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Sn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Tn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),Pn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),In=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),zn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Dn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),_n=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),En=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),Mn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);wn.render=function(e,o,l,a,i,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]=u((()=>{}),["stop"]))},[bn,kn,xn,Cn,An,Sn,Tn,Pn,In,zn,Dn,_n,En,Mn])])};var Vn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),On=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Bn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Un=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Rn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),s=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),i=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:i,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function $n(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Gn={createCanvas:$n,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const o=$n(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=$n(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(),o}};const Fn=ae("Debug.js");let Ln=0,jn=0;var Wn=e=>{Ln=performance.now(),jn=e},Hn=e=>{const t=performance.now(),n=t-Ln;n>jn&&Fn.log(e+": "+n),Ln=t};function Yn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function qn(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var Qn={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:Yn,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:qn,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 Yn(qn(e),qn(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const Zn=ae("PuzzleGraphics.js");function Kn(e,t){const n=se.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Xn={loadPuzzleBitmaps:async function(e){const t=await Gn.loadImageToBitmap(e.info.imageUrl),n=await Gn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Zn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=Qn.pointAdd(a,{x:o,y:0}),c=Qn.pointAdd(r,{x:0,y:o}),u=Qn.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;ose.decodePiece(Jn[e].puzzle.tiles[t]),mo=(e,t)=>ho(e,t).group,yo=(e,t)=>{const n=Jn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},fo=(e,t)=>{const n=Jn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return Qn.pointAdd(o,l)},vo=(e,t)=>ho(e,t).pos,wo=e=>{const t=Oo(e),n=Bo(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}},bo=(e,t)=>{const n=Ao(e),o=ho(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},ko=(e,t)=>ho(e,t).z,xo=(e,t)=>{for(const n of Jn[e].puzzle.tiles){const e=se.decodePiece(n);if(e.owner===t)return e.idx}return-1},Co=e=>Jn[e].puzzle.info.tileDrawSize,Ao=e=>Jn[e].puzzle.info.tileSize,So=e=>Jn[e].puzzle.data.maxGroup,To=e=>Jn[e].puzzle.data.maxZ;function Po(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Io=(e,t,n)=>{for(const o of t)go(e,o,{z:n})},zo=(e,t,n)=>{const o=vo(e,t);go(e,t,{pos:Qn.pointAdd(o,n)})},Do=(e,t,n)=>{const o=Co(e),l=wo(e),a=n;for(const s of t){const t=ho(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)zo(e,s,a)},_o=(e,t)=>ho(e,t).owner,Eo=(e,t)=>{for(const n of t)go(e,n,{owner:-1,z:1})},Mo=(e,t,n)=>{for(const o of t)go(e,o,{owner:n})};function Vo(e,t){const n=Jn[e].puzzle.tiles,o=se.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=se.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const No=(e,t)=>{const n=to(e,t);return n?n.points:0},Oo=e=>Jn[e].puzzle.info.table.width,Bo=e=>Jn[e].puzzle.info.table.height;var Uo={setGame:function(e,t){Jn[e]=t},exists:function(e){return!!Jn[e]||!1},playerExists:oo,getActivePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){oo(e,t)?uo(e,t,{ts:n}):no(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:co,getPieceCount:ao,getImageUrl:function(e){return Jn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Jn[e].puzzle.info.imageUrl=t},get:function(e){return Jn[e]||null},getAllGames:function(){return Object.values(Jn).sort(((e,t)=>ro(e.id)===ro(t.id)?t.puzzle.data.started-e.puzzle.data.started:ro(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=to(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=to(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=to(e,t);return n?n.name:null},getPlayerIndexById:eo,getPlayerIdByIndex:function(e,t){return Jn[e].players.length>t?se.decodePlayer(Jn[e].players[t]).id:null},changePlayer:uo,setPlayer:no,setPiece:function(e,t,n){Jn[e].puzzle.tiles[t]=se.encodePiece(n)},setPuzzleData:function(e,t){Jn[e].puzzle.data=t},getTableWidth:Oo,getTableHeight:Bo,getPuzzle:e=>Jn[e].puzzle,getRng:e=>Jn[e].rng.obj,getPuzzleWidth:e=>Jn[e].puzzle.info.width,getPuzzleHeight:e=>Jn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Jn[e].puzzle.tiles.map(se.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=xo(e,t);return n<0?null:Jn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Jn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:Co,getFinalPiecePos:fo,getStartTs:e=>Jn[e].puzzle.data.started,getFinishTs:e=>Jn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Jn[e].puzzle,s=function(e,t){return t in Jn[e].evtInfos?Jn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([Kt,a.data])},d=t=>{i.push([Xt,se.encodePiece(ho(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=to(e,t);n&&i.push([Jt,se.encodePlayer(n)])},p=n[0];if(p===Lt){const l=n[1];uo(e,t,{bgcolor:l,ts:o}),u()}else if(p===jt){const l=n[1];uo(e,t,{color:l,ts:o}),u()}else if(p===Wt){const l=`${n[1]}`.substr(0,16);uo(e,t,{name:l,ts:o}),u()}else if(p===Bt){const l=n[1],a=n[2],s=to(e,t);if(s){const n=s.x-l,i=s.y-a;uo(e,t,{ts:o,x:n,y:i}),u()}}else if(p===Ut){const l={x:n[1],y:n[2]};uo(e,t,{d:1,ts:o}),u(),s._last_mouse_down=l;const a=((e,t)=>{const n=Jn[e].puzzle.info,o=Jn[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=To(e)+1;po(e,{maxZ:n}),r();const o=Vo(e,a);Io(e,o,To(e)),Mo(e,o,t),c(o)}s._last_mouse=l}else if(p===$t){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)uo(e,t,{x:l,y:a,ts:o}),u();else{const n=xo(e,t);if(n>=0){uo(e,t,{x:l,y:a,ts:o}),u();const r=Vo(e,n);let d=Qn.pointInBounds(i,wo(e))&&Qn.pointInBounds(s._last_mouse_down,wo(e));for(const t of r){const n=bo(e,t);if(Qn.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Do(e,r,{x:t,y:n}),c(r)}}else uo(e,t,{ts:o}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===Rt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=xo(e,t);if(g>=0){const n=Vo(e,g);Mo(e,n,0),c(n);const s=vo(e,g),i=fo(e,g);let h=!1;if(io(e)===ee.REAL){for(const t of n)if(yo(e,t)){h=!0;break}}else h=!0;if(h&&Qn.pointDistance(i,s){const l=Jn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=mo(e,t),l=mo(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=vo(e,t),s=Qn.pointAdd(vo(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(Qn.pointDistance(a,s){const o=Jn[e].puzzle.tiles,l=mo(e,t),a=mo(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(po(e,{maxGroup:So(e)+1}),r(),s=So(e));if(go(e,t,{group:s}),d(t),go(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=se.decodePiece(r);i.includes(t.group)&&(go(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=Vo(e,t),((e,t)=>-1===_o(e,t))(e,n))Eo(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=ko(e,o);t>n&&(n=t)}return n})(e,l);Io(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Vo(e,g)){const o=Po(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])){a=!0;break}}if(a&&so(e)===Z.ANY){const n=No(e,t)+1;uo(e,t,{d:p,ts:o,points:n}),u()}else uo(e,t,{d:p,ts:o}),u();a&&io(e)===ee.REAL&&co(e)===ao(e)&&(po(e,{finished:o}),r()),a&&l&&l(t)}}else uo(e,t,{d:p,ts:o}),u();s._last_mouse=i}else if(p===Gt){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else if(p===Ft){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else uo(e,t,{ts:o}),u();return function(e,t,n){Jn[e].evtInfos[t]=n}(e,t,s),i}};let Ro=-10,$o=20,Go=2,Fo=15;class Lo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Ro+Math.random()*$o,this.vy=-1*(Go+Math.random()*Fo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Go=t/2,Fo=t-Go;const n=1/4*this.canvas.width/(t/2);Ro=-n,$o=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Lo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Lo(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(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Gn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Ko=!0})),t}(l,Gn.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};pn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await pn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let k=()=>0;const x=async()=>{if("play"===o){const o=await pn.connect(n,e,t),l=se.decodeGame(o);Uo.setGame(l.id,l),k=()=>N()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=se.decodeGame(t.game);Uo.setGame(n.id,n),w.lastRealTs=N(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,k=()=>w.lastGameTs}}Ko=!0};await x();const C=Uo.getPieceDrawOffset(e),A=Uo.getPieceDrawSize(e),S=Uo.getPuzzleWidth(e),T=Uo.getPuzzleHeight(e),P=Uo.getTableWidth(e),I=Uo.getTableHeight(e),z={x:(P-S)/2,y:(I-T)/2},D={w:S,h:T},_={w:A,h:A},E=await Xn.loadPuzzleBitmaps(Uo.getPuzzle(e)),V=new Wo(v,Uo.getRng(e));V.init();const O=v.getContext("2d");v.classList.add("loaded");const B=Rn();B.move(-(P-v.width)/2,-(I-v.height)/2);const U=function(e,t,n,o){let l=[],a=!0,s=!1,i=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("Shift"===t.key?p=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?r=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?d=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?s=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?i=e:"q"===t.key?u=e:"e"===t.key&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Ut,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Rt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([$t,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Gt:Ft;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&(" "===e.key&&v([Ht]),"replay"===o&&("I"!==e.key&&"i"!==e.key||v([Qt]),"O"!==e.key&&"o"!==e.key||v([Zt]),"P"!==e.key&&"p"!==e.key||v([qt])),"F"!==e.key&&"f"!==e.key||(Qo=!Qo,Ko=!0),"G"!==e.key&&"g"!==e.key||(Zo=!Zo,Ko=!0),"M"!==e.key&&"m"!==e.key||v([Yt]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});v([Bt,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Gt,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([Ft,...e])}},setHotkeys:e=>{a=e}}}(v,window,B,o),R=Uo.getImageUrl(e),$=()=>{const t=Uo.getStartTs(e),n=Uo.getFinishTs(e),o=k();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),a.setPiecesTotal(Uo.getPieceCount(e));const G=k();a.setActivePlayers(Uo.getActivePlayers(e,G)),a.setIdlePlayers(Uo.getIdlePlayers(e,G));const F=!!Uo.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_volume");if(null===e)return 100;const t=parseInt(e,10);return isNaN(t)?100:t},H=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},Y=()=>{const e=W();i.volume=e/100,i.play()},q=()=>Uo.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Q=()=>Uo.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let Z="",K="",X=!1;const J=e=>{X=e;const[t,n]=e?[Z,"grab"]:[K,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},ee=e=>{Z=Gn.colorizedCanvas(r,c,e).toDataURL(),K=Gn.colorizedCanvas(d,u,e).toDataURL(),J(X)};ee(Q());const te=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ne=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,te())},le=()=>{w.paused=!w.paused,te()},ae=[];let ie;let re;if("play"===o?ae.push(setInterval((()=>{$()}),1e3)):"replay"===o&&te(),"play"===o)pn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Jt:{const n=se.decodePlayer(a);n.id!==t&&(Uo.setPlayer(e,n.id,n),Ko=!0)}break;case Xt:{const t=se.decodePiece(a);Uo.setPiece(e,t.idx,t),Ko=!0}break;case Kt:Uo.setPuzzleData(e,a),Ko=!0}L=!!Uo.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===Vt){const t=o[1];return Uo.addPlayer(e,t,n),!0}if(o[0]===Nt){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Uo.addPlayer(e,t,n),!0}if(o[0]===Ot){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Uo.handleInput(e,t,l,n),!0}return!1};let n=w.lastGameTs;const o=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=N();if(w.paused)return w.lastRealTs=l,void(ie=setTimeout(o,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const o=w.log[w.logPointer],l=n+o[o.length-1],a=w.log[e],i=a[a.length-1],r=l+i;if(r>s){w.skipNonActionPhases&&s+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/o),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{U.createKeyEvents();for(const n of U.consumeAll())if("play"===o){const o=n[0];if(o===Bt){const e=n[1],t=n[2],o=B.worldDimToViewport({w:e,h:t});Ko=!0,B.move(o.w,o.h)}else if(o===$t){if(de&&!Uo.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Ko=!0,B.move(o,l),de=t}}else if(o===jt)ee(n[1]);else if(o===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e),J(!0)}else if(o===Rt)de=null,J(!1);else if(o===Gt){const e={x:n[1],y:n[2]};Ko=!0,B.zoom("in",B.worldToViewport(e))}else if(o===Ft){const e={x:n[1],y:n[2]};Ko=!0,B.zoom("out",B.worldToViewport(e))}else o===Ht?a.togglePreview():o===Yt&&a.toggleSoundsEnabled();const l=k();Uo.handleInput(e,t,n,l,(e=>{H()&&Y()})).length>0&&(Ko=!0),pn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===qt)le();else if(e===Zt)oe();else if(e===Qt)ne();else if(e===Bt){const e=n[1],t=n[2];Ko=!0,B.move(e,t)}else if(e===$t){if(de){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Ko=!0,B.move(o,l),de=t}}else if(e===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e)}else if(e===Rt)de=null;else if(e===Gt){const e={x:n[1],y:n[2]};Ko=!0,B.zoom("in",B.worldToViewport(e))}else if(e===Ft){const e={x:n[1],y:n[2]};Ko=!0,B.zoom("out",B.worldToViewport(e))}else e===Ht&&a.togglePreview()}L=!!Uo.getFinishTs(e),j()&&(V.update(),Ko=!0)},render:async()=>{if(!Ko)return;const n=k();let l,s,i;window.DEBUG&&Wn(0),O.fillStyle=q(),O.fillRect(0,0,v.width,v.height),window.DEBUG&&Hn("clear done"),l=B.worldToViewportRaw(z),s=B.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&Hn("board done");const r=Uo.getPiecesSortedByZIndex(e);window.DEBUG&&Hn("get tiles done"),s=B.worldDimToViewportRaw(_);for(const e of r)(-1===e.owner?Qo:Zo)&&(i=E[e.idx],l=B.worldToViewportRaw({x:C+e.pos.x,y:C+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&Hn("tiles done");const d=[];for(const a of Uo.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(i=await f(a),l=B.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,o]of d)O.fillText(e,t,o);window.DEBUG&&Hn("players done"),a.setActivePlayers(Uo.getActivePlayers(e,n)),a.setIdlePlayers(Uo.getIdlePlayers(e,n)),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),window.DEBUG&&Hn("HUD done"),j()&&V.render(),Ko=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([Lt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([jt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Wt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},onSoundsVolumeChange:e=>{Ho.info("vol changed",e),localStorage.setItem("sound_volume",`${e}`),Y()},replayOnSpeedUp:ne,replayOnSpeedDown:oe,replayOnPauseToggle:le,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:R,player:{background:q(),color:Q(),name:Uo.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:H(),soundsVolume:W()},disconnect:pn.disconnect,connect:x,unload:()=>{ae.forEach((e=>{clearInterval(e)})),ie&&clearTimeout(ie),re&&re.stop()}}}var Jo=e({name:"game",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:It,ConnectionOverlay:gn,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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)},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 el={id:"game"},tl={class:"menu"},nl={class:"tabs"},ol=i("🧩 Puzzles");Jo.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",el,[p(n(u,{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"]),[[x,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",tl,[n("div",nl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[ol])),_: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 ll=e({name:"replay",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:It,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 al={id:"replay"},sl=i("Skip no action phases: "),il={class:"menu"},rl={class:"tabs"},dl=i("🧩 Puzzles");ll.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",al,[p(n(g,{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"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[sl,p(n("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[k,e.skipNoAction]])])]),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",il,[n("div",rl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[dl])),_:1}),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:l[12]||(l[12]=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=C({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:nt},{name:"game",path:"/g/:id",component:Jo},{name:"replay",path:"/replay/:id",component:ll}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=S(T);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=se.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); diff --git a/build/public/assets/index.99efb0e9.js b/build/public/assets/index.99efb0e9.js deleted file mode 100644 index ca43163..0000000 --- a/build/public/assets/index.99efb0e9.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as n,w as l,b as o,r as a,o as i,e as s,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as k,s as x,u as C,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const P={id:"app"},z={key:0,class:"nav"},I=s("Index"),D=s("New game");T.render=function(e,s,r,d,c,u){const p=a("router-link"),g=a("router-view");return i(),t("div",P,[e.showNav?(i(),t("ul",z,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:l((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:l((()=>[D])),_:1})])])):o("",!0),n(g)])};const E=864e5,_=e=>{const t=Math.floor(e/E);e%=E;const n=Math.floor(e/36e5);e%=36e5;const l=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${l}m ${Math.floor(e/1e3)}s`};var M=1,N=1e3,O=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>_(t-e),U=_,R=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",l=e,o=t||O();return`${n} ${B(l,o)}`}}});const V={class:"game-info-text"},G=n("br",null,null,-1),$=n("br",null,null,-1),F=n("br",null,null,-1),L=s(" ↪️ Watch replay ");R.render=function(e,d,c,u,p,g){const h=a("router-link");return i(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:l((()=>[n("span",V,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),G,s(" 👥 "+r(e.game.players),1),$,s(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(i(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:l((()=>[L])),_:1},8,["to"])):o("",!0)],4)};var j=e({components:{GameTeaser:R},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 W=n("h1",null,"Running games",-1),H=n("h1",null,"Finished games",-1);j.render=function(e,l,o,s,r,u){const p=a("game-teaser");return i(),t("div",null,[W,(i(!0),t(d,null,c(e.gamesRunning,((e,l)=>(i(),t("div",{class:"game-teaser-wrap",key:l},[n(p,{game:e},null,8,["game"])])))),128)),H,(i(!0),t(d,null,c(e.gamesFinished,((e,l)=>(i(),t("div",{class:"game-teaser-wrap",key:l},[n(p,{game:e},null,8,["game"])])))),128))])};var Y=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});Y.render=function(e,l,o,a,s,r){return i(),t("div",{class:"imageteaser",style:e.style,onClick:l[2]||(l[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:l[1]||(l[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Q,q,Z,K,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:Y},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,l,o,s,r){const u=a("image-teaser");return i(),t("div",null,[(i(!0),t(d,null,c(e.images,((n,l)=>(i(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:l},null,8,["image","onClick","onEditClick"])))),128))])},(q=Q||(Q={}))[q.Flat=0]="Flat",q[q.Out=1]="Out",q[q.In=-1]="In",(K=Z||(Z={}))[K.FINAL=0]="FINAL",K[K.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class le{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),l=t[n];t[n]=t[e],t[e]=l}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new le(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const oe=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const l=new Date,o=oe(l.getHours(),"00"),a=oe(l.getMinutes(),"00"),i=oe(l.getSeconds(),"00");console[t](`${o}:${a}:${i}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var ie={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",le.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Z.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:le.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const l=[n,e[n]].map(encodeURIComponent);t.push(l.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const se={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};se.render=function(e,n,l,o,a,s){return i(),t("div",{style:s.style,title:l.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.key&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-39ed99c7");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,l,a,s,u,m)=>(i(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.input=t),placeholder:"Plants, People",onChange:l[2]||(l[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:l[3]||(l[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:l[4]||(l[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(i(),t("div",ce,[n("ul",null,[(i(!0),t(d,null,c(e.autocomplete.values,((n,l)=>(i(),t("li",{key:l,class:{active:l===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):o("",!0),(i(!0),t(d,null,c(e.values,((n,l)=>(i(),t("span",{key:l,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-39ed99c7";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const l=n[0];return l.type.startsWith("image/")?l:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=n("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},ve=n("span",{class:"btn"},"Upload File",-1),we={class:"area-settings"},be=n("td",null,[n("label",null,"Title")],-1),ke=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),xe=n("td",null,[n("label",null,"Tags")],-1),Ce={class:"area-buttons"},Ae=s("🧩 Post to gallery "),Se=n("br",null,null,-1),Te=s(" + set up game");ge.render=function(e,l,o,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay new-image-dialog",onClick:l[11]||(l[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[10]||(l[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:l[3]||(l[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:l[4]||(l[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:l[5]||(l[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(i(),t("div",me,[n("span",{class:"remove btn",onClick:l[1]||(l[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(i(),t("div",ye,[n("label",fe,[n("input",{type:"file",style:{display:"none"},onChange:l[2]||(l[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ve])]))],34),n("div",we,[n("table",null,[n("tr",null,[be,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":l[6]||(l[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),ke,n("tr",null,[xe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[7]||(l[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Ce,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:l[8]||(l[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:l[9]||(l[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ae,Se,Te],8,["disabled"])])])])};var Pe=e({name:"edit-image-dialog",components:{ResponsiveImage:se,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const ze={class:"area-image"},Ie={class:"has-image"},De={class:"area-settings"},Ee=n("td",null,[n("label",null,"Title")],-1),_e=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Me=n("td",null,[n("label",null,"Tags")],-1),Ne={class:"area-buttons"};Pe.render=function(e,l,o,s,r,d){const c=a("responsive-image"),h=a("tags-input");return i(),t("div",{class:"overlay edit-image-dialog",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("div",ze,[n("div",Ie,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",De,[n("table",null,[n("tr",null,[Ee,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),_e,n("tr",null,[Me,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[2]||(l[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Ne,[n("button",{class:"btn",onClick:l[3]||(l[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Oe=e({name:"new-game-dialog",components:{ResponsiveImage:se},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Z.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Be={class:"area-image"},Ue={class:"has-image"},Re={key:0,class:"image-title"},Ve={class:"area-settings"},Ge=n("td",null,[n("label",null,"Pieces")],-1),$e=n("td",null,[n("label",null,"Scoring: ")],-1),Fe=s(" Any (Score when pieces are connected to each other or on final location)"),Le=n("br",null,null,-1),je=s(" Final (Score when pieces are put to their final location)"),We=n("td",null,[n("label",null,"Shapes: ")],-1),He=s(" Normal"),Ye=n("br",null,null,-1),Qe=s(" Any (flat pieces can occur anywhere)"),qe=n("br",null,null,-1),Ze=s(" Flat (all pieces flat on all sides)"),Ke=n("td",null,[n("label",null,"Snapping: ")],-1),Xe=s(" Normal (pieces snap to final destination and to each other)"),Je=n("br",null,null,-1),et=s(" Real (pieces snap only to corners, already snapped pieces and to each other)"),tt={class:"area-buttons"};Oe.render=function(e,l,s,d,c,h){const m=a("responsive-image");return i(),t("div",{class:"overlay new-game-dialog",onClick:l[11]||(l[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[10]||(l[10]=u((()=>{}),["stop"]))},[n("div",Be,[n("div",Ue,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(i(),t("div",Re,'"'+r(e.image.title)+'"',1)):o("",!0)]),n("div",Ve,[n("table",null,[n("tr",null,[Ge,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[$e,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[2]||(l[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Fe]),Le,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[3]||(l[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),je])])]),n("tr",null,[We,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[4]||(l[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),He]),Ye,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[5]||(l[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Qe]),qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[6]||(l[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),Ze])])]),n("tr",null,[Ke,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[7]||(l[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Xe]),Je,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[8]||(l[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),et])])])])]),n("div",tt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:l[9]||(l[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var nt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Pe,NewGameDialog:Oe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${ie.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const lt={class:"upload-image-teaser"},ot=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),at={key:0},it=s(" Tags: "),st=s(" Sort by: "),rt=n("option",{value:"date_desc"},"Newest first",-1),dt=n("option",{value:"date_asc"},"Oldest first",-1),ct=n("option",{value:"alpha_asc"},"A-Z",-1),ut=n("option",{value:"alpha_desc"},"Z-A",-1);nt.render=function(e,l,s,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return i(),t("div",null,[n("div",lt,[n("div",{class:"btn btn-big",onClick:l[1]||(l[1]=t=>e.dialog="new-image")},"Upload your image"),ot]),n("div",null,[e.tags.length>0?(i(),t("label",at,[it,(i(!0),t(d,null,c(e.relevantTags,((n,l)=>(i(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:l,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):o("",!0),n("label",null,[st,p(n("select",{"onUpdate:modelValue":l[2]||(l[2]=t=>e.filters.sort=t),onChange:l[3]||(l[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[rt,dt,ct,ut],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(i(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:l[4]||(l[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):o("",!0),"edit-image"===e.dialog?(i(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:l[5]||(l[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):o("",!0),e.image&&"new-game"===e.dialog?(i(),t(v,{key:2,onBgclick:l[6]||(l[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):o("",!0)])};var pt=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 gt={class:"scores"},ht=n("div",null,"Scores",-1),mt=n("td",null,"⚡",-1),yt=n("td",null,"💤",-1);pt.render=function(e,l,o,a,s,u){return i(),t("div",gt,[ht,n("table",null,[(i(!0),t(d,null,c(e.actives,((e,l)=>(i(),t("tr",{key:l,style:{color:e.color}},[mt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(i(!0),t(d,null,c(e.idles,((e,l)=>(i(),t("tr",{key:l,style:{color:e.color}},[yt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var ft=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 U(this.duration)}}});const vt={class:"timer"};ft.render=function(e,l,o,a,s,d){return i(),t("div",vt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var wt=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 bt=n("td",null,[n("label",null,"Background: ")],-1),kt=n("td",null,[n("label",null,"Color: ")],-1),xt=n("td",null,[n("label",null,"Name: ")],-1),Ct=n("td",null,[n("label",null,"Sounds: ")],-1);wt.render=function(e,l,o,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:l[6]||(l[6]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:l[5]||(l[5]=u((()=>{}),["stop"]))},[n("tr",null,[bt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":l[1]||(l[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[kt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":l[2]||(l[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[xt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":l[3]||(l[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[Ct,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":l[4]||(l[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[k,e.modelValue.soundsEnabled]])])])])])};var At=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const St={class:"preview"};At.render=function(e,l,o,a,s,r){return i(),t("div",{class:"overlay",onClick:l[1]||(l[1]=t=>e.$emit("bgclick"))},[n("div",St,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Tt=1,Pt=4,zt=2,It=3,Dt=2,Et=4,_t=3,Mt=9,Nt=1,Ot=2,Bt=3,Ut=4,Rt=5,Vt=6,Gt=7,$t=8,Ft=10,Lt=11,jt=12,Wt=13,Ht=14,Yt=1,Qt=2,qt=3;const Zt=ae("Communication.js");let Kt,Xt=[],Jt=e=>{Xt.push(e)},en=[],tn=e=>{en.push(e)};let nn=0;const ln=e=>{nn!==e&&(nn=e,tn(e))};function on(e){if(2===nn)try{Kt.send(JSON.stringify(e))}catch(t){Zt.info("unable to send message.. maybe because ws is invalid?")}}let an,sn;var rn={connect:function(e,t,n){return an=0,sn={},ln(3),new Promise((l=>{Kt=new WebSocket(e,n+"|"+t),Kt.onopen=()=>{ln(2),on([It])},Kt.onmessage=e=>{const t=JSON.parse(e.data),o=t[0];if(o===Pt){const e=t[1];l(e)}else{if(o!==Tt)throw`[ 2021-05-09 invalid connect msgType ${o} ]`;{const e=t[1],l=t[2];if(e===n&&sn[l])return void delete sn[l];Jt(t)}}},Kt.onerror=()=>{throw ln(1),"[ 2021-05-15 onerror ]"},Kt.onclose=e=>{4e3===e.code||1001===e.code?ln(4):ln(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},l=await fetch(`/api/replay-data${ie.asQueryArgs(n)}`);return await l.json()},disconnect:function(){Kt&&Kt.close(4e3),an=0,sn={}},sendClientEvent:function(e){an++,sn[an]=e,on([zt,an,sn[an]])},onServerChange:function(e){Jt=e;for(const t of Xt)Jt(t);Xt=[]},onConnectionStateChange:function(e){tn=e;for(const t of en)tn(t);en=[]},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},dn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===rn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===rn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const cn={key:0,class:"overlay connection-lost"},un={key:0,class:"overlay-content"},pn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),gn={key:1,class:"overlay-content"},hn=n("div",null,"Connecting...",-1);dn.render=function(e,l,a,s,r,d){return e.show?(i(),t("div",cn,[e.lostConnection?(i(),t("div",un,[pn,n("span",{class:"btn",onClick:l[1]||(l[1]=t=>e.$emit("reconnect"))},"Reconnect")])):o("",!0),e.connecting?(i(),t("div",gn,[hn])):o("",!0)])):o("",!0)};var mn=e({name:"help-overlay",emits:{bgclick:null}});const yn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),fn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),vn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),wn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),bn=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),kn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),xn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),Cn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),An=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Sn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Tn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),Pn=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),zn=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),In=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);mn.render=function(e,l,o,a,s,r){return i(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[yn,fn,vn,wn,bn,kn,xn,Cn,An,Sn,Tn,Pn,zn,In])])};var Dn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),En=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),_n=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Mn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function On(){let e=0,t=0,n=1;const l=(l,o)=>{e+=l/n,t+=o/n},o=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},a=l=>({x:l.x/n-e,y:l.y/n-t}),i=l=>({x:(l.x+e)*n,y:(l.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,move:l,canZoom:e=>n!=o(e),zoom:(e,t)=>((e,t)=>{if(n==e)return!1;const o=1-n/e;return l(-t.x*o,-t.y*o),n=e,!0})(o(e),t),worldToViewport:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function Bn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Un={createCanvas:Bn,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const l=Bn(t,n);return l.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(l)},colorizedCanvas:function(e,t,n){const l=Bn(e.width,e.height),o=l.getContext("2d");return o.save(),o.drawImage(t,0,0),o.fillStyle=n,o.globalCompositeOperation="source-in",o.fillRect(0,0,t.width,t.height),o.restore(),o.save(),o.globalCompositeOperation="destination-over",o.drawImage(e,0,0),o.restore(),l}};const Rn=ae("Debug.js");let Vn=0,Gn=0;var $n=e=>{Vn=performance.now(),Gn=e},Fn=e=>{const t=performance.now(),n=t-Vn;n>Gn&&Rn.log(e+": "+n),Vn=t};function Ln(e,t){const n=e.x-t.x,l=e.y-t.y;return Math.sqrt(n*n+l*l)}function jn(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var Wn={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:Ln,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:jn,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 Ln(jn(e),jn(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const Hn=ae("PuzzleGraphics.js");function Yn(e,t){const n=ie.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Qn={loadPuzzleBitmaps:async function(e){const t=await Un.loadImageToBitmap(e.info.imageUrl),n=await Un.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Hn.log("start createPuzzleTileBitmaps");const l=n.tileSize,o=n.tileMarginWidth,a=n.tileDrawSize,i=l/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],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,a={x:o,y:o},r=Wn.pointAdd(a,{x:l,y:0}),c=Wn.pointAdd(r,{x:0,y:l}),u=Wn.pointSub(c,{x:l,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let l=0;lie.decodePiece(qn[e].puzzle.tiles[t]),cl=(e,t)=>dl(e,t).group,ul=(e,t)=>{const n=qn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},pl=(e,t)=>{const n=qn[e].puzzle.info,l={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},o=function(e,t){const n=qn[e].puzzle.info,l=ie.coordByPieceIdx(n,t),o=l.x*n.tileSize,a=l.y*n.tileSize;return{x:o,y:a}}(e,t);return Wn.pointAdd(l,o)},gl=(e,t)=>dl(e,t).pos,hl=e=>{const t=El(e),n=_l(e),l=Math.round(t/4),o=Math.round(n/4);return{x:0-l,y:0-o,w:t+2*l,h:n+2*o}},ml=(e,t)=>{const n=wl(e),l=dl(e,t);return{x:l.pos.x,y:l.pos.y,w:n,h:n}},yl=(e,t)=>dl(e,t).z,fl=(e,t)=>{for(const n of qn[e].puzzle.tiles){const e=ie.decodePiece(n);if(e.owner===t)return e.idx}return-1},vl=e=>qn[e].puzzle.info.tileDrawSize,wl=e=>qn[e].puzzle.info.tileSize,bl=e=>qn[e].puzzle.data.maxGroup,kl=e=>qn[e].puzzle.data.maxZ;function xl(e,t){const n=qn[e].puzzle.info,l=ie.coordByPieceIdx(n,t);return[l.y>0?t-n.tilesX:-1,l.x0?t-1:-1]}const Cl=(e,t,n)=>{for(const l of t)rl(e,l,{z:n})},Al=(e,t,n)=>{const l=gl(e,t);rl(e,t,{pos:Wn.pointAdd(l,n)})},Sl=(e,t,n)=>{const l=vl(e),o=hl(e),a=n;for(const i of t){const t=dl(e,i);t.pos.x+n.xo.x+o.w&&(a.x=Math.min(o.x+o.w-t.pos.x+l,a.x)),t.pos.y+n.yo.y+o.h&&(a.y=Math.min(o.y+o.h-t.pos.y+l,a.y))}for(const i of t)Al(e,i,a)},Tl=(e,t)=>dl(e,t).owner,Pl=(e,t)=>{for(const n of t)rl(e,n,{owner:-1,z:1})},zl=(e,t,n)=>{for(const l of t)rl(e,l,{owner:n})};function Il(e,t){const n=qn[e].puzzle.tiles,l=ie.decodePiece(n[t]),o=[];if(l.group)for(const a of n){const e=ie.decodePiece(a);e.group===l.group&&o.push(e.idx)}else o.push(l.idx);return o}const Dl=(e,t)=>{const n=Kn(e,t);return n?n.points:0},El=e=>qn[e].puzzle.info.table.width,_l=e=>qn[e].puzzle.info.table.height;var Ml={setGame:function(e,t){qn[e]=t},exists:function(e){return!!qn[e]||!1},playerExists:Jn,getActivePlayers:function(e,t){const n=t-30*N;return el(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*N;return el(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Jn(e,t)?il(e,t,{ts:n}):Xn(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:al,getPieceCount:tl,getImageUrl:function(e){return qn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){qn[e].puzzle.info.imageUrl=t},get:function(e){return qn[e]||null},getAllGames:function(){return Object.values(qn).sort(((e,t)=>ol(e.id)===ol(t.id)?t.puzzle.data.started-e.puzzle.data.started:ol(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Kn(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Kn(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Kn(e,t);return n?n.name:null},getPlayerIndexById:Zn,getPlayerIdByIndex:function(e,t){return qn[e].players.length>t?ie.decodePlayer(qn[e].players[t]).id:null},changePlayer:il,setPlayer:Xn,setPiece:function(e,t,n){qn[e].puzzle.tiles[t]=ie.encodePiece(n)},setPuzzleData:function(e,t){qn[e].puzzle.data=t},getTableWidth:El,getTableHeight:_l,getPuzzle:e=>qn[e].puzzle,getRng:e=>qn[e].rng.obj,getPuzzleWidth:e=>qn[e].puzzle.info.width,getPuzzleHeight:e=>qn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return qn[e].puzzle.tiles.map(ie.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=fl(e,t);return n<0?null:qn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>qn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:vl,getFinalPiecePos:pl,getStartTs:e=>qn[e].puzzle.data.started,getFinishTs:e=>qn[e].puzzle.data.finished,handleInput:function(e,t,n,l,o){const a=qn[e].puzzle,i=function(e,t){return t in qn[e].evtInfos?qn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],r=()=>{s.push([Yt,a.data])},d=t=>{s.push([Qt,ie.encodePiece(dl(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=Kn(e,t);n&&s.push([qt,ie.encodePlayer(n)])},p=n[0];if(p===Vt){const o=n[1];il(e,t,{bgcolor:o,ts:l}),u()}else if(p===Gt){const o=n[1];il(e,t,{color:o,ts:l}),u()}else if(p===$t){const o=`${n[1]}`.substr(0,16);il(e,t,{name:o,ts:l}),u()}else if(p===Mt){const o=n[1],a=n[2],i=Kn(e,t);if(i){const n=i.x-o,s=i.y-a;il(e,t,{ts:l,x:n,y:s}),u()}}else if(p===Nt){const o={x:n[1],y:n[2]};il(e,t,{d:1,ts:l}),u(),i._last_mouse_down=o;const a=((e,t)=>{const n=qn[e].puzzle.info,l=qn[e].puzzle.tiles;let o=-1,a=-1;for(let i=0;io)&&(o=e.z,a=i)}return a})(e,o);if(a>=0){const n=kl(e)+1;sl(e,{maxZ:n}),r();const l=Il(e,a);Cl(e,l,kl(e)),zl(e,l,t),c(l)}i._last_mouse=o}else if(p===Bt){const o=n[1],a=n[2],s={x:o,y:a};if(null===i._last_mouse_down)il(e,t,{x:o,y:a,ts:l}),u();else{const n=fl(e,t);if(n>=0){il(e,t,{x:o,y:a,ts:l}),u();const r=Il(e,n);let d=Wn.pointInBounds(s,hl(e))&&Wn.pointInBounds(i._last_mouse_down,hl(e));for(const t of r){const n=ml(e,t);if(Wn.pointInBounds(s,n)){d=!0;break}}if(d){const t=o-i._last_mouse_down.x,n=a-i._last_mouse_down.y;Sl(e,r,{x:t,y:n}),c(r)}}else il(e,t,{ts:l}),u();i._last_mouse_down=s}i._last_mouse=s}else if(p===Ot){const s={x:n[1],y:n[2]},p=0;i._last_mouse_down=null;const g=fl(e,t);if(g>=0){const n=Il(e,g);zl(e,n,0),c(n);const i=gl(e,g),s=pl(e,g);let h=!1;if(ll(e)===ee.REAL){for(const t of n)if(ul(e,t)){h=!0;break}}else h=!0;if(h&&Wn.pointDistance(s,i){const o=qn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const l=cl(e,t),o=cl(e,n);return!(!l||l!==o)})(e,t,n))return!1;const a=gl(e,t),i=Wn.pointAdd(gl(e,n),{x:l[0]*o.tileSize,y:l[1]*o.tileSize});if(Wn.pointDistance(a,i){const l=qn[e].puzzle.tiles,o=cl(e,t),a=cl(e,n);let i;const s=[];o&&s.push(o),a&&s.push(a),o?i=o:a?i=a:(sl(e,{maxGroup:bl(e)+1}),r(),i=bl(e));if(rl(e,t,{group:i}),d(t),rl(e,n,{group:i}),d(n),s.length>0)for(const r of l){const t=ie.decodePiece(r);s.includes(t.group)&&(rl(e,t.idx,{group:i}),d(t.idx))}})(e,t,n),o=Il(e,t),((e,t)=>-1===Tl(e,t))(e,n))Pl(e,o);else{const t=((e,t)=>{let n=0;for(const l of t){const t=yl(e,l);t>n&&(n=t)}return n})(e,o);Cl(e,o,t)}return c(o),!0}return!1};let a=!1;for(const t of Il(e,g)){const l=xl(e,t);if(n(e,t,l[0],[0,1])||n(e,t,l[1],[-1,0])||n(e,t,l[2],[0,-1])||n(e,t,l[3],[1,0])){a=!0;break}}if(a&&nl(e)===Z.ANY){const n=Dl(e,t)+1;il(e,t,{d:p,ts:l,points:n}),u()}else il(e,t,{d:p,ts:l}),u();a&&ll(e)===ee.REAL&&al(e)===tl(e)&&(sl(e,{finished:l}),r()),a&&o&&o(t)}}else il(e,t,{d:p,ts:l}),u();i._last_mouse=s}else if(p===Ut){const o=n[1],a=n[2];il(e,t,{x:o,y:a,ts:l}),u(),i._last_mouse={x:o,y:a}}else if(p===Rt){const o=n[1],a=n[2];il(e,t,{x:o,y:a,ts:l}),u(),i._last_mouse={x:o,y:a}}else il(e,t,{ts:l}),u();return function(e,t,n){qn[e].evtInfos[t]=n}(e,t,i),s}};let Nl=-10,Ol=20,Bl=2,Ul=15;class Rl{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Nl+Math.random()*Ol,this.vy=-1*(Bl+Math.random()*Ul),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Bl=t/2,Ul=t-Bl;const n=1/4*this.canvas.width/(t/2);Nl=-n,Ol=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Rl(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Rl(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(!y[t]){const n=e.d?r:d;if(e.color){const l=e.d?c:u;y[t]=await createImageBitmap(Un.colorizedCanvas(n,l,e.color))}else y[t]=n}return y[t]},v=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,Wl=!0})),t}(o,Un.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};rn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await rn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let k=()=>0;const x=async()=>{if("play"===l){const l=await rn.connect(n,e,t),o=ie.decodeGame(l);Ml.setGame(o.id,o),k=()=>O()}else{if("replay"!==l)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=ie.decodeGame(t.game);Ml.setGame(n.id,n),w.lastRealTs=O(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,k=()=>w.lastGameTs}}Wl=!0};await x();const C=Ml.getPieceDrawOffset(e),A=Ml.getPieceDrawSize(e),S=Ml.getPuzzleWidth(e),T=Ml.getPuzzleHeight(e),P=Ml.getTableWidth(e),z=Ml.getTableHeight(e),I={x:(P-S)/2,y:(z-T)/2},D={w:S,h:T},E={w:A,h:A},_=await Qn.loadPuzzleBitmaps(Ml.getPuzzle(e)),N=new Gl(v,Ml.getRng(e));N.init();const B=v.getContext("2d");v.classList.add("loaded");const U=On();U.move(-(P-v.width)/2,-(z-v.height)/2);const R=function(e,t,n,l){let o=[],a=!0,i=!1,s=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const l=n.viewportToWorld({x:e,y:t});return[l.x,l.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("Shift"===t.key?p=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?r=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?d=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?u=e:"e"===t.key&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Nt,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Ot,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([Bt,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Ut:Rt;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&(" "===e.key&&v([Ft]),"replay"===l&&("I"!==e.key&&"i"!==e.key||v([Wt]),"O"!==e.key&&"o"!==e.key||v([Ht]),"P"!==e.key&&"p"!==e.key||v([jt])),"F"!==e.key&&"f"!==e.key||(Ll=!Ll,Wl=!0),"G"!==e.key&&"g"!==e.key||(jl=!jl,Wl=!0),"M"!==e.key&&"m"!==e.key||v([Lt]))}));const v=e=>{o.push(e)};return{addEvent:v,consumeAll:()=>{if(0===o.length)return[];const e=o.slice();return o=[],e},createKeyEvents:()=>{const e=(i?1:0)-(s?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const l=(p?24:12)*Math.sqrt(n.getCurrentZoom()),o=n.viewportDimToWorld({w:e*l,h:t*l});v([Mt,o.w,o.h]),f&&(f[0]-=o.w,f[1]-=o.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Ut,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([Rt,...e])}},setHotkeys:e=>{a=e}}}(v,window,U,l),V=Ml.getImageUrl(e),G=()=>{const t=Ml.getStartTs(e),n=Ml.getFinishTs(e),l=k();a.setFinished(!!n),a.setDuration((n||l)-t)};G(),a.setPiecesDone(Ml.getFinishedPiecesCount(e)),a.setPiecesTotal(Ml.getPieceCount(e));const $=k();a.setActivePlayers(Ml.getActivePlayers(e,$)),a.setIdlePlayers(Ml.getIdlePlayers(e,$));const F=!!Ml.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},H=()=>Ml.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Y=()=>Ml.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let Q="",q="",Z=!1;const K=e=>{Z=e;const[t,n]=e?[Q,"grab"]:[q,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},X=e=>{Q=Un.colorizedCanvas(r,c,e).toDataURL(),q=Un.colorizedCanvas(d,u,e).toDataURL(),K(Z)};X(Y());const J=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ee=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,J())},ne=()=>{w.paused=!w.paused,J()},le=[];let oe;let ae;if("play"===l?le.push(setInterval((()=>{G()}),1e3)):"replay"===l&&J(),"play"===l)rn.onServerChange((n=>{n[0],n[1],n[2];const l=n[3];for(const[o,a]of l)switch(o){case qt:{const n=ie.decodePlayer(a);n.id!==t&&(Ml.setPlayer(e,n.id,n),Wl=!0)}break;case Qt:{const t=ie.decodePiece(a);Ml.setPiece(e,t.idx,t),Wl=!0}break;case Yt:Ml.setPuzzleData(e,a),Wl=!0}L=!!Ml.getFinishTs(e)}));else if("replay"===l){const t=(t,n)=>{const l=t;if(l[0]===Dt){const t=l[1];return Ml.addPlayer(e,t,n),!0}if(l[0]===Et){const t=Ml.getPlayerIdByIndex(e,l[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Ml.addPlayer(e,t,n),!0}if(l[0]===_t){const t=Ml.getPlayerIdByIndex(e,l[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const o=l[2];return Ml.handleInput(e,t,o,n),!0}return!1};let n=w.lastGameTs;const l=async()=>{w.logPointer+1>=w.log.length&&await b(e);const o=O();if(w.paused)return w.lastRealTs=o,void(oe=setTimeout(l,50));const a=(o-w.lastRealTs)*w.speeds[w.speedIdx];let i=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const l=w.log[w.logPointer],o=n+l[l.length-1],a=w.log[e],s=a[a.length-1],r=o+s;if(r>i){w.skipNonActionPhases&&i+500*M{let t=!1;const n=e.fps||60,l=e.slow||1,o=e.update,a=e.render,i=window.requestAnimationFrame,s=1/n,r=l*s;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,o(s);a(c/l),u=d,t||i(p)};return i(p),{stop:()=>{t=!0}}})({update:()=>{R.createKeyEvents();for(const n of R.consumeAll())if("play"===l){const l=n[0];if(l===Mt){const e=n[1],t=n[2],l=U.worldDimToViewport({w:e,h:t});Wl=!0,U.move(l.w,l.h)}else if(l===Bt){if(se&&!Ml.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),l=Math.round(t.x-se.x),o=Math.round(t.y-se.y);Wl=!0,U.move(l,o),se=t}}else if(l===Gt)X(n[1]);else if(l===Nt){const e={x:n[1],y:n[2]};se=U.worldToViewport(e),K(!0)}else if(l===Ot)se=null,K(!1);else if(l===Ut){const e={x:n[1],y:n[2]};Wl=!0,U.zoom("in",U.worldToViewport(e))}else if(l===Rt){const e={x:n[1],y:n[2]};Wl=!0,U.zoom("out",U.worldToViewport(e))}else l===Ft?a.togglePreview():l===Lt&&a.toggleSoundsEnabled();const o=k();Ml.handleInput(e,t,n,o,(e=>{W()&&s.play()})).length>0&&(Wl=!0),rn.sendClientEvent(n)}else if("replay"===l){const e=n[0];if(e===jt)ne();else if(e===Ht)te();else if(e===Wt)ee();else if(e===Mt){const e=n[1],t=n[2];Wl=!0,U.move(e,t)}else if(e===Bt){if(se){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),l=Math.round(t.x-se.x),o=Math.round(t.y-se.y);Wl=!0,U.move(l,o),se=t}}else if(e===Nt){const e={x:n[1],y:n[2]};se=U.worldToViewport(e)}else if(e===Ot)se=null;else if(e===Ut){const e={x:n[1],y:n[2]};Wl=!0,U.zoom("in",U.worldToViewport(e))}else if(e===Rt){const e={x:n[1],y:n[2]};Wl=!0,U.zoom("out",U.worldToViewport(e))}else e===Ft&&a.togglePreview()}L=!!Ml.getFinishTs(e),j()&&(N.update(),Wl=!0)},render:async()=>{if(!Wl)return;const n=k();let o,i,s;window.DEBUG&&$n(0),B.fillStyle=H(),B.fillRect(0,0,v.width,v.height),window.DEBUG&&Fn("clear done"),o=U.worldToViewportRaw(I),i=U.worldDimToViewportRaw(D),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(o.x,o.y,i.w,i.h),window.DEBUG&&Fn("board done");const r=Ml.getPiecesSortedByZIndex(e);window.DEBUG&&Fn("get tiles done"),i=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?Ll:jl)&&(s=_[e.idx],o=U.worldToViewportRaw({x:C+e.pos.x,y:C+e.pos.y}),B.drawImage(s,0,0,s.width,s.height,o.x,o.y,i.w,i.h));window.DEBUG&&Fn("tiles done");const d=[];for(const a of Ml.getActivePlayers(e,n))c=a,("replay"===l||c.id!==t)&&(s=await f(a),o=U.worldToViewport(a),B.drawImage(s,o.x-g,o.y-m),d.push([`${a.name} (${a.points})`,o.x,o.y+h]));var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,l]of d)B.fillText(e,t,l);window.DEBUG&&Fn("players done"),a.setActivePlayers(Ml.getActivePlayers(e,n)),a.setIdlePlayers(Ml.getIdlePlayers(e,n)),a.setPiecesDone(Ml.getFinishedPiecesCount(e)),window.DEBUG&&Fn("HUD done"),j()&&N.render(),Wl=!1}}),{setHotkeys:e=>{R.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),R.addEvent([Vt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),R.addEvent([Gt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),R.addEvent([$t,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},replayOnSpeedUp:ee,replayOnSpeedDown:te,replayOnPauseToggle:ne,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:V,player:{background:H(),color:Y(),name:Ml.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:W()},disconnect:rn.disconnect,connect:x,unload:()=>{le.forEach((e=>{clearInterval(e)})),oe&&clearTimeout(oe),ae&&ae.stop()}}}var Yl=e({name:"game",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:At,ConnectionOverlay:dn,HelpOverlay:mn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Hl(`${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)},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 Ql={id:"game"},ql={class:"menu"},Zl={class:"tabs"},Kl=s("🧩 Puzzles");Yl.render=function(e,o,s,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Ql,[p(n(u,{onBgclick:o[1]||(o[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":o[2]||(o[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(g,{onBgclick:o[3]||(o[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(h,{onBgclick:o[4]||(o[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",ql,[n("div",Zl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[Kl])),_:1}),n("div",{class:"opener",onClick:o[5]||(o[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:o[6]||(o[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:o[7]||(o[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var Xl=e({name:"replay",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:At,HelpOverlay:mn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Hl(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 Jl={id:"replay"},eo=s("Skip no action phases: "),to={class:"menu"},no={class:"tabs"},lo=s("🧩 Puzzles");Xl.render=function(e,o,s,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return i(),t("div",Jl,[p(n(g,{onBgclick:o[1]||(o[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":o[2]||(o[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:o[3]||(o[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(m,{onBgclick:o[4]||(o[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:l((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[eo,p(n("input",{type:"checkbox","onUpdate:modelValue":o[5]||(o[5]=t=>e.skipNoAction=t),onChange:o[6]||(o[6]=t=>e.g.replayOnSkipToggle())},null,544),[[k,e.skipNoAction]])])]),n("button",{class:"btn",onClick:o[7]||(o[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:o[8]||(o[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:o[9]||(o[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",to,[n("div",no,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[lo])),_:1}),n("div",{class:"opener",onClick:o[10]||(o[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:o[11]||(o[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:o[12]||(o[12]=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=C({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:nt},{name:"game",path:"/g/:id",component:Yl},{name:"replay",path:"/replay/:id",component:Xl}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const l=S(T);l.config.globalProperties.$config=t,l.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=ie.uniqId(),localStorage.setItem("ID",e)),e}(),l.use(n),l.mount("#app")})(); diff --git a/build/public/assets/index.84d14088.css b/build/public/assets/index.a9024809.css similarity index 97% rename from build/public/assets/index.84d14088.css rename to build/public/assets/index.a9024809.css index 6bff45b..85eeda7 100644 --- a/build/public/assets/index.84d14088.css +++ b/build/public/assets/index.a9024809.css @@ -1 +1 @@ -:root{--main-color:#c1b19f;--main-darker-color:#4f4e4c;--link-color:#808db0;--link-hover-color:#c5cfeb;--highlight-color:#dd7e7e;--positive-color:#64a756;--input-bg-color:#262523;--bg-color:rgba(0,0,0,.7)}body,html{margin:0;background:#2b2b2b;color:var(--main-color);height:100%}*{font-family:monospace;font-size:15px}h1,h2,h3,h4{font-size:20px}a{color:var(--link-color);text-decoration:none}a:hover{color:var(--link-hover-color)}td,th{vertical-align:top}.btn{display:inline-block;background:var(--input-bg-color);color:var(--link-color);border:solid 1px #000;padding:5px 10px;box-shadow:1px 1px 2px rgba(0,0,0,.5),0 0 1px rgba(150,150,150,.4) inset;border-radius:4px;user-select:none}.btn-big{font-size:1.5em;padding:10px 20px}.btn:hover{background:#2f2e2c;color:var(--link-hover-color);border:solid 1px #111;box-shadow:0 0 1px rgba(150,150,150,.4) inset;cursor:pointer}.btn:disabled{background:#2f2e2c;color:#8c4747!important;border:solid 1px #111;box-shadow:0 0 1px rgba(150,150,150,.4) inset;cursor:not-allowed}input{background:#333230;border-radius:4px;color:var(--main-color);padding:6px 10px;border:solid 1px #000;box-shadow:0 0 3px rgba(0,0,0,.3) inset}input:focus{border:solid 1px #686767;background:var(--input-bg-color)}.scores{position:absolute;right:0;top:0;background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7)}.timer{position:absolute;left:0;top:0;background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7)}.menu{position:absolute;top:0;left:50%;transform:translateX(-50%);background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7);z-index:2}.closed{display:none}.overlay{position:fixed;top:0;left:0;right:0;bottom:0;z-index:10;background:var(--bg-color)}.overlay.transparent{background:0 0}.overlay-content{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7);z-index:1}.connection-lost .overlay-content{padding:20px;text-align:center}.preview{position:absolute;top:20px;left:20px;bottom:20px;right:20px}.preview .img{height:100%;width:100%;position:absolute;background-repeat:no-repeat;background-position:center;background-size:contain}.menu .opener{display:inline-block;margin-right:10px;color:var(--link-color)}.menu .opener:last-child{margin-right:0}.menu .opener:hover{color:var(--link-hover-color);cursor:pointer}kbd{background-color:#eee;border-radius:3px;border:1px solid #b4b4b4;box-shadow:0 1px 1px rgba(0,0,0,.2),0 2px 0 0 rgba(255,255,255,.7) inset;color:#333;display:inline-block;font-size:.85em;font-weight:700;line-height:1;padding:2px 4px;white-space:nowrap}.hint{color:var(--main-darker-color)}.bit{background:#3b3737;border-radius:.5em;padding:.25em .5em;display:inline-block;margin:0 .25em .25em 0;cursor:pointer}.bit.on{color:var(--positive-color)}.upload-image-teaser{text-align:center}.upload-image-teaser .btn{margin-bottom:.5em}table label{line-height:32px}.nav{list-style:none;padding:0}.nav li{display:inline-block;margin-right:1em}.image-list{overflow:scroll}.image-list-inner{white-space:nowrap}.imageteaser{width:150px;height:100px;display:inline-block;margin:5px;background-size:contain;background-position:center;background-repeat:no-repeat;background-color:#222;cursor:pointer}.game-teaser-wrap{display:inline-block;width:20%;padding:5px;box-sizing:border-box}.game-teaser{display:block;background-repeat:no-repeat;background-position:center;background-size:contain;position:relative;padding-top:56.25%;width:100%;background-color:#222}.game-info{position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%}.game-info-text{position:absolute;top:0;background:var(--bg-color);padding:5px}.game-replay{position:absolute;top:0;right:0}html.view-game{overflow:hidden}html.view-game body{overflow:hidden}html.view-replay{overflow:hidden}html.view-replay body{overflow:hidden}.imageteaser{position:relative}.imageteaser .edit{display:none;position:absolute}.imageteaser:hover .edit{display:inline-block}.input[data-v-39ed99c7]{margin-bottom:.5em}.autocomplete[data-v-39ed99c7]{position:relative}.autocomplete ul[data-v-39ed99c7]{list-style:none;padding:0;margin:0;position:absolute;left:0;right:0;background:#333230;top:-.5em}.autocomplete ul li[data-v-39ed99c7]{position:relative;padding:.5em .5em .5em 1.5em;cursor:pointer}.autocomplete ul li.active[data-v-39ed99c7]{color:var(--link-hover-color);background:var(--input-bg-color)}.autocomplete ul li.active[data-v-39ed99c7]:before{content:'▶';display:block;position:absolute;left:.5em}.new-image-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}@media (max-width:1400px){.new-image-dialog .overlay-content{grid-template-columns:auto;grid-template-rows:1fr min-content min-content;grid-template-areas:"image" "settings" "buttons"}.new-image-dialog .overlay-content .area-buttons .btn br{display:none}}.new-image-dialog .area-image{grid-area:image;margin:.5em;border:solid 6px transparent}.new-image-dialog .area-image.no-image{align-content:center;display:grid;text-align:center;border:solid 6px;position:relative}.new-image-dialog .area-image.droppable{border:dashed 6px}.new-image-dialog .area-image .has-image{position:relative;width:100%;height:100%}.new-image-dialog .area-image .has-image .remove{position:absolute;top:.5em;left:.5em}.new-image-dialog .area-settings{grid-area:settings}.new-image-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.new-image-dialog .area-buttons{align-self:end;grid-area:buttons}.new-image-dialog .area-buttons button{width:100%;margin-top:.5em}.new-image-dialog .upload{position:absolute;top:0;left:0;right:0;bottom:0;cursor:pointer}.new-image-dialog .upload .btn{position:absolute;top:50%;transform:translate(-50%,-50%)}.area-image .drop-target{display:none}.area-image.droppable .drop-target{pointer-events:none;position:absolute;top:0;left:0;right:0;bottom:0;z-index:3}.edit-image-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}.edit-image-dialog .area-image{grid-area:image;margin:20px}.edit-image-dialog .area-image.no-image{align-content:center;display:grid;text-align:center;border:dashed 6px;position:relative}.edit-image-dialog .area-image .has-image{position:relative;width:100%;height:100%}.edit-image-dialog .area-image .has-image .remove{position:absolute;top:.5em;left:.5em}.edit-image-dialog .area-settings{grid-area:settings}.edit-image-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.edit-image-dialog .area-buttons{align-self:end;grid-area:buttons}.edit-image-dialog .area-buttons button{width:100%;margin-top:.5em}.new-game-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}.new-game-dialog .area-image{grid-area:image;display:grid;grid-template-rows:1fr min-content;grid-template-areas:"image" "image-title";margin-right:1em}@media (max-width:1400px){.new-game-dialog .overlay-content{grid-template-columns:auto;grid-template-rows:1fr min-content min-content;grid-template-areas:"image" "settings" "buttons"}.new-game-dialog .area-image{margin-right:0}}.new-game-dialog .area-settings{grid-area:settings}.new-game-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.new-game-dialog .area-buttons{align-self:end;grid-area:buttons}.new-game-dialog .area-buttons button{width:100%}.new-game-dialog .has-image{box-sizing:border-box;grid-area:image;position:relative;width:100%;height:100%;border:solid 1px}.new-game-dialog .image-title{grid-area:image-title;text-align:center;padding:.5em 0;background:var(--main-color);color:#262523}.new-game-dialog .has-image .remove{position:absolute;top:.5em;left:.5em} \ No newline at end of file +:root{--main-color:#c1b19f;--main-darker-color:#4f4e4c;--link-color:#808db0;--link-hover-color:#c5cfeb;--highlight-color:#dd7e7e;--positive-color:#64a756;--input-bg-color:#262523;--bg-color:rgba(0,0,0,.7)}body,html{margin:0;background:#2b2b2b;color:var(--main-color);height:100%}*{font-family:monospace;font-size:15px}h1,h2,h3,h4{font-size:20px}a{color:var(--link-color);text-decoration:none}a:hover{color:var(--link-hover-color)}td,th{vertical-align:top}.btn{display:inline-block;background:var(--input-bg-color);color:var(--link-color);border:solid 1px #000;padding:5px 10px;box-shadow:1px 1px 2px rgba(0,0,0,.5),0 0 1px rgba(150,150,150,.4) inset;border-radius:4px;user-select:none}.btn-big{font-size:1.5em;padding:10px 20px}.btn:hover{background:#2f2e2c;color:var(--link-hover-color);border:solid 1px #111;box-shadow:0 0 1px rgba(150,150,150,.4) inset;cursor:pointer}.btn:disabled{background:#2f2e2c;color:#8c4747!important;border:solid 1px #111;box-shadow:0 0 1px rgba(150,150,150,.4) inset;cursor:not-allowed}input{background:#333230;border-radius:4px;color:var(--main-color);padding:6px 10px;border:solid 1px #000;box-shadow:0 0 3px rgba(0,0,0,.3) inset}input:focus{border:solid 1px #686767;background:var(--input-bg-color)}.scores{position:absolute;right:0;top:0;background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7)}.timer{position:absolute;left:0;top:0;background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7)}.menu{position:absolute;top:0;left:50%;transform:translateX(-50%);background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7);z-index:2}.closed{display:none}.overlay{position:fixed;top:0;left:0;right:0;bottom:0;z-index:10;background:var(--bg-color)}.overlay.transparent{background:0 0}.overlay-content{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7);z-index:1}.connection-lost .overlay-content{padding:20px;text-align:center}.preview{position:absolute;top:20px;left:20px;bottom:20px;right:20px}.preview .img{height:100%;width:100%;position:absolute;background-repeat:no-repeat;background-position:center;background-size:contain}.menu .opener{display:inline-block;margin-right:10px;color:var(--link-color)}.menu .opener:last-child{margin-right:0}.menu .opener:hover{color:var(--link-hover-color);cursor:pointer}kbd{background-color:#eee;border-radius:3px;border:1px solid #b4b4b4;box-shadow:0 1px 1px rgba(0,0,0,.2),0 2px 0 0 rgba(255,255,255,.7) inset;color:#333;display:inline-block;font-size:.85em;font-weight:700;line-height:1;padding:2px 4px;white-space:nowrap}.hint{color:var(--main-darker-color)}.bit{background:#3b3737;border-radius:.5em;padding:.25em .5em;display:inline-block;margin:0 .25em .25em 0;cursor:pointer}.bit.on{color:var(--positive-color)}.upload-image-teaser{text-align:center}.upload-image-teaser .btn{margin-bottom:.5em}table label{line-height:32px}.nav{list-style:none;padding:0}.nav li{display:inline-block;margin-right:1em}.image-list{overflow:scroll}.image-list-inner{white-space:nowrap}.imageteaser{width:150px;height:100px;display:inline-block;margin:5px;background-size:contain;background-position:center;background-repeat:no-repeat;background-color:#222;cursor:pointer}.game-teaser-wrap{display:inline-block;width:20%;padding:5px;box-sizing:border-box}.game-teaser{display:block;background-repeat:no-repeat;background-position:center;background-size:contain;position:relative;padding-top:56.25%;width:100%;background-color:#222}.game-info{position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%}.game-info-text{position:absolute;top:0;background:var(--bg-color);padding:5px}.game-replay{position:absolute;top:0;right:0}html.view-game{overflow:hidden}html.view-game body{overflow:hidden}html.view-replay{overflow:hidden}html.view-replay body{overflow:hidden}.imageteaser{position:relative}.imageteaser .edit{display:none;position:absolute}.imageteaser:hover .edit{display:inline-block}.input[data-v-39ed99c7]{margin-bottom:.5em}.autocomplete[data-v-39ed99c7]{position:relative}.autocomplete ul[data-v-39ed99c7]{list-style:none;padding:0;margin:0;position:absolute;left:0;right:0;background:#333230;top:-.5em}.autocomplete ul li[data-v-39ed99c7]{position:relative;padding:.5em .5em .5em 1.5em;cursor:pointer}.autocomplete ul li.active[data-v-39ed99c7]{color:var(--link-hover-color);background:var(--input-bg-color)}.autocomplete ul li.active[data-v-39ed99c7]:before{content:'▶';display:block;position:absolute;left:.5em}.new-image-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}@media (max-width:1400px){.new-image-dialog .overlay-content{grid-template-columns:auto;grid-template-rows:1fr min-content min-content;grid-template-areas:"image" "settings" "buttons"}.new-image-dialog .overlay-content .area-buttons .btn br{display:none}}.new-image-dialog .area-image{grid-area:image;margin:.5em;border:solid 6px transparent}.new-image-dialog .area-image.no-image{align-content:center;display:grid;text-align:center;border:solid 6px;position:relative}.new-image-dialog .area-image.droppable{border:dashed 6px}.new-image-dialog .area-image .has-image{position:relative;width:100%;height:100%}.new-image-dialog .area-image .has-image .remove{position:absolute;top:.5em;left:.5em}.new-image-dialog .area-settings{grid-area:settings}.new-image-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.new-image-dialog .area-buttons{align-self:end;grid-area:buttons}.new-image-dialog .area-buttons button{width:100%;margin-top:.5em}.new-image-dialog .upload{position:absolute;top:0;left:0;right:0;bottom:0;cursor:pointer}.new-image-dialog .upload .btn{position:absolute;top:50%;transform:translate(-50%,-50%)}.area-image .drop-target{display:none}.area-image.droppable .drop-target{pointer-events:none;position:absolute;top:0;left:0;right:0;bottom:0;z-index:3}.edit-image-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}.edit-image-dialog .area-image{grid-area:image;margin:20px}.edit-image-dialog .area-image.no-image{align-content:center;display:grid;text-align:center;border:dashed 6px;position:relative}.edit-image-dialog .area-image .has-image{position:relative;width:100%;height:100%}.edit-image-dialog .area-image .has-image .remove{position:absolute;top:.5em;left:.5em}.edit-image-dialog .area-settings{grid-area:settings}.edit-image-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.edit-image-dialog .area-buttons{align-self:end;grid-area:buttons}.edit-image-dialog .area-buttons button{width:100%;margin-top:.5em}.new-game-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}.new-game-dialog .area-image{grid-area:image;display:grid;grid-template-rows:1fr min-content;grid-template-areas:"image" "image-title";margin-right:1em}@media (max-width:1400px){.new-game-dialog .overlay-content{grid-template-columns:auto;grid-template-rows:1fr min-content min-content;grid-template-areas:"image" "settings" "buttons"}.new-game-dialog .area-image{margin-right:0}}.new-game-dialog .area-settings{grid-area:settings}.new-game-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.new-game-dialog .area-buttons{align-self:end;grid-area:buttons}.new-game-dialog .area-buttons button{width:100%}.new-game-dialog .has-image{box-sizing:border-box;grid-area:image;position:relative;width:100%;height:100%;border:solid 1px}.new-game-dialog .image-title{grid-area:image-title;text-align:center;padding:.5em 0;background:var(--main-color);color:#262523}.new-game-dialog .has-image .remove{position:absolute;top:.5em;left:.5em}.sound-volume span[data-v-a1d1c822]{cursor:pointer;user-select:none}.sound-volume input[data-v-a1d1c822]{vertical-align:middle} \ No newline at end of file diff --git a/build/public/index.html b/build/public/index.html index c5ade0f..5e60fb5 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,9 +4,9 @@ 🧩 jigsaw.hyottoko.club - + - +
diff --git a/src/frontend/components/SettingsOverlay.vue b/src/frontend/components/SettingsOverlay.vue index 01155e4..e9c3543 100644 --- a/src/frontend/components/SettingsOverlay.vue +++ b/src/frontend/components/SettingsOverlay.vue @@ -17,6 +17,20 @@ + + + + 🔉 + + 🔊 + +
@@ -30,7 +44,23 @@ export default defineComponent({ 'update:modelValue': null, }, props: { - modelValue: Object, + modelValue: { + type: Object, + required: true, + }, + }, + methods: { + updateVolume (ev: Event): void { + (this.modelValue as any).soundsVolume = (ev.target as HTMLInputElement).value + }, + decreaseVolume (): void { + const vol = parseInt(this.modelValue.soundsVolume, 10) - 5 + this.modelValue.soundsVolume = Math.max(0, vol) + }, + increaseVolume (): void { + const vol = parseInt(this.modelValue.soundsVolume, 10) + 5 + this.modelValue.soundsVolume = Math.min(100, vol) + }, }, created () { // TODO: ts type PlayerSettings @@ -40,3 +70,7 @@ export default defineComponent({ }, }) + diff --git a/src/frontend/game.ts b/src/frontend/game.ts index d83cf27..3322e99 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -439,6 +439,14 @@ export async function main( let finished = longFinished const justFinished = () => finished && !longFinished + const playerSoundVolume = (): number => { + const volume = localStorage.getItem('sound_volume') + if (volume === null) { + return 100 + } + const vol = parseInt(volume, 10) + return isNaN(vol) ? 100 : vol + } const playerSoundEnabled = (): boolean => { const enabled = localStorage.getItem('sound_enabled') if (enabled === null) { @@ -446,6 +454,13 @@ export async function main( } return enabled === '1' } + + const playClick = () => { + const vol = playerSoundVolume() + clickAudio.volume = vol / 100 + clickAudio.play() + } + const playerBgColor = () => { return (Game.getPlayerBgColor(gameId, clientId) || localStorage.getItem('bg_color') @@ -709,7 +724,7 @@ export async function main( ts, (playerId: string) => { if (playerSoundEnabled()) { - clickAudio.play() + playClick() } } ) @@ -889,6 +904,11 @@ export async function main( onSoundsEnabledChange: (value: boolean) => { localStorage.setItem('sound_enabled', value ? '1' : '0') }, + onSoundsVolumeChange: (value: number) => { + log.info('vol changed', value) + localStorage.setItem('sound_volume', `${value}`) + playClick() + }, replayOnSpeedUp, replayOnSpeedDown, replayOnPauseToggle, @@ -899,6 +919,7 @@ export async function main( color: playerColor(), name: playerName(), soundsEnabled: playerSoundEnabled(), + soundsVolume: playerSoundVolume(), }, disconnect: Communication.disconnect, connect: connect, diff --git a/src/frontend/views/Game.vue b/src/frontend/views/Game.vue index f8689b7..14594c2 100644 --- a/src/frontend/views/Game.vue +++ b/src/frontend/views/Game.vue @@ -71,6 +71,7 @@ export default defineComponent({ color: '', name: '', soundsEnabled: false, + soundsVolume: 100, }, previewImageUrl: '', setHotkeys: (v: boolean) => {}, @@ -78,6 +79,7 @@ export default defineComponent({ onColorChange: (v: string) => {}, onNameChange: (v: string) => {}, onSoundsEnabledChange: (v: boolean) => {}, + onSoundsVolumeChange: (v: number) => {}, connect: () => {}, disconnect: () => {}, unload: () => {}, @@ -100,6 +102,9 @@ export default defineComponent({ this.$watch(() => this.g.player.soundsEnabled, (value: boolean) => { this.g.onSoundsEnabledChange(value) }) + this.$watch(() => this.g.player.soundsVolume, (value: number) => { + this.g.onSoundsVolumeChange(value) + }) this.g = await main( `${this.$route.params.id}`, // @ts-ignore diff --git a/src/frontend/views/Replay.vue b/src/frontend/views/Replay.vue index 74bcbba..64f1dfb 100644 --- a/src/frontend/views/Replay.vue +++ b/src/frontend/views/Replay.vue @@ -77,6 +77,7 @@ export default defineComponent({ color: '', name: '', soundsEnabled: false, + soundsVolume: 100, }, previewImageUrl: '', setHotkeys: (v: boolean) => {}, @@ -84,6 +85,7 @@ export default defineComponent({ onColorChange: (v: string) => {}, onNameChange: (v: string) => {}, onSoundsEnabledChange: (v: boolean) => {}, + onSoundsVolumeChange: (v: number) => {}, replayOnSpeedUp: () => {}, replayOnSpeedDown: () => {}, replayOnPauseToggle: () => {}, @@ -115,6 +117,9 @@ export default defineComponent({ this.$watch(() => this.g.player.soundsEnabled, (value: boolean) => { this.g.onSoundsEnabledChange(value) }) + this.$watch(() => this.g.player.soundsVolume, (value: number) => { + this.g.onSoundsVolumeChange(value) + }) this.g = await main( `${this.$route.params.id}`, // @ts-ignore From ff69a5e1958e85dd372ab30c7b28ce7ac0806322 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sun, 6 Jun 2021 17:28:37 +0200 Subject: [PATCH 11/45] use ev.code instead of ev.key where possible to support different keyboard layouts --- build/public/assets/index.5791217a.js | 1 + ...{index.a9024809.css => index.5c03949d.css} | 2 +- build/public/assets/index.6b00de5a.js | 1 - build/public/index.html | 4 +-- src/frontend/components/TagsInput.vue | 4 +-- src/frontend/game.ts | 29 ++++++++++--------- 6 files changed, 21 insertions(+), 20 deletions(-) create mode 100644 build/public/assets/index.5791217a.js rename build/public/assets/{index.a9024809.css => index.5c03949d.css} (96%) delete mode 100644 build/public/assets/index.6b00de5a.js diff --git a/build/public/assets/index.5791217a.js b/build/public/assets/index.5791217a.js new file mode 100644 index 0000000..2311d28 --- /dev/null +++ b/build/public/assets/index.5791217a.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as x,s as C,u as k,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const P={id:"app"},I={key:0,class:"nav"},z=i("Index"),D=i("New game");T.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",P,[e.showNav?(s(),t("ul",I,[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((()=>[D])),_:1})])])):l("",!0),n(g)])};const E=864e5,_=e=>{const t=Math.floor(e/E);e%=E;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 M=1,V=1e3,N=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>_(t-e),B=_,U=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||N();return`${n} ${O(o,l)}`}}});const R={class:"game-info-text"},$=n("br",null,null,-1),G=n("br",null,null,-1),F=n("br",null,null,-1),L=i(" ↪️ Watch replay ");U.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),$,i(" 👥 "+r(e.game.players),1),G,i(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[L])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:U},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 W=n("h1",null,"Running games",-1),K=n("h1",null,"Finished games",-1);j.render=function(e,o,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(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)),K,(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 H=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});H.render=function(e,o,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y,Q,q,Z,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:H},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(Q=Y||(Y={}))[Q.Flat=0]="Flat",Q[Q.Out=1]="Out",Q[Q.In=-1]="In",(Z=q||(q={}))[Z.FINAL=0]="FINAL",Z[Z.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),s=le(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var se={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||q.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const ie={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};ie.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-a4fa5e7e");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ce,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-a4fa5e7e";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=n("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},ve=n("span",{class:"btn"},"Upload File",-1),we={class:"area-settings"},be=n("td",null,[n("label",null,"Title")],-1),xe=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ce=n("td",null,[n("label",null,"Tags")],-1),ke={class:"area-buttons"},Ae=i("🧩 Post to gallery "),Se=n("br",null,null,-1),Te=i(" + set up game");ge.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(s(),t("div",me,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",ye,[n("label",fe,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ve])]))],34),n("div",we,[n("table",null,[n("tr",null,[be,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),xe,n("tr",null,[Ce,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",ke,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ae,Se,Te],8,["disabled"])])])])};var Pe=e({name:"edit-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Ie={class:"area-image"},ze={class:"has-image"},De={class:"area-settings"},Ee=n("td",null,[n("label",null,"Title")],-1),_e=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Me=n("td",null,[n("label",null,"Tags")],-1),Ve={class:"area-buttons"};Pe.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Ie,[n("div",ze,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",De,[n("table",null,[n("tr",null,[Ee,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),_e,n("tr",null,[Me,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Ve,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Ne=e({name:"new-game-dialog",components:{ResponsiveImage:ie},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:q.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Oe={class:"area-image"},Be={class:"has-image"},Ue={key:0,class:"image-title"},Re={class:"area-settings"},$e=n("td",null,[n("label",null,"Pieces")],-1),Ge=n("td",null,[n("label",null,"Scoring: ")],-1),Fe=i(" Any (Score when pieces are connected to each other or on final location)"),Le=n("br",null,null,-1),je=i(" Final (Score when pieces are put to their final location)"),We=n("td",null,[n("label",null,"Shapes: ")],-1),Ke=i(" Normal"),He=n("br",null,null,-1),Ye=i(" Any (flat pieces can occur anywhere)"),Qe=n("br",null,null,-1),qe=i(" Flat (all pieces flat on all sides)"),Ze=n("td",null,[n("label",null,"Snapping: ")],-1),Xe=i(" Normal (pieces snap to final destination and to each other)"),Je=n("br",null,null,-1),et=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),tt={class:"area-buttons"};Ne.render=function(e,o,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Oe,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(s(),t("div",Ue,'"'+r(e.image.title)+'"',1)):l("",!0)]),n("div",Re,[n("table",null,[n("tr",null,[$e,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[Ge,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Fe]),Le,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),je])])]),n("tr",null,[We,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),Ke]),He,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ye]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),qe])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Xe]),Je,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),et])])])])]),n("div",tt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var nt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Pe,NewGameDialog:Ne},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${se.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const ot={class:"upload-image-teaser"},lt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),at={key:0},st=i(" Tags: "),it=i(" Sort by: "),rt=n("option",{value:"date_desc"},"Newest first",-1),dt=n("option",{value:"date_asc"},"Oldest first",-1),ct=n("option",{value:"alpha_asc"},"A-Z",-1),ut=n("option",{value:"alpha_desc"},"Z-A",-1);nt.render=function(e,o,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[n("div",ot,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),lt]),n("div",null,[e.tags.length>0?(s(),t("label",at,[st,(s(!0),t(d,null,c(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[it,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[rt,dt,ct,ut],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var pt=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 gt={class:"scores"},ht=n("div",null,"Scores",-1),mt=n("td",null,"⚡",-1),yt=n("td",null,"💤",-1);pt.render=function(e,o,l,a,i,u){return s(),t("div",gt,[ht,n("table",null,[(s(!0),t(d,null,c(e.actives,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[mt,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}},[yt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var ft=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 B(this.duration)}}});const vt={class:"timer"};ft.render=function(e,o,l,a,i,d){return s(),t("div",vt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var wt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const bt=m();y("data-v-a1d1c822");const xt=n("td",null,[n("label",null,"Background: ")],-1),Ct=n("td",null,[n("label",null,"Color: ")],-1),kt=n("td",null,[n("label",null,"Name: ")],-1),At=n("td",null,[n("label",null,"Sounds: ")],-1),St=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Tt={class:"sound-volume"};f();const Pt=bt(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[9]||(o[9]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[8]||(o[8]=u((()=>{}),["stop"]))},[n("tr",null,[xt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[Ct,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[kt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[At,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[x,e.modelValue.soundsEnabled]])])]),n("tr",null,[St,n("td",Tt,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])])])]))));wt.render=Pt,wt.__scopeId="data-v-a1d1c822";var It=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const zt={class:"preview"};It.render=function(e,o,l,a,i,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",zt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Dt=1,Et=4,_t=2,Mt=3,Vt=2,Nt=4,Ot=3,Bt=9,Ut=1,Rt=2,$t=3,Gt=4,Ft=5,Lt=6,jt=7,Wt=8,Kt=10,Ht=11,Yt=12,Qt=13,qt=14,Zt=1,Xt=2,Jt=3;const en=ae("Communication.js");let tn,nn=[],on=e=>{nn.push(e)},ln=[],an=e=>{ln.push(e)};let sn=0;const rn=e=>{sn!==e&&(sn=e,an(e))};function dn(e){if(2===sn)try{tn.send(JSON.stringify(e))}catch(t){en.info("unable to send message.. maybe because ws is invalid?")}}let cn,un;var pn={connect:function(e,t,n){return cn=0,un={},rn(3),new Promise((o=>{tn=new WebSocket(e,n+"|"+t),tn.onopen=()=>{rn(2),dn([Mt])},tn.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Et){const e=t[1];o(e)}else{if(l!==Dt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&un[o])return void delete un[o];on(t)}}},tn.onerror=()=>{throw rn(1),"[ 2021-05-15 onerror ]"},tn.onclose=e=>{4e3===e.code||1001===e.code?rn(4):rn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${se.asQueryArgs(n)}`);return await o.json()},disconnect:function(){tn&&tn.close(4e3),cn=0,un={}},sendClientEvent:function(e){cn++,un[cn]=e,dn([_t,cn,un[cn]])},onServerChange:function(e){on=e;for(const t of nn)on(t);nn=[]},onConnectionStateChange:function(e){an=e;for(const t of ln)an(t);ln=[]},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},gn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===pn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===pn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const hn={key:0,class:"overlay connection-lost"},mn={key:0,class:"overlay-content"},yn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),fn={key:1,class:"overlay-content"},vn=n("div",null,"Connecting...",-1);gn.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",hn,[e.lostConnection?(s(),t("div",mn,[yn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",fn,[vn])):l("",!0)])):l("",!0)};var wn=e({name:"help-overlay",emits:{bgclick:null}});const bn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),xn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),Cn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),kn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),An=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Sn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Tn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),Pn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),In=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),zn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Dn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),En=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),_n=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),Mn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);wn.render=function(e,o,l,a,i,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]=u((()=>{}),["stop"]))},[bn,xn,Cn,kn,An,Sn,Tn,Pn,In,zn,Dn,En,_n,Mn])])};var Vn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),On=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Bn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Un=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Rn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),s=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),i=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:i,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function $n(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Gn={createCanvas:$n,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const o=$n(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=$n(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(),o}};const Fn=ae("Debug.js");let Ln=0,jn=0;var Wn=e=>{Ln=performance.now(),jn=e},Kn=e=>{const t=performance.now(),n=t-Ln;n>jn&&Fn.log(e+": "+n),Ln=t};function Hn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function Yn(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var Qn={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:Hn,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:Yn,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 Hn(Yn(e),Yn(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const qn=ae("PuzzleGraphics.js");function Zn(e,t){const n=se.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Xn={loadPuzzleBitmaps:async function(e){const t=await Gn.loadImageToBitmap(e.info.imageUrl),n=await Gn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){qn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=Qn.pointAdd(a,{x:o,y:0}),c=Qn.pointAdd(r,{x:0,y:o}),u=Qn.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;ose.decodePiece(Jn[e].puzzle.tiles[t]),mo=(e,t)=>ho(e,t).group,yo=(e,t)=>{const n=Jn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},fo=(e,t)=>{const n=Jn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return Qn.pointAdd(o,l)},vo=(e,t)=>ho(e,t).pos,wo=e=>{const t=Oo(e),n=Bo(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}},bo=(e,t)=>{const n=Ao(e),o=ho(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},xo=(e,t)=>ho(e,t).z,Co=(e,t)=>{for(const n of Jn[e].puzzle.tiles){const e=se.decodePiece(n);if(e.owner===t)return e.idx}return-1},ko=e=>Jn[e].puzzle.info.tileDrawSize,Ao=e=>Jn[e].puzzle.info.tileSize,So=e=>Jn[e].puzzle.data.maxGroup,To=e=>Jn[e].puzzle.data.maxZ;function Po(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Io=(e,t,n)=>{for(const o of t)go(e,o,{z:n})},zo=(e,t,n)=>{const o=vo(e,t);go(e,t,{pos:Qn.pointAdd(o,n)})},Do=(e,t,n)=>{const o=ko(e),l=wo(e),a=n;for(const s of t){const t=ho(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)zo(e,s,a)},Eo=(e,t)=>ho(e,t).owner,_o=(e,t)=>{for(const n of t)go(e,n,{owner:-1,z:1})},Mo=(e,t,n)=>{for(const o of t)go(e,o,{owner:n})};function Vo(e,t){const n=Jn[e].puzzle.tiles,o=se.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=se.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const No=(e,t)=>{const n=to(e,t);return n?n.points:0},Oo=e=>Jn[e].puzzle.info.table.width,Bo=e=>Jn[e].puzzle.info.table.height;var Uo={setGame:function(e,t){Jn[e]=t},exists:function(e){return!!Jn[e]||!1},playerExists:oo,getActivePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){oo(e,t)?uo(e,t,{ts:n}):no(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:co,getPieceCount:ao,getImageUrl:function(e){return Jn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Jn[e].puzzle.info.imageUrl=t},get:function(e){return Jn[e]||null},getAllGames:function(){return Object.values(Jn).sort(((e,t)=>ro(e.id)===ro(t.id)?t.puzzle.data.started-e.puzzle.data.started:ro(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=to(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=to(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=to(e,t);return n?n.name:null},getPlayerIndexById:eo,getPlayerIdByIndex:function(e,t){return Jn[e].players.length>t?se.decodePlayer(Jn[e].players[t]).id:null},changePlayer:uo,setPlayer:no,setPiece:function(e,t,n){Jn[e].puzzle.tiles[t]=se.encodePiece(n)},setPuzzleData:function(e,t){Jn[e].puzzle.data=t},getTableWidth:Oo,getTableHeight:Bo,getPuzzle:e=>Jn[e].puzzle,getRng:e=>Jn[e].rng.obj,getPuzzleWidth:e=>Jn[e].puzzle.info.width,getPuzzleHeight:e=>Jn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Jn[e].puzzle.tiles.map(se.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Co(e,t);return n<0?null:Jn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Jn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:ko,getFinalPiecePos:fo,getStartTs:e=>Jn[e].puzzle.data.started,getFinishTs:e=>Jn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Jn[e].puzzle,s=function(e,t){return t in Jn[e].evtInfos?Jn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([Zt,a.data])},d=t=>{i.push([Xt,se.encodePiece(ho(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=to(e,t);n&&i.push([Jt,se.encodePlayer(n)])},p=n[0];if(p===Lt){const l=n[1];uo(e,t,{bgcolor:l,ts:o}),u()}else if(p===jt){const l=n[1];uo(e,t,{color:l,ts:o}),u()}else if(p===Wt){const l=`${n[1]}`.substr(0,16);uo(e,t,{name:l,ts:o}),u()}else if(p===Bt){const l=n[1],a=n[2],s=to(e,t);if(s){const n=s.x-l,i=s.y-a;uo(e,t,{ts:o,x:n,y:i}),u()}}else if(p===Ut){const l={x:n[1],y:n[2]};uo(e,t,{d:1,ts:o}),u(),s._last_mouse_down=l;const a=((e,t)=>{const n=Jn[e].puzzle.info,o=Jn[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=To(e)+1;po(e,{maxZ:n}),r();const o=Vo(e,a);Io(e,o,To(e)),Mo(e,o,t),c(o)}s._last_mouse=l}else if(p===$t){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)uo(e,t,{x:l,y:a,ts:o}),u();else{const n=Co(e,t);if(n>=0){uo(e,t,{x:l,y:a,ts:o}),u();const r=Vo(e,n);let d=Qn.pointInBounds(i,wo(e))&&Qn.pointInBounds(s._last_mouse_down,wo(e));for(const t of r){const n=bo(e,t);if(Qn.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Do(e,r,{x:t,y:n}),c(r)}}else uo(e,t,{ts:o}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===Rt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Co(e,t);if(g>=0){const n=Vo(e,g);Mo(e,n,0),c(n);const s=vo(e,g),i=fo(e,g);let h=!1;if(io(e)===ee.REAL){for(const t of n)if(yo(e,t)){h=!0;break}}else h=!0;if(h&&Qn.pointDistance(i,s){const l=Jn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=mo(e,t),l=mo(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=vo(e,t),s=Qn.pointAdd(vo(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(Qn.pointDistance(a,s){const o=Jn[e].puzzle.tiles,l=mo(e,t),a=mo(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(po(e,{maxGroup:So(e)+1}),r(),s=So(e));if(go(e,t,{group:s}),d(t),go(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=se.decodePiece(r);i.includes(t.group)&&(go(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=Vo(e,t),((e,t)=>-1===Eo(e,t))(e,n))_o(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=xo(e,o);t>n&&(n=t)}return n})(e,l);Io(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Vo(e,g)){const o=Po(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])){a=!0;break}}if(a&&so(e)===q.ANY){const n=No(e,t)+1;uo(e,t,{d:p,ts:o,points:n}),u()}else uo(e,t,{d:p,ts:o}),u();a&&io(e)===ee.REAL&&co(e)===ao(e)&&(po(e,{finished:o}),r()),a&&l&&l(t)}}else uo(e,t,{d:p,ts:o}),u();s._last_mouse=i}else if(p===Gt){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else if(p===Ft){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else uo(e,t,{ts:o}),u();return function(e,t,n){Jn[e].evtInfos[t]=n}(e,t,s),i}};let Ro=-10,$o=20,Go=2,Fo=15;class Lo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Ro+Math.random()*$o,this.vy=-1*(Go+Math.random()*Fo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Go=t/2,Fo=t-Go;const n=1/4*this.canvas.width/(t/2);Ro=-n,$o=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Lo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Lo(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(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Gn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Zo=!0})),t}(l,Gn.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};pn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await pn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let x=()=>0;const C=async()=>{if("play"===o){const o=await pn.connect(n,e,t),l=se.decodeGame(o);Uo.setGame(l.id,l),x=()=>N()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=se.decodeGame(t.game);Uo.setGame(n.id,n),w.lastRealTs=N(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,x=()=>w.lastGameTs}}Zo=!0};await C();const k=Uo.getPieceDrawOffset(e),A=Uo.getPieceDrawSize(e),S=Uo.getPuzzleWidth(e),T=Uo.getPuzzleHeight(e),P=Uo.getTableWidth(e),I=Uo.getTableHeight(e),z={x:(P-S)/2,y:(I-T)/2},D={w:S,h:T},E={w:A,h:A},_=await Xn.loadPuzzleBitmaps(Uo.getPuzzle(e)),V=new Wo(v,Uo.getRng(e));V.init();const O=v.getContext("2d");v.classList.add("loaded");const B=Rn();B.move(-(P-v.width)/2,-(I-v.height)/2);const U=function(e,t,n,o){let l=[],a=!0,s=!1,i=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Ut,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Rt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([$t,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Gt:Ft;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([Kt]),"replay"===o&&("KeyI"===e.code&&v([Qt]),"KeyO"===e.code&&v([qt]),"KeyP"===e.code&&v([Yt])),"KeyF"===e.code&&(Qo=!Qo,Zo=!0),"KeyG"===e.code&&(qo=!qo,Zo=!0),"KeyM"===e.code&&v([Ht]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});v([Bt,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Gt,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([Ft,...e])}},setHotkeys:e=>{a=e}}}(v,window,B,o),R=Uo.getImageUrl(e),$=()=>{const t=Uo.getStartTs(e),n=Uo.getFinishTs(e),o=x();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),a.setPiecesTotal(Uo.getPieceCount(e));const G=x();a.setActivePlayers(Uo.getActivePlayers(e,G)),a.setIdlePlayers(Uo.getIdlePlayers(e,G));const F=!!Uo.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_volume");if(null===e)return 100;const t=parseInt(e,10);return isNaN(t)?100:t},K=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},H=()=>{const e=W();i.volume=e/100,i.play()},Y=()=>Uo.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Q=()=>Uo.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let q="",Z="",X=!1;const J=e=>{X=e;const[t,n]=e?[q,"grab"]:[Z,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},ee=e=>{q=Gn.colorizedCanvas(r,c,e).toDataURL(),Z=Gn.colorizedCanvas(d,u,e).toDataURL(),J(X)};ee(Q());const te=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ne=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,te())},le=()=>{w.paused=!w.paused,te()},ae=[];let ie;let re;if("play"===o?ae.push(setInterval((()=>{$()}),1e3)):"replay"===o&&te(),"play"===o)pn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Jt:{const n=se.decodePlayer(a);n.id!==t&&(Uo.setPlayer(e,n.id,n),Zo=!0)}break;case Xt:{const t=se.decodePiece(a);Uo.setPiece(e,t.idx,t),Zo=!0}break;case Zt:Uo.setPuzzleData(e,a),Zo=!0}L=!!Uo.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===Vt){const t=o[1];return Uo.addPlayer(e,t,n),!0}if(o[0]===Nt){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Uo.addPlayer(e,t,n),!0}if(o[0]===Ot){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Uo.handleInput(e,t,l,n),!0}return!1};let n=w.lastGameTs;const o=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=N();if(w.paused)return w.lastRealTs=l,void(ie=setTimeout(o,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const o=w.log[w.logPointer],l=n+o[o.length-1],a=w.log[e],i=a[a.length-1],r=l+i;if(r>s){w.skipNonActionPhases&&s+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/o),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{U.createKeyEvents();for(const n of U.consumeAll())if("play"===o){const o=n[0];if(o===Bt){const e=n[1],t=n[2],o=B.worldDimToViewport({w:e,h:t});Zo=!0,B.move(o.w,o.h)}else if(o===$t){if(de&&!Uo.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Zo=!0,B.move(o,l),de=t}}else if(o===jt)ee(n[1]);else if(o===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e),J(!0)}else if(o===Rt)de=null,J(!1);else if(o===Gt){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("in",B.worldToViewport(e))}else if(o===Ft){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("out",B.worldToViewport(e))}else o===Kt?a.togglePreview():o===Ht&&a.toggleSoundsEnabled();const l=x();Uo.handleInput(e,t,n,l,(e=>{K()&&H()})).length>0&&(Zo=!0),pn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===Yt)le();else if(e===qt)oe();else if(e===Qt)ne();else if(e===Bt){const e=n[1],t=n[2];Zo=!0,B.move(e,t)}else if(e===$t){if(de){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Zo=!0,B.move(o,l),de=t}}else if(e===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e)}else if(e===Rt)de=null;else if(e===Gt){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("in",B.worldToViewport(e))}else if(e===Ft){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("out",B.worldToViewport(e))}else e===Kt&&a.togglePreview()}L=!!Uo.getFinishTs(e),j()&&(V.update(),Zo=!0)},render:async()=>{if(!Zo)return;const n=x();let l,s,i;window.DEBUG&&Wn(0),O.fillStyle=Y(),O.fillRect(0,0,v.width,v.height),window.DEBUG&&Kn("clear done"),l=B.worldToViewportRaw(z),s=B.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&Kn("board done");const r=Uo.getPiecesSortedByZIndex(e);window.DEBUG&&Kn("get tiles done"),s=B.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?Qo:qo)&&(i=_[e.idx],l=B.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&Kn("tiles done");const d=[];for(const a of Uo.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(i=await f(a),l=B.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,o]of d)O.fillText(e,t,o);window.DEBUG&&Kn("players done"),a.setActivePlayers(Uo.getActivePlayers(e,n)),a.setIdlePlayers(Uo.getIdlePlayers(e,n)),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),window.DEBUG&&Kn("HUD done"),j()&&V.render(),Zo=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([Lt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([jt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Wt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},onSoundsVolumeChange:e=>{Ko.info("vol changed",e),localStorage.setItem("sound_volume",`${e}`),H()},replayOnSpeedUp:ne,replayOnSpeedDown:oe,replayOnPauseToggle:le,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:R,player:{background:Y(),color:Q(),name:Uo.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:K(),soundsVolume:W()},disconnect:pn.disconnect,connect:C,unload:()=>{ae.forEach((e=>{clearInterval(e)})),ie&&clearTimeout(ie),re&&re.stop()}}}var Jo=e({name:"game",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:It,ConnectionOverlay:gn,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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)},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 el={id:"game"},tl={class:"menu"},nl={class:"tabs"},ol=i("🧩 Puzzles");Jo.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",el,[p(n(u,{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"]),[[C,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",tl,[n("div",nl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[ol])),_: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 ll=e({name:"replay",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:It,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 al={id:"replay"},sl=i("Skip no action phases: "),il={class:"menu"},rl={class:"tabs"},dl=i("🧩 Puzzles");ll.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",al,[p(n(g,{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"]),[[C,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[sl,p(n("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[x,e.skipNoAction]])])]),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",il,[n("div",rl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[dl])),_:1}),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:l[12]||(l[12]=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=k({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:nt},{name:"game",path:"/g/:id",component:Jo},{name:"replay",path:"/replay/:id",component:ll}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=S(T);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=se.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); diff --git a/build/public/assets/index.a9024809.css b/build/public/assets/index.5c03949d.css similarity index 96% rename from build/public/assets/index.a9024809.css rename to build/public/assets/index.5c03949d.css index 85eeda7..20f51c8 100644 --- a/build/public/assets/index.a9024809.css +++ b/build/public/assets/index.5c03949d.css @@ -1 +1 @@ -:root{--main-color:#c1b19f;--main-darker-color:#4f4e4c;--link-color:#808db0;--link-hover-color:#c5cfeb;--highlight-color:#dd7e7e;--positive-color:#64a756;--input-bg-color:#262523;--bg-color:rgba(0,0,0,.7)}body,html{margin:0;background:#2b2b2b;color:var(--main-color);height:100%}*{font-family:monospace;font-size:15px}h1,h2,h3,h4{font-size:20px}a{color:var(--link-color);text-decoration:none}a:hover{color:var(--link-hover-color)}td,th{vertical-align:top}.btn{display:inline-block;background:var(--input-bg-color);color:var(--link-color);border:solid 1px #000;padding:5px 10px;box-shadow:1px 1px 2px rgba(0,0,0,.5),0 0 1px rgba(150,150,150,.4) inset;border-radius:4px;user-select:none}.btn-big{font-size:1.5em;padding:10px 20px}.btn:hover{background:#2f2e2c;color:var(--link-hover-color);border:solid 1px #111;box-shadow:0 0 1px rgba(150,150,150,.4) inset;cursor:pointer}.btn:disabled{background:#2f2e2c;color:#8c4747!important;border:solid 1px #111;box-shadow:0 0 1px rgba(150,150,150,.4) inset;cursor:not-allowed}input{background:#333230;border-radius:4px;color:var(--main-color);padding:6px 10px;border:solid 1px #000;box-shadow:0 0 3px rgba(0,0,0,.3) inset}input:focus{border:solid 1px #686767;background:var(--input-bg-color)}.scores{position:absolute;right:0;top:0;background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7)}.timer{position:absolute;left:0;top:0;background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7)}.menu{position:absolute;top:0;left:50%;transform:translateX(-50%);background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7);z-index:2}.closed{display:none}.overlay{position:fixed;top:0;left:0;right:0;bottom:0;z-index:10;background:var(--bg-color)}.overlay.transparent{background:0 0}.overlay-content{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7);z-index:1}.connection-lost .overlay-content{padding:20px;text-align:center}.preview{position:absolute;top:20px;left:20px;bottom:20px;right:20px}.preview .img{height:100%;width:100%;position:absolute;background-repeat:no-repeat;background-position:center;background-size:contain}.menu .opener{display:inline-block;margin-right:10px;color:var(--link-color)}.menu .opener:last-child{margin-right:0}.menu .opener:hover{color:var(--link-hover-color);cursor:pointer}kbd{background-color:#eee;border-radius:3px;border:1px solid #b4b4b4;box-shadow:0 1px 1px rgba(0,0,0,.2),0 2px 0 0 rgba(255,255,255,.7) inset;color:#333;display:inline-block;font-size:.85em;font-weight:700;line-height:1;padding:2px 4px;white-space:nowrap}.hint{color:var(--main-darker-color)}.bit{background:#3b3737;border-radius:.5em;padding:.25em .5em;display:inline-block;margin:0 .25em .25em 0;cursor:pointer}.bit.on{color:var(--positive-color)}.upload-image-teaser{text-align:center}.upload-image-teaser .btn{margin-bottom:.5em}table label{line-height:32px}.nav{list-style:none;padding:0}.nav li{display:inline-block;margin-right:1em}.image-list{overflow:scroll}.image-list-inner{white-space:nowrap}.imageteaser{width:150px;height:100px;display:inline-block;margin:5px;background-size:contain;background-position:center;background-repeat:no-repeat;background-color:#222;cursor:pointer}.game-teaser-wrap{display:inline-block;width:20%;padding:5px;box-sizing:border-box}.game-teaser{display:block;background-repeat:no-repeat;background-position:center;background-size:contain;position:relative;padding-top:56.25%;width:100%;background-color:#222}.game-info{position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%}.game-info-text{position:absolute;top:0;background:var(--bg-color);padding:5px}.game-replay{position:absolute;top:0;right:0}html.view-game{overflow:hidden}html.view-game body{overflow:hidden}html.view-replay{overflow:hidden}html.view-replay body{overflow:hidden}.imageteaser{position:relative}.imageteaser .edit{display:none;position:absolute}.imageteaser:hover .edit{display:inline-block}.input[data-v-39ed99c7]{margin-bottom:.5em}.autocomplete[data-v-39ed99c7]{position:relative}.autocomplete ul[data-v-39ed99c7]{list-style:none;padding:0;margin:0;position:absolute;left:0;right:0;background:#333230;top:-.5em}.autocomplete ul li[data-v-39ed99c7]{position:relative;padding:.5em .5em .5em 1.5em;cursor:pointer}.autocomplete ul li.active[data-v-39ed99c7]{color:var(--link-hover-color);background:var(--input-bg-color)}.autocomplete ul li.active[data-v-39ed99c7]:before{content:'▶';display:block;position:absolute;left:.5em}.new-image-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}@media (max-width:1400px){.new-image-dialog .overlay-content{grid-template-columns:auto;grid-template-rows:1fr min-content min-content;grid-template-areas:"image" "settings" "buttons"}.new-image-dialog .overlay-content .area-buttons .btn br{display:none}}.new-image-dialog .area-image{grid-area:image;margin:.5em;border:solid 6px transparent}.new-image-dialog .area-image.no-image{align-content:center;display:grid;text-align:center;border:solid 6px;position:relative}.new-image-dialog .area-image.droppable{border:dashed 6px}.new-image-dialog .area-image .has-image{position:relative;width:100%;height:100%}.new-image-dialog .area-image .has-image .remove{position:absolute;top:.5em;left:.5em}.new-image-dialog .area-settings{grid-area:settings}.new-image-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.new-image-dialog .area-buttons{align-self:end;grid-area:buttons}.new-image-dialog .area-buttons button{width:100%;margin-top:.5em}.new-image-dialog .upload{position:absolute;top:0;left:0;right:0;bottom:0;cursor:pointer}.new-image-dialog .upload .btn{position:absolute;top:50%;transform:translate(-50%,-50%)}.area-image .drop-target{display:none}.area-image.droppable .drop-target{pointer-events:none;position:absolute;top:0;left:0;right:0;bottom:0;z-index:3}.edit-image-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}.edit-image-dialog .area-image{grid-area:image;margin:20px}.edit-image-dialog .area-image.no-image{align-content:center;display:grid;text-align:center;border:dashed 6px;position:relative}.edit-image-dialog .area-image .has-image{position:relative;width:100%;height:100%}.edit-image-dialog .area-image .has-image .remove{position:absolute;top:.5em;left:.5em}.edit-image-dialog .area-settings{grid-area:settings}.edit-image-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.edit-image-dialog .area-buttons{align-self:end;grid-area:buttons}.edit-image-dialog .area-buttons button{width:100%;margin-top:.5em}.new-game-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}.new-game-dialog .area-image{grid-area:image;display:grid;grid-template-rows:1fr min-content;grid-template-areas:"image" "image-title";margin-right:1em}@media (max-width:1400px){.new-game-dialog .overlay-content{grid-template-columns:auto;grid-template-rows:1fr min-content min-content;grid-template-areas:"image" "settings" "buttons"}.new-game-dialog .area-image{margin-right:0}}.new-game-dialog .area-settings{grid-area:settings}.new-game-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.new-game-dialog .area-buttons{align-self:end;grid-area:buttons}.new-game-dialog .area-buttons button{width:100%}.new-game-dialog .has-image{box-sizing:border-box;grid-area:image;position:relative;width:100%;height:100%;border:solid 1px}.new-game-dialog .image-title{grid-area:image-title;text-align:center;padding:.5em 0;background:var(--main-color);color:#262523}.new-game-dialog .has-image .remove{position:absolute;top:.5em;left:.5em}.sound-volume span[data-v-a1d1c822]{cursor:pointer;user-select:none}.sound-volume input[data-v-a1d1c822]{vertical-align:middle} \ No newline at end of file +:root{--main-color:#c1b19f;--main-darker-color:#4f4e4c;--link-color:#808db0;--link-hover-color:#c5cfeb;--highlight-color:#dd7e7e;--positive-color:#64a756;--input-bg-color:#262523;--bg-color:rgba(0,0,0,.7)}body,html{margin:0;background:#2b2b2b;color:var(--main-color);height:100%}*{font-family:monospace;font-size:15px}h1,h2,h3,h4{font-size:20px}a{color:var(--link-color);text-decoration:none}a:hover{color:var(--link-hover-color)}td,th{vertical-align:top}.btn{display:inline-block;background:var(--input-bg-color);color:var(--link-color);border:solid 1px #000;padding:5px 10px;box-shadow:1px 1px 2px rgba(0,0,0,.5),0 0 1px rgba(150,150,150,.4) inset;border-radius:4px;user-select:none}.btn-big{font-size:1.5em;padding:10px 20px}.btn:hover{background:#2f2e2c;color:var(--link-hover-color);border:solid 1px #111;box-shadow:0 0 1px rgba(150,150,150,.4) inset;cursor:pointer}.btn:disabled{background:#2f2e2c;color:#8c4747!important;border:solid 1px #111;box-shadow:0 0 1px rgba(150,150,150,.4) inset;cursor:not-allowed}input{background:#333230;border-radius:4px;color:var(--main-color);padding:6px 10px;border:solid 1px #000;box-shadow:0 0 3px rgba(0,0,0,.3) inset}input:focus{border:solid 1px #686767;background:var(--input-bg-color)}.scores{position:absolute;right:0;top:0;background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7)}.timer{position:absolute;left:0;top:0;background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7)}.menu{position:absolute;top:0;left:50%;transform:translateX(-50%);background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7);z-index:2}.closed{display:none}.overlay{position:fixed;top:0;left:0;right:0;bottom:0;z-index:10;background:var(--bg-color)}.overlay.transparent{background:0 0}.overlay-content{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7);z-index:1}.connection-lost .overlay-content{padding:20px;text-align:center}.preview{position:absolute;top:20px;left:20px;bottom:20px;right:20px}.preview .img{height:100%;width:100%;position:absolute;background-repeat:no-repeat;background-position:center;background-size:contain}.menu .opener{display:inline-block;margin-right:10px;color:var(--link-color)}.menu .opener:last-child{margin-right:0}.menu .opener:hover{color:var(--link-hover-color);cursor:pointer}kbd{background-color:#eee;border-radius:3px;border:1px solid #b4b4b4;box-shadow:0 1px 1px rgba(0,0,0,.2),0 2px 0 0 rgba(255,255,255,.7) inset;color:#333;display:inline-block;font-size:.85em;font-weight:700;line-height:1;padding:2px 4px;white-space:nowrap}.hint{color:var(--main-darker-color)}.bit{background:#3b3737;border-radius:.5em;padding:.25em .5em;display:inline-block;margin:0 .25em .25em 0;cursor:pointer}.bit.on{color:var(--positive-color)}.upload-image-teaser{text-align:center}.upload-image-teaser .btn{margin-bottom:.5em}table label{line-height:32px}.nav{list-style:none;padding:0}.nav li{display:inline-block;margin-right:1em}.image-list{overflow:scroll}.image-list-inner{white-space:nowrap}.imageteaser{width:150px;height:100px;display:inline-block;margin:5px;background-size:contain;background-position:center;background-repeat:no-repeat;background-color:#222;cursor:pointer}.game-teaser-wrap{display:inline-block;width:20%;padding:5px;box-sizing:border-box}.game-teaser{display:block;background-repeat:no-repeat;background-position:center;background-size:contain;position:relative;padding-top:56.25%;width:100%;background-color:#222}.game-info{position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%}.game-info-text{position:absolute;top:0;background:var(--bg-color);padding:5px}.game-replay{position:absolute;top:0;right:0}html.view-game{overflow:hidden}html.view-game body{overflow:hidden}html.view-replay{overflow:hidden}html.view-replay body{overflow:hidden}.imageteaser{position:relative}.imageteaser .edit{display:none;position:absolute}.imageteaser:hover .edit{display:inline-block}.input[data-v-a4fa5e7e]{margin-bottom:.5em}.autocomplete[data-v-a4fa5e7e]{position:relative}.autocomplete ul[data-v-a4fa5e7e]{list-style:none;padding:0;margin:0;position:absolute;left:0;right:0;background:#333230;top:-.5em}.autocomplete ul li[data-v-a4fa5e7e]{position:relative;padding:.5em .5em .5em 1.5em;cursor:pointer}.autocomplete ul li.active[data-v-a4fa5e7e]{color:var(--link-hover-color);background:var(--input-bg-color)}.autocomplete ul li.active[data-v-a4fa5e7e]:before{content:'▶';display:block;position:absolute;left:.5em}.new-image-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}@media (max-width:1400px){.new-image-dialog .overlay-content{grid-template-columns:auto;grid-template-rows:1fr min-content min-content;grid-template-areas:"image" "settings" "buttons"}.new-image-dialog .overlay-content .area-buttons .btn br{display:none}}.new-image-dialog .area-image{grid-area:image;margin:.5em;border:solid 6px transparent}.new-image-dialog .area-image.no-image{align-content:center;display:grid;text-align:center;border:solid 6px;position:relative}.new-image-dialog .area-image.droppable{border:dashed 6px}.new-image-dialog .area-image .has-image{position:relative;width:100%;height:100%}.new-image-dialog .area-image .has-image .remove{position:absolute;top:.5em;left:.5em}.new-image-dialog .area-settings{grid-area:settings}.new-image-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.new-image-dialog .area-buttons{align-self:end;grid-area:buttons}.new-image-dialog .area-buttons button{width:100%;margin-top:.5em}.new-image-dialog .upload{position:absolute;top:0;left:0;right:0;bottom:0;cursor:pointer}.new-image-dialog .upload .btn{position:absolute;top:50%;transform:translate(-50%,-50%)}.area-image .drop-target{display:none}.area-image.droppable .drop-target{pointer-events:none;position:absolute;top:0;left:0;right:0;bottom:0;z-index:3}.edit-image-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}.edit-image-dialog .area-image{grid-area:image;margin:20px}.edit-image-dialog .area-image.no-image{align-content:center;display:grid;text-align:center;border:dashed 6px;position:relative}.edit-image-dialog .area-image .has-image{position:relative;width:100%;height:100%}.edit-image-dialog .area-image .has-image .remove{position:absolute;top:.5em;left:.5em}.edit-image-dialog .area-settings{grid-area:settings}.edit-image-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.edit-image-dialog .area-buttons{align-self:end;grid-area:buttons}.edit-image-dialog .area-buttons button{width:100%;margin-top:.5em}.new-game-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}.new-game-dialog .area-image{grid-area:image;display:grid;grid-template-rows:1fr min-content;grid-template-areas:"image" "image-title";margin-right:1em}@media (max-width:1400px){.new-game-dialog .overlay-content{grid-template-columns:auto;grid-template-rows:1fr min-content min-content;grid-template-areas:"image" "settings" "buttons"}.new-game-dialog .area-image{margin-right:0}}.new-game-dialog .area-settings{grid-area:settings}.new-game-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.new-game-dialog .area-buttons{align-self:end;grid-area:buttons}.new-game-dialog .area-buttons button{width:100%}.new-game-dialog .has-image{box-sizing:border-box;grid-area:image;position:relative;width:100%;height:100%;border:solid 1px}.new-game-dialog .image-title{grid-area:image-title;text-align:center;padding:.5em 0;background:var(--main-color);color:#262523}.new-game-dialog .has-image .remove{position:absolute;top:.5em;left:.5em}.sound-volume span[data-v-a1d1c822]{cursor:pointer;user-select:none}.sound-volume input[data-v-a1d1c822]{vertical-align:middle} \ No newline at end of file diff --git a/build/public/assets/index.6b00de5a.js b/build/public/assets/index.6b00de5a.js deleted file mode 100644 index 9cc0cea..0000000 --- a/build/public/assets/index.6b00de5a.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as k,s as x,u as C,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const P={id:"app"},I={key:0,class:"nav"},z=i("Index"),D=i("New game");T.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",P,[e.showNav?(s(),t("ul",I,[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((()=>[D])),_:1})])])):l("",!0),n(g)])};const _=864e5,E=e=>{const t=Math.floor(e/_);e%=_;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 M=1,V=1e3,N=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>E(t-e),B=E,U=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||N();return`${n} ${O(o,l)}`}}});const R={class:"game-info-text"},$=n("br",null,null,-1),G=n("br",null,null,-1),F=n("br",null,null,-1),L=i(" ↪️ Watch replay ");U.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),$,i(" 👥 "+r(e.game.players),1),G,i(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[L])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:U},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 W=n("h1",null,"Running games",-1),H=n("h1",null,"Finished games",-1);j.render=function(e,o,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(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)),H,(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 Y=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});Y.render=function(e,o,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var q,Q,Z,K,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:Y},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(Q=q||(q={}))[Q.Flat=0]="Flat",Q[Q.Out=1]="Out",Q[Q.In=-1]="In",(K=Z||(Z={}))[K.FINAL=0]="FINAL",K[K.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),s=le(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var se={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Z.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const ie={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};ie.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.key&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-39ed99c7");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ce,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-39ed99c7";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=n("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},ve=n("span",{class:"btn"},"Upload File",-1),we={class:"area-settings"},be=n("td",null,[n("label",null,"Title")],-1),ke=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),xe=n("td",null,[n("label",null,"Tags")],-1),Ce={class:"area-buttons"},Ae=i("🧩 Post to gallery "),Se=n("br",null,null,-1),Te=i(" + set up game");ge.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(s(),t("div",me,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",ye,[n("label",fe,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ve])]))],34),n("div",we,[n("table",null,[n("tr",null,[be,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),ke,n("tr",null,[xe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Ce,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ae,Se,Te],8,["disabled"])])])])};var Pe=e({name:"edit-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Ie={class:"area-image"},ze={class:"has-image"},De={class:"area-settings"},_e=n("td",null,[n("label",null,"Title")],-1),Ee=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Me=n("td",null,[n("label",null,"Tags")],-1),Ve={class:"area-buttons"};Pe.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Ie,[n("div",ze,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",De,[n("table",null,[n("tr",null,[_e,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ee,n("tr",null,[Me,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Ve,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Ne=e({name:"new-game-dialog",components:{ResponsiveImage:ie},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Z.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Oe={class:"area-image"},Be={class:"has-image"},Ue={key:0,class:"image-title"},Re={class:"area-settings"},$e=n("td",null,[n("label",null,"Pieces")],-1),Ge=n("td",null,[n("label",null,"Scoring: ")],-1),Fe=i(" Any (Score when pieces are connected to each other or on final location)"),Le=n("br",null,null,-1),je=i(" Final (Score when pieces are put to their final location)"),We=n("td",null,[n("label",null,"Shapes: ")],-1),He=i(" Normal"),Ye=n("br",null,null,-1),qe=i(" Any (flat pieces can occur anywhere)"),Qe=n("br",null,null,-1),Ze=i(" Flat (all pieces flat on all sides)"),Ke=n("td",null,[n("label",null,"Snapping: ")],-1),Xe=i(" Normal (pieces snap to final destination and to each other)"),Je=n("br",null,null,-1),et=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),tt={class:"area-buttons"};Ne.render=function(e,o,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Oe,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(s(),t("div",Ue,'"'+r(e.image.title)+'"',1)):l("",!0)]),n("div",Re,[n("table",null,[n("tr",null,[$e,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[Ge,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Fe]),Le,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),je])])]),n("tr",null,[We,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),He]),Ye,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),qe]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),Ze])])]),n("tr",null,[Ke,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Xe]),Je,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),et])])])])]),n("div",tt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var nt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Pe,NewGameDialog:Ne},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${se.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const ot={class:"upload-image-teaser"},lt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),at={key:0},st=i(" Tags: "),it=i(" Sort by: "),rt=n("option",{value:"date_desc"},"Newest first",-1),dt=n("option",{value:"date_asc"},"Oldest first",-1),ct=n("option",{value:"alpha_asc"},"A-Z",-1),ut=n("option",{value:"alpha_desc"},"Z-A",-1);nt.render=function(e,o,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[n("div",ot,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),lt]),n("div",null,[e.tags.length>0?(s(),t("label",at,[st,(s(!0),t(d,null,c(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[it,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[rt,dt,ct,ut],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var pt=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 gt={class:"scores"},ht=n("div",null,"Scores",-1),mt=n("td",null,"⚡",-1),yt=n("td",null,"💤",-1);pt.render=function(e,o,l,a,i,u){return s(),t("div",gt,[ht,n("table",null,[(s(!0),t(d,null,c(e.actives,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[mt,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}},[yt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var ft=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 B(this.duration)}}});const vt={class:"timer"};ft.render=function(e,o,l,a,i,d){return s(),t("div",vt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var wt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const bt=m();y("data-v-a1d1c822");const kt=n("td",null,[n("label",null,"Background: ")],-1),xt=n("td",null,[n("label",null,"Color: ")],-1),Ct=n("td",null,[n("label",null,"Name: ")],-1),At=n("td",null,[n("label",null,"Sounds: ")],-1),St=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Tt={class:"sound-volume"};f();const Pt=bt(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[9]||(o[9]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[8]||(o[8]=u((()=>{}),["stop"]))},[n("tr",null,[kt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[xt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[Ct,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[At,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[k,e.modelValue.soundsEnabled]])])]),n("tr",null,[St,n("td",Tt,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])])])]))));wt.render=Pt,wt.__scopeId="data-v-a1d1c822";var It=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const zt={class:"preview"};It.render=function(e,o,l,a,i,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",zt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Dt=1,_t=4,Et=2,Mt=3,Vt=2,Nt=4,Ot=3,Bt=9,Ut=1,Rt=2,$t=3,Gt=4,Ft=5,Lt=6,jt=7,Wt=8,Ht=10,Yt=11,qt=12,Qt=13,Zt=14,Kt=1,Xt=2,Jt=3;const en=ae("Communication.js");let tn,nn=[],on=e=>{nn.push(e)},ln=[],an=e=>{ln.push(e)};let sn=0;const rn=e=>{sn!==e&&(sn=e,an(e))};function dn(e){if(2===sn)try{tn.send(JSON.stringify(e))}catch(t){en.info("unable to send message.. maybe because ws is invalid?")}}let cn,un;var pn={connect:function(e,t,n){return cn=0,un={},rn(3),new Promise((o=>{tn=new WebSocket(e,n+"|"+t),tn.onopen=()=>{rn(2),dn([Mt])},tn.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===_t){const e=t[1];o(e)}else{if(l!==Dt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&un[o])return void delete un[o];on(t)}}},tn.onerror=()=>{throw rn(1),"[ 2021-05-15 onerror ]"},tn.onclose=e=>{4e3===e.code||1001===e.code?rn(4):rn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${se.asQueryArgs(n)}`);return await o.json()},disconnect:function(){tn&&tn.close(4e3),cn=0,un={}},sendClientEvent:function(e){cn++,un[cn]=e,dn([Et,cn,un[cn]])},onServerChange:function(e){on=e;for(const t of nn)on(t);nn=[]},onConnectionStateChange:function(e){an=e;for(const t of ln)an(t);ln=[]},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},gn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===pn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===pn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const hn={key:0,class:"overlay connection-lost"},mn={key:0,class:"overlay-content"},yn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),fn={key:1,class:"overlay-content"},vn=n("div",null,"Connecting...",-1);gn.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",hn,[e.lostConnection?(s(),t("div",mn,[yn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",fn,[vn])):l("",!0)])):l("",!0)};var wn=e({name:"help-overlay",emits:{bgclick:null}});const bn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),kn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),xn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),Cn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),An=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Sn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Tn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),Pn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),In=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),zn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Dn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),_n=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),En=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),Mn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);wn.render=function(e,o,l,a,i,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]=u((()=>{}),["stop"]))},[bn,kn,xn,Cn,An,Sn,Tn,Pn,In,zn,Dn,_n,En,Mn])])};var Vn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),On=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Bn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Un=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Rn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),s=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),i=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:i,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function $n(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Gn={createCanvas:$n,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const o=$n(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=$n(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(),o}};const Fn=ae("Debug.js");let Ln=0,jn=0;var Wn=e=>{Ln=performance.now(),jn=e},Hn=e=>{const t=performance.now(),n=t-Ln;n>jn&&Fn.log(e+": "+n),Ln=t};function Yn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function qn(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var Qn={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:Yn,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:qn,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 Yn(qn(e),qn(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const Zn=ae("PuzzleGraphics.js");function Kn(e,t){const n=se.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Xn={loadPuzzleBitmaps:async function(e){const t=await Gn.loadImageToBitmap(e.info.imageUrl),n=await Gn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Zn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=Qn.pointAdd(a,{x:o,y:0}),c=Qn.pointAdd(r,{x:0,y:o}),u=Qn.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;ose.decodePiece(Jn[e].puzzle.tiles[t]),mo=(e,t)=>ho(e,t).group,yo=(e,t)=>{const n=Jn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},fo=(e,t)=>{const n=Jn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return Qn.pointAdd(o,l)},vo=(e,t)=>ho(e,t).pos,wo=e=>{const t=Oo(e),n=Bo(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}},bo=(e,t)=>{const n=Ao(e),o=ho(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},ko=(e,t)=>ho(e,t).z,xo=(e,t)=>{for(const n of Jn[e].puzzle.tiles){const e=se.decodePiece(n);if(e.owner===t)return e.idx}return-1},Co=e=>Jn[e].puzzle.info.tileDrawSize,Ao=e=>Jn[e].puzzle.info.tileSize,So=e=>Jn[e].puzzle.data.maxGroup,To=e=>Jn[e].puzzle.data.maxZ;function Po(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Io=(e,t,n)=>{for(const o of t)go(e,o,{z:n})},zo=(e,t,n)=>{const o=vo(e,t);go(e,t,{pos:Qn.pointAdd(o,n)})},Do=(e,t,n)=>{const o=Co(e),l=wo(e),a=n;for(const s of t){const t=ho(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)zo(e,s,a)},_o=(e,t)=>ho(e,t).owner,Eo=(e,t)=>{for(const n of t)go(e,n,{owner:-1,z:1})},Mo=(e,t,n)=>{for(const o of t)go(e,o,{owner:n})};function Vo(e,t){const n=Jn[e].puzzle.tiles,o=se.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=se.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const No=(e,t)=>{const n=to(e,t);return n?n.points:0},Oo=e=>Jn[e].puzzle.info.table.width,Bo=e=>Jn[e].puzzle.info.table.height;var Uo={setGame:function(e,t){Jn[e]=t},exists:function(e){return!!Jn[e]||!1},playerExists:oo,getActivePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){oo(e,t)?uo(e,t,{ts:n}):no(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:co,getPieceCount:ao,getImageUrl:function(e){return Jn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Jn[e].puzzle.info.imageUrl=t},get:function(e){return Jn[e]||null},getAllGames:function(){return Object.values(Jn).sort(((e,t)=>ro(e.id)===ro(t.id)?t.puzzle.data.started-e.puzzle.data.started:ro(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=to(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=to(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=to(e,t);return n?n.name:null},getPlayerIndexById:eo,getPlayerIdByIndex:function(e,t){return Jn[e].players.length>t?se.decodePlayer(Jn[e].players[t]).id:null},changePlayer:uo,setPlayer:no,setPiece:function(e,t,n){Jn[e].puzzle.tiles[t]=se.encodePiece(n)},setPuzzleData:function(e,t){Jn[e].puzzle.data=t},getTableWidth:Oo,getTableHeight:Bo,getPuzzle:e=>Jn[e].puzzle,getRng:e=>Jn[e].rng.obj,getPuzzleWidth:e=>Jn[e].puzzle.info.width,getPuzzleHeight:e=>Jn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Jn[e].puzzle.tiles.map(se.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=xo(e,t);return n<0?null:Jn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Jn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:Co,getFinalPiecePos:fo,getStartTs:e=>Jn[e].puzzle.data.started,getFinishTs:e=>Jn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Jn[e].puzzle,s=function(e,t){return t in Jn[e].evtInfos?Jn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([Kt,a.data])},d=t=>{i.push([Xt,se.encodePiece(ho(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=to(e,t);n&&i.push([Jt,se.encodePlayer(n)])},p=n[0];if(p===Lt){const l=n[1];uo(e,t,{bgcolor:l,ts:o}),u()}else if(p===jt){const l=n[1];uo(e,t,{color:l,ts:o}),u()}else if(p===Wt){const l=`${n[1]}`.substr(0,16);uo(e,t,{name:l,ts:o}),u()}else if(p===Bt){const l=n[1],a=n[2],s=to(e,t);if(s){const n=s.x-l,i=s.y-a;uo(e,t,{ts:o,x:n,y:i}),u()}}else if(p===Ut){const l={x:n[1],y:n[2]};uo(e,t,{d:1,ts:o}),u(),s._last_mouse_down=l;const a=((e,t)=>{const n=Jn[e].puzzle.info,o=Jn[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=To(e)+1;po(e,{maxZ:n}),r();const o=Vo(e,a);Io(e,o,To(e)),Mo(e,o,t),c(o)}s._last_mouse=l}else if(p===$t){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)uo(e,t,{x:l,y:a,ts:o}),u();else{const n=xo(e,t);if(n>=0){uo(e,t,{x:l,y:a,ts:o}),u();const r=Vo(e,n);let d=Qn.pointInBounds(i,wo(e))&&Qn.pointInBounds(s._last_mouse_down,wo(e));for(const t of r){const n=bo(e,t);if(Qn.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Do(e,r,{x:t,y:n}),c(r)}}else uo(e,t,{ts:o}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===Rt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=xo(e,t);if(g>=0){const n=Vo(e,g);Mo(e,n,0),c(n);const s=vo(e,g),i=fo(e,g);let h=!1;if(io(e)===ee.REAL){for(const t of n)if(yo(e,t)){h=!0;break}}else h=!0;if(h&&Qn.pointDistance(i,s){const l=Jn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=mo(e,t),l=mo(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=vo(e,t),s=Qn.pointAdd(vo(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(Qn.pointDistance(a,s){const o=Jn[e].puzzle.tiles,l=mo(e,t),a=mo(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(po(e,{maxGroup:So(e)+1}),r(),s=So(e));if(go(e,t,{group:s}),d(t),go(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=se.decodePiece(r);i.includes(t.group)&&(go(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=Vo(e,t),((e,t)=>-1===_o(e,t))(e,n))Eo(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=ko(e,o);t>n&&(n=t)}return n})(e,l);Io(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Vo(e,g)){const o=Po(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])){a=!0;break}}if(a&&so(e)===Z.ANY){const n=No(e,t)+1;uo(e,t,{d:p,ts:o,points:n}),u()}else uo(e,t,{d:p,ts:o}),u();a&&io(e)===ee.REAL&&co(e)===ao(e)&&(po(e,{finished:o}),r()),a&&l&&l(t)}}else uo(e,t,{d:p,ts:o}),u();s._last_mouse=i}else if(p===Gt){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else if(p===Ft){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else uo(e,t,{ts:o}),u();return function(e,t,n){Jn[e].evtInfos[t]=n}(e,t,s),i}};let Ro=-10,$o=20,Go=2,Fo=15;class Lo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Ro+Math.random()*$o,this.vy=-1*(Go+Math.random()*Fo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Go=t/2,Fo=t-Go;const n=1/4*this.canvas.width/(t/2);Ro=-n,$o=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Lo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Lo(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(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Gn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Ko=!0})),t}(l,Gn.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};pn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await pn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let k=()=>0;const x=async()=>{if("play"===o){const o=await pn.connect(n,e,t),l=se.decodeGame(o);Uo.setGame(l.id,l),k=()=>N()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=se.decodeGame(t.game);Uo.setGame(n.id,n),w.lastRealTs=N(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,k=()=>w.lastGameTs}}Ko=!0};await x();const C=Uo.getPieceDrawOffset(e),A=Uo.getPieceDrawSize(e),S=Uo.getPuzzleWidth(e),T=Uo.getPuzzleHeight(e),P=Uo.getTableWidth(e),I=Uo.getTableHeight(e),z={x:(P-S)/2,y:(I-T)/2},D={w:S,h:T},_={w:A,h:A},E=await Xn.loadPuzzleBitmaps(Uo.getPuzzle(e)),V=new Wo(v,Uo.getRng(e));V.init();const O=v.getContext("2d");v.classList.add("loaded");const B=Rn();B.move(-(P-v.width)/2,-(I-v.height)/2);const U=function(e,t,n,o){let l=[],a=!0,s=!1,i=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("Shift"===t.key?p=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?r=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?d=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?s=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?i=e:"q"===t.key?u=e:"e"===t.key&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Ut,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Rt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([$t,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Gt:Ft;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&(" "===e.key&&v([Ht]),"replay"===o&&("I"!==e.key&&"i"!==e.key||v([Qt]),"O"!==e.key&&"o"!==e.key||v([Zt]),"P"!==e.key&&"p"!==e.key||v([qt])),"F"!==e.key&&"f"!==e.key||(Qo=!Qo,Ko=!0),"G"!==e.key&&"g"!==e.key||(Zo=!Zo,Ko=!0),"M"!==e.key&&"m"!==e.key||v([Yt]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});v([Bt,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Gt,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([Ft,...e])}},setHotkeys:e=>{a=e}}}(v,window,B,o),R=Uo.getImageUrl(e),$=()=>{const t=Uo.getStartTs(e),n=Uo.getFinishTs(e),o=k();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),a.setPiecesTotal(Uo.getPieceCount(e));const G=k();a.setActivePlayers(Uo.getActivePlayers(e,G)),a.setIdlePlayers(Uo.getIdlePlayers(e,G));const F=!!Uo.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_volume");if(null===e)return 100;const t=parseInt(e,10);return isNaN(t)?100:t},H=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},Y=()=>{const e=W();i.volume=e/100,i.play()},q=()=>Uo.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Q=()=>Uo.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let Z="",K="",X=!1;const J=e=>{X=e;const[t,n]=e?[Z,"grab"]:[K,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},ee=e=>{Z=Gn.colorizedCanvas(r,c,e).toDataURL(),K=Gn.colorizedCanvas(d,u,e).toDataURL(),J(X)};ee(Q());const te=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ne=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,te())},le=()=>{w.paused=!w.paused,te()},ae=[];let ie;let re;if("play"===o?ae.push(setInterval((()=>{$()}),1e3)):"replay"===o&&te(),"play"===o)pn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Jt:{const n=se.decodePlayer(a);n.id!==t&&(Uo.setPlayer(e,n.id,n),Ko=!0)}break;case Xt:{const t=se.decodePiece(a);Uo.setPiece(e,t.idx,t),Ko=!0}break;case Kt:Uo.setPuzzleData(e,a),Ko=!0}L=!!Uo.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===Vt){const t=o[1];return Uo.addPlayer(e,t,n),!0}if(o[0]===Nt){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Uo.addPlayer(e,t,n),!0}if(o[0]===Ot){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Uo.handleInput(e,t,l,n),!0}return!1};let n=w.lastGameTs;const o=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=N();if(w.paused)return w.lastRealTs=l,void(ie=setTimeout(o,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const o=w.log[w.logPointer],l=n+o[o.length-1],a=w.log[e],i=a[a.length-1],r=l+i;if(r>s){w.skipNonActionPhases&&s+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/o),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{U.createKeyEvents();for(const n of U.consumeAll())if("play"===o){const o=n[0];if(o===Bt){const e=n[1],t=n[2],o=B.worldDimToViewport({w:e,h:t});Ko=!0,B.move(o.w,o.h)}else if(o===$t){if(de&&!Uo.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Ko=!0,B.move(o,l),de=t}}else if(o===jt)ee(n[1]);else if(o===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e),J(!0)}else if(o===Rt)de=null,J(!1);else if(o===Gt){const e={x:n[1],y:n[2]};Ko=!0,B.zoom("in",B.worldToViewport(e))}else if(o===Ft){const e={x:n[1],y:n[2]};Ko=!0,B.zoom("out",B.worldToViewport(e))}else o===Ht?a.togglePreview():o===Yt&&a.toggleSoundsEnabled();const l=k();Uo.handleInput(e,t,n,l,(e=>{H()&&Y()})).length>0&&(Ko=!0),pn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===qt)le();else if(e===Zt)oe();else if(e===Qt)ne();else if(e===Bt){const e=n[1],t=n[2];Ko=!0,B.move(e,t)}else if(e===$t){if(de){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Ko=!0,B.move(o,l),de=t}}else if(e===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e)}else if(e===Rt)de=null;else if(e===Gt){const e={x:n[1],y:n[2]};Ko=!0,B.zoom("in",B.worldToViewport(e))}else if(e===Ft){const e={x:n[1],y:n[2]};Ko=!0,B.zoom("out",B.worldToViewport(e))}else e===Ht&&a.togglePreview()}L=!!Uo.getFinishTs(e),j()&&(V.update(),Ko=!0)},render:async()=>{if(!Ko)return;const n=k();let l,s,i;window.DEBUG&&Wn(0),O.fillStyle=q(),O.fillRect(0,0,v.width,v.height),window.DEBUG&&Hn("clear done"),l=B.worldToViewportRaw(z),s=B.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&Hn("board done");const r=Uo.getPiecesSortedByZIndex(e);window.DEBUG&&Hn("get tiles done"),s=B.worldDimToViewportRaw(_);for(const e of r)(-1===e.owner?Qo:Zo)&&(i=E[e.idx],l=B.worldToViewportRaw({x:C+e.pos.x,y:C+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&Hn("tiles done");const d=[];for(const a of Uo.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(i=await f(a),l=B.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,o]of d)O.fillText(e,t,o);window.DEBUG&&Hn("players done"),a.setActivePlayers(Uo.getActivePlayers(e,n)),a.setIdlePlayers(Uo.getIdlePlayers(e,n)),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),window.DEBUG&&Hn("HUD done"),j()&&V.render(),Ko=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([Lt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([jt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Wt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},onSoundsVolumeChange:e=>{Ho.info("vol changed",e),localStorage.setItem("sound_volume",`${e}`),Y()},replayOnSpeedUp:ne,replayOnSpeedDown:oe,replayOnPauseToggle:le,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:R,player:{background:q(),color:Q(),name:Uo.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:H(),soundsVolume:W()},disconnect:pn.disconnect,connect:x,unload:()=>{ae.forEach((e=>{clearInterval(e)})),ie&&clearTimeout(ie),re&&re.stop()}}}var Jo=e({name:"game",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:It,ConnectionOverlay:gn,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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)},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 el={id:"game"},tl={class:"menu"},nl={class:"tabs"},ol=i("🧩 Puzzles");Jo.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",el,[p(n(u,{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"]),[[x,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",tl,[n("div",nl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[ol])),_: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 ll=e({name:"replay",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:It,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 al={id:"replay"},sl=i("Skip no action phases: "),il={class:"menu"},rl={class:"tabs"},dl=i("🧩 Puzzles");ll.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",al,[p(n(g,{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"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[sl,p(n("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[k,e.skipNoAction]])])]),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",il,[n("div",rl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[dl])),_:1}),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:l[12]||(l[12]=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=C({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:nt},{name:"game",path:"/g/:id",component:Jo},{name:"replay",path:"/replay/:id",component:ll}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=S(T);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=se.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 5e60fb5..9a6f879 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,9 +4,9 @@ 🧩 jigsaw.hyottoko.club - + - +
diff --git a/src/frontend/components/TagsInput.vue b/src/frontend/components/TagsInput.vue index e0d4f1f..0fbbcdc 100644 --- a/src/frontend/components/TagsInput.vue +++ b/src/frontend/components/TagsInput.vue @@ -56,14 +56,14 @@ export default defineComponent({ }, methods: { onKeyUp (ev: KeyboardEvent) { - if (ev.key === 'ArrowDown' && this.autocomplete.values.length > 0) { + if (ev.code === 'ArrowDown' && this.autocomplete.values.length > 0) { if (this.autocomplete.idx < this.autocomplete.values.length - 1) { this.autocomplete.idx++ } ev.stopPropagation() return false } - if (ev.key === 'ArrowUp' && this.autocomplete.values.length > 0) { + if (ev.code === 'ArrowUp' && this.autocomplete.values.length > 0) { if (this.autocomplete.idx > 0) { this.autocomplete.idx-- } diff --git a/src/frontend/game.ts b/src/frontend/game.ts index 3322e99..03ab612 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -122,19 +122,20 @@ function EventAdapter ( if (!KEYS_ON) { return } - if (ev.key === 'Shift') { + + if (ev.code === 'ShiftLeft' || ev.code === 'ShiftRight') { SHIFT = state - } else if (ev.key === 'ArrowUp' || ev.key === 'w' || ev.key === 'W') { + } else if (ev.code === 'ArrowUp' || ev.code === 'KeyW') { UP = state - } else if (ev.key === 'ArrowDown' || ev.key === 's' || ev.key === 'S') { + } else if (ev.code === 'ArrowDown' || ev.code === 'KeyS') { DOWN = state - } else if (ev.key === 'ArrowLeft' || ev.key === 'a' || ev.key === 'A') { + } else if (ev.code === 'ArrowLeft' || ev.code === 'KeyA') { LEFT = state - } else if (ev.key === 'ArrowRight' || ev.key === 'd' || ev.key === 'D') { + } else if (ev.code === 'ArrowRight' || ev.code === 'KeyD') { RIGHT = state - } else if (ev.key === 'q') { + } else if (ev.code === 'KeyQ') { ZOOM_OUT = state - } else if (ev.key === 'e') { + } else if (ev.code === 'KeyE') { ZOOM_IN = state } } @@ -176,32 +177,32 @@ function EventAdapter ( if (!KEYS_ON) { return } - if (ev.key === ' ') { + if (ev.code === 'Space') { addEvent([Protocol.INPUT_EV_TOGGLE_PREVIEW]) } if (MODE === MODE_REPLAY) { - if (ev.key === 'I' || ev.key === 'i') { + if (ev.code === 'KeyI') { addEvent([Protocol.INPUT_EV_REPLAY_SPEED_UP]) } - if (ev.key === 'O' || ev.key === 'o') { + if (ev.code === 'KeyO') { addEvent([Protocol.INPUT_EV_REPLAY_SPEED_DOWN]) } - if (ev.key === 'P' || ev.key === 'p') { + if (ev.code === 'KeyP') { addEvent([Protocol.INPUT_EV_REPLAY_TOGGLE_PAUSE]) } } - if (ev.key === 'F' || ev.key === 'f') { + if (ev.code === 'KeyF') { PIECE_VIEW_FIXED = !PIECE_VIEW_FIXED RERENDER = true } - if (ev.key === 'G' || ev.key === 'g') { + if (ev.code === 'KeyG') { PIECE_VIEW_LOOSE = !PIECE_VIEW_LOOSE RERENDER = true } - if (ev.key === 'M' || ev.key === 'm') { + if (ev.code === 'KeyM') { addEvent([Protocol.INPUT_EV_TOGGLE_SOUNDS]) } }) From 10d56e2898060e97e3f42c6cabdafe05d1053c68 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Mon, 7 Jun 2021 00:32:05 +0200 Subject: [PATCH 12/45] dont change bg colors in replay --- build/public/assets/{index.5791217a.js => index.ac3207a7.js} | 2 +- build/public/index.html | 2 +- src/frontend/game.ts | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) rename build/public/assets/{index.5791217a.js => index.ac3207a7.js} (77%) diff --git a/build/public/assets/index.5791217a.js b/build/public/assets/index.ac3207a7.js similarity index 77% rename from build/public/assets/index.5791217a.js rename to build/public/assets/index.ac3207a7.js index 2311d28..98a476c 100644 --- a/build/public/assets/index.5791217a.js +++ b/build/public/assets/index.ac3207a7.js @@ -1 +1 @@ -import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as x,s as C,u as k,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const P={id:"app"},I={key:0,class:"nav"},z=i("Index"),D=i("New game");T.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",P,[e.showNav?(s(),t("ul",I,[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((()=>[D])),_:1})])])):l("",!0),n(g)])};const E=864e5,_=e=>{const t=Math.floor(e/E);e%=E;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 M=1,V=1e3,N=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>_(t-e),B=_,U=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||N();return`${n} ${O(o,l)}`}}});const R={class:"game-info-text"},$=n("br",null,null,-1),G=n("br",null,null,-1),F=n("br",null,null,-1),L=i(" ↪️ Watch replay ");U.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),$,i(" 👥 "+r(e.game.players),1),G,i(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[L])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:U},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 W=n("h1",null,"Running games",-1),K=n("h1",null,"Finished games",-1);j.render=function(e,o,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(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)),K,(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 H=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});H.render=function(e,o,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y,Q,q,Z,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:H},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(Q=Y||(Y={}))[Q.Flat=0]="Flat",Q[Q.Out=1]="Out",Q[Q.In=-1]="In",(Z=q||(q={}))[Z.FINAL=0]="FINAL",Z[Z.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),s=le(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var se={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||q.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const ie={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};ie.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-a4fa5e7e");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ce,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-a4fa5e7e";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=n("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},ve=n("span",{class:"btn"},"Upload File",-1),we={class:"area-settings"},be=n("td",null,[n("label",null,"Title")],-1),xe=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ce=n("td",null,[n("label",null,"Tags")],-1),ke={class:"area-buttons"},Ae=i("🧩 Post to gallery "),Se=n("br",null,null,-1),Te=i(" + set up game");ge.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(s(),t("div",me,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",ye,[n("label",fe,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ve])]))],34),n("div",we,[n("table",null,[n("tr",null,[be,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),xe,n("tr",null,[Ce,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",ke,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ae,Se,Te],8,["disabled"])])])])};var Pe=e({name:"edit-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Ie={class:"area-image"},ze={class:"has-image"},De={class:"area-settings"},Ee=n("td",null,[n("label",null,"Title")],-1),_e=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Me=n("td",null,[n("label",null,"Tags")],-1),Ve={class:"area-buttons"};Pe.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Ie,[n("div",ze,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",De,[n("table",null,[n("tr",null,[Ee,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),_e,n("tr",null,[Me,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Ve,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Ne=e({name:"new-game-dialog",components:{ResponsiveImage:ie},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:q.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Oe={class:"area-image"},Be={class:"has-image"},Ue={key:0,class:"image-title"},Re={class:"area-settings"},$e=n("td",null,[n("label",null,"Pieces")],-1),Ge=n("td",null,[n("label",null,"Scoring: ")],-1),Fe=i(" Any (Score when pieces are connected to each other or on final location)"),Le=n("br",null,null,-1),je=i(" Final (Score when pieces are put to their final location)"),We=n("td",null,[n("label",null,"Shapes: ")],-1),Ke=i(" Normal"),He=n("br",null,null,-1),Ye=i(" Any (flat pieces can occur anywhere)"),Qe=n("br",null,null,-1),qe=i(" Flat (all pieces flat on all sides)"),Ze=n("td",null,[n("label",null,"Snapping: ")],-1),Xe=i(" Normal (pieces snap to final destination and to each other)"),Je=n("br",null,null,-1),et=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),tt={class:"area-buttons"};Ne.render=function(e,o,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Oe,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(s(),t("div",Ue,'"'+r(e.image.title)+'"',1)):l("",!0)]),n("div",Re,[n("table",null,[n("tr",null,[$e,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[Ge,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Fe]),Le,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),je])])]),n("tr",null,[We,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),Ke]),He,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ye]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),qe])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Xe]),Je,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),et])])])])]),n("div",tt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var nt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Pe,NewGameDialog:Ne},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${se.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const ot={class:"upload-image-teaser"},lt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),at={key:0},st=i(" Tags: "),it=i(" Sort by: "),rt=n("option",{value:"date_desc"},"Newest first",-1),dt=n("option",{value:"date_asc"},"Oldest first",-1),ct=n("option",{value:"alpha_asc"},"A-Z",-1),ut=n("option",{value:"alpha_desc"},"Z-A",-1);nt.render=function(e,o,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[n("div",ot,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),lt]),n("div",null,[e.tags.length>0?(s(),t("label",at,[st,(s(!0),t(d,null,c(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[it,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[rt,dt,ct,ut],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var pt=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 gt={class:"scores"},ht=n("div",null,"Scores",-1),mt=n("td",null,"⚡",-1),yt=n("td",null,"💤",-1);pt.render=function(e,o,l,a,i,u){return s(),t("div",gt,[ht,n("table",null,[(s(!0),t(d,null,c(e.actives,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[mt,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}},[yt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var ft=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 B(this.duration)}}});const vt={class:"timer"};ft.render=function(e,o,l,a,i,d){return s(),t("div",vt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var wt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const bt=m();y("data-v-a1d1c822");const xt=n("td",null,[n("label",null,"Background: ")],-1),Ct=n("td",null,[n("label",null,"Color: ")],-1),kt=n("td",null,[n("label",null,"Name: ")],-1),At=n("td",null,[n("label",null,"Sounds: ")],-1),St=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Tt={class:"sound-volume"};f();const Pt=bt(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[9]||(o[9]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[8]||(o[8]=u((()=>{}),["stop"]))},[n("tr",null,[xt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[Ct,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[kt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[At,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[x,e.modelValue.soundsEnabled]])])]),n("tr",null,[St,n("td",Tt,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])])])]))));wt.render=Pt,wt.__scopeId="data-v-a1d1c822";var It=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const zt={class:"preview"};It.render=function(e,o,l,a,i,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",zt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Dt=1,Et=4,_t=2,Mt=3,Vt=2,Nt=4,Ot=3,Bt=9,Ut=1,Rt=2,$t=3,Gt=4,Ft=5,Lt=6,jt=7,Wt=8,Kt=10,Ht=11,Yt=12,Qt=13,qt=14,Zt=1,Xt=2,Jt=3;const en=ae("Communication.js");let tn,nn=[],on=e=>{nn.push(e)},ln=[],an=e=>{ln.push(e)};let sn=0;const rn=e=>{sn!==e&&(sn=e,an(e))};function dn(e){if(2===sn)try{tn.send(JSON.stringify(e))}catch(t){en.info("unable to send message.. maybe because ws is invalid?")}}let cn,un;var pn={connect:function(e,t,n){return cn=0,un={},rn(3),new Promise((o=>{tn=new WebSocket(e,n+"|"+t),tn.onopen=()=>{rn(2),dn([Mt])},tn.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Et){const e=t[1];o(e)}else{if(l!==Dt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&un[o])return void delete un[o];on(t)}}},tn.onerror=()=>{throw rn(1),"[ 2021-05-15 onerror ]"},tn.onclose=e=>{4e3===e.code||1001===e.code?rn(4):rn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${se.asQueryArgs(n)}`);return await o.json()},disconnect:function(){tn&&tn.close(4e3),cn=0,un={}},sendClientEvent:function(e){cn++,un[cn]=e,dn([_t,cn,un[cn]])},onServerChange:function(e){on=e;for(const t of nn)on(t);nn=[]},onConnectionStateChange:function(e){an=e;for(const t of ln)an(t);ln=[]},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},gn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===pn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===pn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const hn={key:0,class:"overlay connection-lost"},mn={key:0,class:"overlay-content"},yn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),fn={key:1,class:"overlay-content"},vn=n("div",null,"Connecting...",-1);gn.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",hn,[e.lostConnection?(s(),t("div",mn,[yn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",fn,[vn])):l("",!0)])):l("",!0)};var wn=e({name:"help-overlay",emits:{bgclick:null}});const bn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),xn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),Cn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),kn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),An=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Sn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Tn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),Pn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),In=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),zn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Dn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),En=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),_n=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),Mn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);wn.render=function(e,o,l,a,i,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]=u((()=>{}),["stop"]))},[bn,xn,Cn,kn,An,Sn,Tn,Pn,In,zn,Dn,En,_n,Mn])])};var Vn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),On=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Bn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Un=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Rn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),s=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),i=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:i,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function $n(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Gn={createCanvas:$n,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const o=$n(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=$n(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(),o}};const Fn=ae("Debug.js");let Ln=0,jn=0;var Wn=e=>{Ln=performance.now(),jn=e},Kn=e=>{const t=performance.now(),n=t-Ln;n>jn&&Fn.log(e+": "+n),Ln=t};function Hn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function Yn(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var Qn={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:Hn,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:Yn,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 Hn(Yn(e),Yn(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const qn=ae("PuzzleGraphics.js");function Zn(e,t){const n=se.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Xn={loadPuzzleBitmaps:async function(e){const t=await Gn.loadImageToBitmap(e.info.imageUrl),n=await Gn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){qn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=Qn.pointAdd(a,{x:o,y:0}),c=Qn.pointAdd(r,{x:0,y:o}),u=Qn.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;ose.decodePiece(Jn[e].puzzle.tiles[t]),mo=(e,t)=>ho(e,t).group,yo=(e,t)=>{const n=Jn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},fo=(e,t)=>{const n=Jn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return Qn.pointAdd(o,l)},vo=(e,t)=>ho(e,t).pos,wo=e=>{const t=Oo(e),n=Bo(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}},bo=(e,t)=>{const n=Ao(e),o=ho(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},xo=(e,t)=>ho(e,t).z,Co=(e,t)=>{for(const n of Jn[e].puzzle.tiles){const e=se.decodePiece(n);if(e.owner===t)return e.idx}return-1},ko=e=>Jn[e].puzzle.info.tileDrawSize,Ao=e=>Jn[e].puzzle.info.tileSize,So=e=>Jn[e].puzzle.data.maxGroup,To=e=>Jn[e].puzzle.data.maxZ;function Po(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Io=(e,t,n)=>{for(const o of t)go(e,o,{z:n})},zo=(e,t,n)=>{const o=vo(e,t);go(e,t,{pos:Qn.pointAdd(o,n)})},Do=(e,t,n)=>{const o=ko(e),l=wo(e),a=n;for(const s of t){const t=ho(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)zo(e,s,a)},Eo=(e,t)=>ho(e,t).owner,_o=(e,t)=>{for(const n of t)go(e,n,{owner:-1,z:1})},Mo=(e,t,n)=>{for(const o of t)go(e,o,{owner:n})};function Vo(e,t){const n=Jn[e].puzzle.tiles,o=se.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=se.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const No=(e,t)=>{const n=to(e,t);return n?n.points:0},Oo=e=>Jn[e].puzzle.info.table.width,Bo=e=>Jn[e].puzzle.info.table.height;var Uo={setGame:function(e,t){Jn[e]=t},exists:function(e){return!!Jn[e]||!1},playerExists:oo,getActivePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){oo(e,t)?uo(e,t,{ts:n}):no(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:co,getPieceCount:ao,getImageUrl:function(e){return Jn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Jn[e].puzzle.info.imageUrl=t},get:function(e){return Jn[e]||null},getAllGames:function(){return Object.values(Jn).sort(((e,t)=>ro(e.id)===ro(t.id)?t.puzzle.data.started-e.puzzle.data.started:ro(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=to(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=to(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=to(e,t);return n?n.name:null},getPlayerIndexById:eo,getPlayerIdByIndex:function(e,t){return Jn[e].players.length>t?se.decodePlayer(Jn[e].players[t]).id:null},changePlayer:uo,setPlayer:no,setPiece:function(e,t,n){Jn[e].puzzle.tiles[t]=se.encodePiece(n)},setPuzzleData:function(e,t){Jn[e].puzzle.data=t},getTableWidth:Oo,getTableHeight:Bo,getPuzzle:e=>Jn[e].puzzle,getRng:e=>Jn[e].rng.obj,getPuzzleWidth:e=>Jn[e].puzzle.info.width,getPuzzleHeight:e=>Jn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Jn[e].puzzle.tiles.map(se.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Co(e,t);return n<0?null:Jn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Jn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:ko,getFinalPiecePos:fo,getStartTs:e=>Jn[e].puzzle.data.started,getFinishTs:e=>Jn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Jn[e].puzzle,s=function(e,t){return t in Jn[e].evtInfos?Jn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([Zt,a.data])},d=t=>{i.push([Xt,se.encodePiece(ho(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=to(e,t);n&&i.push([Jt,se.encodePlayer(n)])},p=n[0];if(p===Lt){const l=n[1];uo(e,t,{bgcolor:l,ts:o}),u()}else if(p===jt){const l=n[1];uo(e,t,{color:l,ts:o}),u()}else if(p===Wt){const l=`${n[1]}`.substr(0,16);uo(e,t,{name:l,ts:o}),u()}else if(p===Bt){const l=n[1],a=n[2],s=to(e,t);if(s){const n=s.x-l,i=s.y-a;uo(e,t,{ts:o,x:n,y:i}),u()}}else if(p===Ut){const l={x:n[1],y:n[2]};uo(e,t,{d:1,ts:o}),u(),s._last_mouse_down=l;const a=((e,t)=>{const n=Jn[e].puzzle.info,o=Jn[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=To(e)+1;po(e,{maxZ:n}),r();const o=Vo(e,a);Io(e,o,To(e)),Mo(e,o,t),c(o)}s._last_mouse=l}else if(p===$t){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)uo(e,t,{x:l,y:a,ts:o}),u();else{const n=Co(e,t);if(n>=0){uo(e,t,{x:l,y:a,ts:o}),u();const r=Vo(e,n);let d=Qn.pointInBounds(i,wo(e))&&Qn.pointInBounds(s._last_mouse_down,wo(e));for(const t of r){const n=bo(e,t);if(Qn.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Do(e,r,{x:t,y:n}),c(r)}}else uo(e,t,{ts:o}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===Rt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Co(e,t);if(g>=0){const n=Vo(e,g);Mo(e,n,0),c(n);const s=vo(e,g),i=fo(e,g);let h=!1;if(io(e)===ee.REAL){for(const t of n)if(yo(e,t)){h=!0;break}}else h=!0;if(h&&Qn.pointDistance(i,s){const l=Jn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=mo(e,t),l=mo(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=vo(e,t),s=Qn.pointAdd(vo(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(Qn.pointDistance(a,s){const o=Jn[e].puzzle.tiles,l=mo(e,t),a=mo(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(po(e,{maxGroup:So(e)+1}),r(),s=So(e));if(go(e,t,{group:s}),d(t),go(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=se.decodePiece(r);i.includes(t.group)&&(go(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=Vo(e,t),((e,t)=>-1===Eo(e,t))(e,n))_o(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=xo(e,o);t>n&&(n=t)}return n})(e,l);Io(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Vo(e,g)){const o=Po(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])){a=!0;break}}if(a&&so(e)===q.ANY){const n=No(e,t)+1;uo(e,t,{d:p,ts:o,points:n}),u()}else uo(e,t,{d:p,ts:o}),u();a&&io(e)===ee.REAL&&co(e)===ao(e)&&(po(e,{finished:o}),r()),a&&l&&l(t)}}else uo(e,t,{d:p,ts:o}),u();s._last_mouse=i}else if(p===Gt){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else if(p===Ft){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else uo(e,t,{ts:o}),u();return function(e,t,n){Jn[e].evtInfos[t]=n}(e,t,s),i}};let Ro=-10,$o=20,Go=2,Fo=15;class Lo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Ro+Math.random()*$o,this.vy=-1*(Go+Math.random()*Fo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Go=t/2,Fo=t-Go;const n=1/4*this.canvas.width/(t/2);Ro=-n,$o=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Lo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Lo(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(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Gn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Zo=!0})),t}(l,Gn.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};pn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await pn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let x=()=>0;const C=async()=>{if("play"===o){const o=await pn.connect(n,e,t),l=se.decodeGame(o);Uo.setGame(l.id,l),x=()=>N()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=se.decodeGame(t.game);Uo.setGame(n.id,n),w.lastRealTs=N(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,x=()=>w.lastGameTs}}Zo=!0};await C();const k=Uo.getPieceDrawOffset(e),A=Uo.getPieceDrawSize(e),S=Uo.getPuzzleWidth(e),T=Uo.getPuzzleHeight(e),P=Uo.getTableWidth(e),I=Uo.getTableHeight(e),z={x:(P-S)/2,y:(I-T)/2},D={w:S,h:T},E={w:A,h:A},_=await Xn.loadPuzzleBitmaps(Uo.getPuzzle(e)),V=new Wo(v,Uo.getRng(e));V.init();const O=v.getContext("2d");v.classList.add("loaded");const B=Rn();B.move(-(P-v.width)/2,-(I-v.height)/2);const U=function(e,t,n,o){let l=[],a=!0,s=!1,i=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Ut,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Rt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([$t,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Gt:Ft;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([Kt]),"replay"===o&&("KeyI"===e.code&&v([Qt]),"KeyO"===e.code&&v([qt]),"KeyP"===e.code&&v([Yt])),"KeyF"===e.code&&(Qo=!Qo,Zo=!0),"KeyG"===e.code&&(qo=!qo,Zo=!0),"KeyM"===e.code&&v([Ht]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});v([Bt,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Gt,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([Ft,...e])}},setHotkeys:e=>{a=e}}}(v,window,B,o),R=Uo.getImageUrl(e),$=()=>{const t=Uo.getStartTs(e),n=Uo.getFinishTs(e),o=x();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),a.setPiecesTotal(Uo.getPieceCount(e));const G=x();a.setActivePlayers(Uo.getActivePlayers(e,G)),a.setIdlePlayers(Uo.getIdlePlayers(e,G));const F=!!Uo.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_volume");if(null===e)return 100;const t=parseInt(e,10);return isNaN(t)?100:t},K=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},H=()=>{const e=W();i.volume=e/100,i.play()},Y=()=>Uo.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Q=()=>Uo.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let q="",Z="",X=!1;const J=e=>{X=e;const[t,n]=e?[q,"grab"]:[Z,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},ee=e=>{q=Gn.colorizedCanvas(r,c,e).toDataURL(),Z=Gn.colorizedCanvas(d,u,e).toDataURL(),J(X)};ee(Q());const te=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ne=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,te())},le=()=>{w.paused=!w.paused,te()},ae=[];let ie;let re;if("play"===o?ae.push(setInterval((()=>{$()}),1e3)):"replay"===o&&te(),"play"===o)pn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Jt:{const n=se.decodePlayer(a);n.id!==t&&(Uo.setPlayer(e,n.id,n),Zo=!0)}break;case Xt:{const t=se.decodePiece(a);Uo.setPiece(e,t.idx,t),Zo=!0}break;case Zt:Uo.setPuzzleData(e,a),Zo=!0}L=!!Uo.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===Vt){const t=o[1];return Uo.addPlayer(e,t,n),!0}if(o[0]===Nt){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Uo.addPlayer(e,t,n),!0}if(o[0]===Ot){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Uo.handleInput(e,t,l,n),!0}return!1};let n=w.lastGameTs;const o=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=N();if(w.paused)return w.lastRealTs=l,void(ie=setTimeout(o,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const o=w.log[w.logPointer],l=n+o[o.length-1],a=w.log[e],i=a[a.length-1],r=l+i;if(r>s){w.skipNonActionPhases&&s+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/o),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{U.createKeyEvents();for(const n of U.consumeAll())if("play"===o){const o=n[0];if(o===Bt){const e=n[1],t=n[2],o=B.worldDimToViewport({w:e,h:t});Zo=!0,B.move(o.w,o.h)}else if(o===$t){if(de&&!Uo.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Zo=!0,B.move(o,l),de=t}}else if(o===jt)ee(n[1]);else if(o===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e),J(!0)}else if(o===Rt)de=null,J(!1);else if(o===Gt){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("in",B.worldToViewport(e))}else if(o===Ft){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("out",B.worldToViewport(e))}else o===Kt?a.togglePreview():o===Ht&&a.toggleSoundsEnabled();const l=x();Uo.handleInput(e,t,n,l,(e=>{K()&&H()})).length>0&&(Zo=!0),pn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===Yt)le();else if(e===qt)oe();else if(e===Qt)ne();else if(e===Bt){const e=n[1],t=n[2];Zo=!0,B.move(e,t)}else if(e===$t){if(de){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Zo=!0,B.move(o,l),de=t}}else if(e===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e)}else if(e===Rt)de=null;else if(e===Gt){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("in",B.worldToViewport(e))}else if(e===Ft){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("out",B.worldToViewport(e))}else e===Kt&&a.togglePreview()}L=!!Uo.getFinishTs(e),j()&&(V.update(),Zo=!0)},render:async()=>{if(!Zo)return;const n=x();let l,s,i;window.DEBUG&&Wn(0),O.fillStyle=Y(),O.fillRect(0,0,v.width,v.height),window.DEBUG&&Kn("clear done"),l=B.worldToViewportRaw(z),s=B.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&Kn("board done");const r=Uo.getPiecesSortedByZIndex(e);window.DEBUG&&Kn("get tiles done"),s=B.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?Qo:qo)&&(i=_[e.idx],l=B.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&Kn("tiles done");const d=[];for(const a of Uo.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(i=await f(a),l=B.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,o]of d)O.fillText(e,t,o);window.DEBUG&&Kn("players done"),a.setActivePlayers(Uo.getActivePlayers(e,n)),a.setIdlePlayers(Uo.getIdlePlayers(e,n)),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),window.DEBUG&&Kn("HUD done"),j()&&V.render(),Zo=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([Lt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([jt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Wt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},onSoundsVolumeChange:e=>{Ko.info("vol changed",e),localStorage.setItem("sound_volume",`${e}`),H()},replayOnSpeedUp:ne,replayOnSpeedDown:oe,replayOnPauseToggle:le,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:R,player:{background:Y(),color:Q(),name:Uo.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:K(),soundsVolume:W()},disconnect:pn.disconnect,connect:C,unload:()=>{ae.forEach((e=>{clearInterval(e)})),ie&&clearTimeout(ie),re&&re.stop()}}}var Jo=e({name:"game",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:It,ConnectionOverlay:gn,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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)},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 el={id:"game"},tl={class:"menu"},nl={class:"tabs"},ol=i("🧩 Puzzles");Jo.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",el,[p(n(u,{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"]),[[C,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",tl,[n("div",nl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[ol])),_: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 ll=e({name:"replay",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:It,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 al={id:"replay"},sl=i("Skip no action phases: "),il={class:"menu"},rl={class:"tabs"},dl=i("🧩 Puzzles");ll.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",al,[p(n(g,{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"]),[[C,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[sl,p(n("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[x,e.skipNoAction]])])]),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",il,[n("div",rl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[dl])),_:1}),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:l[12]||(l[12]=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=k({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:nt},{name:"game",path:"/g/:id",component:Jo},{name:"replay",path:"/replay/:id",component:ll}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=S(T);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=se.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); +import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as x,s as C,u as k,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const P={id:"app"},I={key:0,class:"nav"},z=i("Index"),D=i("New game");T.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",P,[e.showNav?(s(),t("ul",I,[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((()=>[D])),_:1})])])):l("",!0),n(g)])};const _=864e5,E=e=>{const t=Math.floor(e/_);e%=_;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 M=1,V=1e3,N=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>E(t-e),B=E,U=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||N();return`${n} ${O(o,l)}`}}});const R={class:"game-info-text"},$=n("br",null,null,-1),G=n("br",null,null,-1),F=n("br",null,null,-1),L=i(" ↪️ Watch replay ");U.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),$,i(" 👥 "+r(e.game.players),1),G,i(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[L])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:U},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 W=n("h1",null,"Running games",-1),K=n("h1",null,"Finished games",-1);j.render=function(e,o,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(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)),K,(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 H=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});H.render=function(e,o,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y,Q,q,Z,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:H},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(Q=Y||(Y={}))[Q.Flat=0]="Flat",Q[Q.Out=1]="Out",Q[Q.In=-1]="In",(Z=q||(q={}))[Z.FINAL=0]="FINAL",Z[Z.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),s=le(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var se={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||q.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const ie={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};ie.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-a4fa5e7e");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ce,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-a4fa5e7e";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=n("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},ve=n("span",{class:"btn"},"Upload File",-1),we={class:"area-settings"},be=n("td",null,[n("label",null,"Title")],-1),xe=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ce=n("td",null,[n("label",null,"Tags")],-1),ke={class:"area-buttons"},Ae=i("🧩 Post to gallery "),Se=n("br",null,null,-1),Te=i(" + set up game");ge.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(s(),t("div",me,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",ye,[n("label",fe,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ve])]))],34),n("div",we,[n("table",null,[n("tr",null,[be,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),xe,n("tr",null,[Ce,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",ke,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ae,Se,Te],8,["disabled"])])])])};var Pe=e({name:"edit-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Ie={class:"area-image"},ze={class:"has-image"},De={class:"area-settings"},_e=n("td",null,[n("label",null,"Title")],-1),Ee=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Me=n("td",null,[n("label",null,"Tags")],-1),Ve={class:"area-buttons"};Pe.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Ie,[n("div",ze,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",De,[n("table",null,[n("tr",null,[_e,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ee,n("tr",null,[Me,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Ve,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Ne=e({name:"new-game-dialog",components:{ResponsiveImage:ie},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:q.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Oe={class:"area-image"},Be={class:"has-image"},Ue={key:0,class:"image-title"},Re={class:"area-settings"},$e=n("td",null,[n("label",null,"Pieces")],-1),Ge=n("td",null,[n("label",null,"Scoring: ")],-1),Fe=i(" Any (Score when pieces are connected to each other or on final location)"),Le=n("br",null,null,-1),je=i(" Final (Score when pieces are put to their final location)"),We=n("td",null,[n("label",null,"Shapes: ")],-1),Ke=i(" Normal"),He=n("br",null,null,-1),Ye=i(" Any (flat pieces can occur anywhere)"),Qe=n("br",null,null,-1),qe=i(" Flat (all pieces flat on all sides)"),Ze=n("td",null,[n("label",null,"Snapping: ")],-1),Xe=i(" Normal (pieces snap to final destination and to each other)"),Je=n("br",null,null,-1),et=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),tt={class:"area-buttons"};Ne.render=function(e,o,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Oe,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(s(),t("div",Ue,'"'+r(e.image.title)+'"',1)):l("",!0)]),n("div",Re,[n("table",null,[n("tr",null,[$e,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[Ge,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Fe]),Le,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),je])])]),n("tr",null,[We,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),Ke]),He,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ye]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),qe])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Xe]),Je,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),et])])])])]),n("div",tt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var nt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Pe,NewGameDialog:Ne},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${se.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const ot={class:"upload-image-teaser"},lt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),at={key:0},st=i(" Tags: "),it=i(" Sort by: "),rt=n("option",{value:"date_desc"},"Newest first",-1),dt=n("option",{value:"date_asc"},"Oldest first",-1),ct=n("option",{value:"alpha_asc"},"A-Z",-1),ut=n("option",{value:"alpha_desc"},"Z-A",-1);nt.render=function(e,o,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[n("div",ot,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),lt]),n("div",null,[e.tags.length>0?(s(),t("label",at,[st,(s(!0),t(d,null,c(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[it,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[rt,dt,ct,ut],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var pt=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 gt={class:"scores"},ht=n("div",null,"Scores",-1),mt=n("td",null,"⚡",-1),yt=n("td",null,"💤",-1);pt.render=function(e,o,l,a,i,u){return s(),t("div",gt,[ht,n("table",null,[(s(!0),t(d,null,c(e.actives,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[mt,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}},[yt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var ft=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 B(this.duration)}}});const vt={class:"timer"};ft.render=function(e,o,l,a,i,d){return s(),t("div",vt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var wt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const bt=m();y("data-v-a1d1c822");const xt=n("td",null,[n("label",null,"Background: ")],-1),Ct=n("td",null,[n("label",null,"Color: ")],-1),kt=n("td",null,[n("label",null,"Name: ")],-1),At=n("td",null,[n("label",null,"Sounds: ")],-1),St=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Tt={class:"sound-volume"};f();const Pt=bt(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[9]||(o[9]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[8]||(o[8]=u((()=>{}),["stop"]))},[n("tr",null,[xt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[Ct,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[kt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[At,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[x,e.modelValue.soundsEnabled]])])]),n("tr",null,[St,n("td",Tt,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])])])]))));wt.render=Pt,wt.__scopeId="data-v-a1d1c822";var It=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const zt={class:"preview"};It.render=function(e,o,l,a,i,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",zt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Dt=1,_t=4,Et=2,Mt=3,Vt=2,Nt=4,Ot=3,Bt=9,Ut=1,Rt=2,$t=3,Gt=4,Ft=5,Lt=6,jt=7,Wt=8,Kt=10,Ht=11,Yt=12,Qt=13,qt=14,Zt=1,Xt=2,Jt=3;const en=ae("Communication.js");let tn,nn=[],on=e=>{nn.push(e)},ln=[],an=e=>{ln.push(e)};let sn=0;const rn=e=>{sn!==e&&(sn=e,an(e))};function dn(e){if(2===sn)try{tn.send(JSON.stringify(e))}catch(t){en.info("unable to send message.. maybe because ws is invalid?")}}let cn,un;var pn={connect:function(e,t,n){return cn=0,un={},rn(3),new Promise((o=>{tn=new WebSocket(e,n+"|"+t),tn.onopen=()=>{rn(2),dn([Mt])},tn.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===_t){const e=t[1];o(e)}else{if(l!==Dt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&un[o])return void delete un[o];on(t)}}},tn.onerror=()=>{throw rn(1),"[ 2021-05-15 onerror ]"},tn.onclose=e=>{4e3===e.code||1001===e.code?rn(4):rn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${se.asQueryArgs(n)}`);return await o.json()},disconnect:function(){tn&&tn.close(4e3),cn=0,un={}},sendClientEvent:function(e){cn++,un[cn]=e,dn([Et,cn,un[cn]])},onServerChange:function(e){on=e;for(const t of nn)on(t);nn=[]},onConnectionStateChange:function(e){an=e;for(const t of ln)an(t);ln=[]},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},gn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===pn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===pn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const hn={key:0,class:"overlay connection-lost"},mn={key:0,class:"overlay-content"},yn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),fn={key:1,class:"overlay-content"},vn=n("div",null,"Connecting...",-1);gn.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",hn,[e.lostConnection?(s(),t("div",mn,[yn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",fn,[vn])):l("",!0)])):l("",!0)};var wn=e({name:"help-overlay",emits:{bgclick:null}});const bn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),xn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),Cn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),kn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),An=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Sn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Tn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),Pn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),In=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),zn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Dn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),_n=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),En=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),Mn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);wn.render=function(e,o,l,a,i,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]=u((()=>{}),["stop"]))},[bn,xn,Cn,kn,An,Sn,Tn,Pn,In,zn,Dn,_n,En,Mn])])};var Vn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),On=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Bn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Un=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Rn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),s=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),i=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:i,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function $n(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Gn={createCanvas:$n,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const o=$n(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=$n(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(),o}};const Fn=ae("Debug.js");let Ln=0,jn=0;var Wn=e=>{Ln=performance.now(),jn=e},Kn=e=>{const t=performance.now(),n=t-Ln;n>jn&&Fn.log(e+": "+n),Ln=t};function Hn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function Yn(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var Qn={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:Hn,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:Yn,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 Hn(Yn(e),Yn(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const qn=ae("PuzzleGraphics.js");function Zn(e,t){const n=se.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Xn={loadPuzzleBitmaps:async function(e){const t=await Gn.loadImageToBitmap(e.info.imageUrl),n=await Gn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){qn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=Qn.pointAdd(a,{x:o,y:0}),c=Qn.pointAdd(r,{x:0,y:o}),u=Qn.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;ose.decodePiece(Jn[e].puzzle.tiles[t]),mo=(e,t)=>ho(e,t).group,yo=(e,t)=>{const n=Jn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},fo=(e,t)=>{const n=Jn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return Qn.pointAdd(o,l)},vo=(e,t)=>ho(e,t).pos,wo=e=>{const t=Oo(e),n=Bo(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}},bo=(e,t)=>{const n=Ao(e),o=ho(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},xo=(e,t)=>ho(e,t).z,Co=(e,t)=>{for(const n of Jn[e].puzzle.tiles){const e=se.decodePiece(n);if(e.owner===t)return e.idx}return-1},ko=e=>Jn[e].puzzle.info.tileDrawSize,Ao=e=>Jn[e].puzzle.info.tileSize,So=e=>Jn[e].puzzle.data.maxGroup,To=e=>Jn[e].puzzle.data.maxZ;function Po(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Io=(e,t,n)=>{for(const o of t)go(e,o,{z:n})},zo=(e,t,n)=>{const o=vo(e,t);go(e,t,{pos:Qn.pointAdd(o,n)})},Do=(e,t,n)=>{const o=ko(e),l=wo(e),a=n;for(const s of t){const t=ho(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)zo(e,s,a)},_o=(e,t)=>ho(e,t).owner,Eo=(e,t)=>{for(const n of t)go(e,n,{owner:-1,z:1})},Mo=(e,t,n)=>{for(const o of t)go(e,o,{owner:n})};function Vo(e,t){const n=Jn[e].puzzle.tiles,o=se.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=se.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const No=(e,t)=>{const n=to(e,t);return n?n.points:0},Oo=e=>Jn[e].puzzle.info.table.width,Bo=e=>Jn[e].puzzle.info.table.height;var Uo={setGame:function(e,t){Jn[e]=t},exists:function(e){return!!Jn[e]||!1},playerExists:oo,getActivePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){oo(e,t)?uo(e,t,{ts:n}):no(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:co,getPieceCount:ao,getImageUrl:function(e){return Jn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Jn[e].puzzle.info.imageUrl=t},get:function(e){return Jn[e]||null},getAllGames:function(){return Object.values(Jn).sort(((e,t)=>ro(e.id)===ro(t.id)?t.puzzle.data.started-e.puzzle.data.started:ro(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=to(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=to(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=to(e,t);return n?n.name:null},getPlayerIndexById:eo,getPlayerIdByIndex:function(e,t){return Jn[e].players.length>t?se.decodePlayer(Jn[e].players[t]).id:null},changePlayer:uo,setPlayer:no,setPiece:function(e,t,n){Jn[e].puzzle.tiles[t]=se.encodePiece(n)},setPuzzleData:function(e,t){Jn[e].puzzle.data=t},getTableWidth:Oo,getTableHeight:Bo,getPuzzle:e=>Jn[e].puzzle,getRng:e=>Jn[e].rng.obj,getPuzzleWidth:e=>Jn[e].puzzle.info.width,getPuzzleHeight:e=>Jn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Jn[e].puzzle.tiles.map(se.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Co(e,t);return n<0?null:Jn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Jn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:ko,getFinalPiecePos:fo,getStartTs:e=>Jn[e].puzzle.data.started,getFinishTs:e=>Jn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Jn[e].puzzle,s=function(e,t){return t in Jn[e].evtInfos?Jn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([Zt,a.data])},d=t=>{i.push([Xt,se.encodePiece(ho(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=to(e,t);n&&i.push([Jt,se.encodePlayer(n)])},p=n[0];if(p===Lt){const l=n[1];uo(e,t,{bgcolor:l,ts:o}),u()}else if(p===jt){const l=n[1];uo(e,t,{color:l,ts:o}),u()}else if(p===Wt){const l=`${n[1]}`.substr(0,16);uo(e,t,{name:l,ts:o}),u()}else if(p===Bt){const l=n[1],a=n[2],s=to(e,t);if(s){const n=s.x-l,i=s.y-a;uo(e,t,{ts:o,x:n,y:i}),u()}}else if(p===Ut){const l={x:n[1],y:n[2]};uo(e,t,{d:1,ts:o}),u(),s._last_mouse_down=l;const a=((e,t)=>{const n=Jn[e].puzzle.info,o=Jn[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=To(e)+1;po(e,{maxZ:n}),r();const o=Vo(e,a);Io(e,o,To(e)),Mo(e,o,t),c(o)}s._last_mouse=l}else if(p===$t){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)uo(e,t,{x:l,y:a,ts:o}),u();else{const n=Co(e,t);if(n>=0){uo(e,t,{x:l,y:a,ts:o}),u();const r=Vo(e,n);let d=Qn.pointInBounds(i,wo(e))&&Qn.pointInBounds(s._last_mouse_down,wo(e));for(const t of r){const n=bo(e,t);if(Qn.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Do(e,r,{x:t,y:n}),c(r)}}else uo(e,t,{ts:o}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===Rt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Co(e,t);if(g>=0){const n=Vo(e,g);Mo(e,n,0),c(n);const s=vo(e,g),i=fo(e,g);let h=!1;if(io(e)===ee.REAL){for(const t of n)if(yo(e,t)){h=!0;break}}else h=!0;if(h&&Qn.pointDistance(i,s){const l=Jn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=mo(e,t),l=mo(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=vo(e,t),s=Qn.pointAdd(vo(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(Qn.pointDistance(a,s){const o=Jn[e].puzzle.tiles,l=mo(e,t),a=mo(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(po(e,{maxGroup:So(e)+1}),r(),s=So(e));if(go(e,t,{group:s}),d(t),go(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=se.decodePiece(r);i.includes(t.group)&&(go(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=Vo(e,t),((e,t)=>-1===_o(e,t))(e,n))Eo(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=xo(e,o);t>n&&(n=t)}return n})(e,l);Io(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Vo(e,g)){const o=Po(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])){a=!0;break}}if(a&&so(e)===q.ANY){const n=No(e,t)+1;uo(e,t,{d:p,ts:o,points:n}),u()}else uo(e,t,{d:p,ts:o}),u();a&&io(e)===ee.REAL&&co(e)===ao(e)&&(po(e,{finished:o}),r()),a&&l&&l(t)}}else uo(e,t,{d:p,ts:o}),u();s._last_mouse=i}else if(p===Gt){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else if(p===Ft){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else uo(e,t,{ts:o}),u();return function(e,t,n){Jn[e].evtInfos[t]=n}(e,t,s),i}};let Ro=-10,$o=20,Go=2,Fo=15;class Lo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Ro+Math.random()*$o,this.vy=-1*(Go+Math.random()*Fo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Go=t/2,Fo=t-Go;const n=1/4*this.canvas.width/(t/2);Ro=-n,$o=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Lo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Lo(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(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Gn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Zo=!0})),t}(l,Gn.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};pn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await pn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let x=()=>0;const C=async()=>{if("play"===o){const o=await pn.connect(n,e,t),l=se.decodeGame(o);Uo.setGame(l.id,l),x=()=>N()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=se.decodeGame(t.game);Uo.setGame(n.id,n),w.lastRealTs=N(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,x=()=>w.lastGameTs}}Zo=!0};await C();const k=Uo.getPieceDrawOffset(e),A=Uo.getPieceDrawSize(e),S=Uo.getPuzzleWidth(e),T=Uo.getPuzzleHeight(e),P=Uo.getTableWidth(e),I=Uo.getTableHeight(e),z={x:(P-S)/2,y:(I-T)/2},D={w:S,h:T},_={w:A,h:A},E=await Xn.loadPuzzleBitmaps(Uo.getPuzzle(e)),V=new Wo(v,Uo.getRng(e));V.init();const O=v.getContext("2d");v.classList.add("loaded");const B=Rn();B.move(-(P-v.width)/2,-(I-v.height)/2);const U=function(e,t,n,o){let l=[],a=!0,s=!1,i=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Ut,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Rt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([$t,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Gt:Ft;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([Kt]),"replay"===o&&("KeyI"===e.code&&v([Qt]),"KeyO"===e.code&&v([qt]),"KeyP"===e.code&&v([Yt])),"KeyF"===e.code&&(Qo=!Qo,Zo=!0),"KeyG"===e.code&&(qo=!qo,Zo=!0),"KeyM"===e.code&&v([Ht]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});v([Bt,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Gt,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([Ft,...e])}},setHotkeys:e=>{a=e}}}(v,window,B,o),R=Uo.getImageUrl(e),$=()=>{const t=Uo.getStartTs(e),n=Uo.getFinishTs(e),o=x();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),a.setPiecesTotal(Uo.getPieceCount(e));const G=x();a.setActivePlayers(Uo.getActivePlayers(e,G)),a.setIdlePlayers(Uo.getIdlePlayers(e,G));const F=!!Uo.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_volume");if(null===e)return 100;const t=parseInt(e,10);return isNaN(t)?100:t},K=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},H=()=>{const e=W();i.volume=e/100,i.play()},Y=()=>"replay"===o?localStorage.getItem("bg_color")||"#222222":Uo.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Q=()=>Uo.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let q="",Z="",X=!1;const J=e=>{X=e;const[t,n]=e?[q,"grab"]:[Z,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},ee=e=>{q=Gn.colorizedCanvas(r,c,e).toDataURL(),Z=Gn.colorizedCanvas(d,u,e).toDataURL(),J(X)};ee(Q());const te=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ne=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,te())},le=()=>{w.paused=!w.paused,te()},ae=[];let ie;let re;if("play"===o?ae.push(setInterval((()=>{$()}),1e3)):"replay"===o&&te(),"play"===o)pn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Jt:{const n=se.decodePlayer(a);n.id!==t&&(Uo.setPlayer(e,n.id,n),Zo=!0)}break;case Xt:{const t=se.decodePiece(a);Uo.setPiece(e,t.idx,t),Zo=!0}break;case Zt:Uo.setPuzzleData(e,a),Zo=!0}L=!!Uo.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===Vt){const t=o[1];return Uo.addPlayer(e,t,n),!0}if(o[0]===Nt){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Uo.addPlayer(e,t,n),!0}if(o[0]===Ot){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Uo.handleInput(e,t,l,n),!0}return!1};let n=w.lastGameTs;const o=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=N();if(w.paused)return w.lastRealTs=l,void(ie=setTimeout(o,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const o=w.log[w.logPointer],l=n+o[o.length-1],a=w.log[e],i=a[a.length-1],r=l+i;if(r>s){w.skipNonActionPhases&&s+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/o),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{U.createKeyEvents();for(const n of U.consumeAll())if("play"===o){const o=n[0];if(o===Bt){const e=n[1],t=n[2],o=B.worldDimToViewport({w:e,h:t});Zo=!0,B.move(o.w,o.h)}else if(o===$t){if(de&&!Uo.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Zo=!0,B.move(o,l),de=t}}else if(o===jt)ee(n[1]);else if(o===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e),J(!0)}else if(o===Rt)de=null,J(!1);else if(o===Gt){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("in",B.worldToViewport(e))}else if(o===Ft){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("out",B.worldToViewport(e))}else o===Kt?a.togglePreview():o===Ht&&a.toggleSoundsEnabled();const l=x();Uo.handleInput(e,t,n,l,(e=>{K()&&H()})).length>0&&(Zo=!0),pn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===Yt)le();else if(e===qt)oe();else if(e===Qt)ne();else if(e===Bt){const e=n[1],t=n[2];Zo=!0,B.move(e,t)}else if(e===$t){if(de){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Zo=!0,B.move(o,l),de=t}}else if(e===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e)}else if(e===Rt)de=null;else if(e===Gt){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("in",B.worldToViewport(e))}else if(e===Ft){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("out",B.worldToViewport(e))}else e===Kt&&a.togglePreview()}L=!!Uo.getFinishTs(e),j()&&(V.update(),Zo=!0)},render:async()=>{if(!Zo)return;const n=x();let l,s,i;window.DEBUG&&Wn(0),O.fillStyle=Y(),O.fillRect(0,0,v.width,v.height),window.DEBUG&&Kn("clear done"),l=B.worldToViewportRaw(z),s=B.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&Kn("board done");const r=Uo.getPiecesSortedByZIndex(e);window.DEBUG&&Kn("get tiles done"),s=B.worldDimToViewportRaw(_);for(const e of r)(-1===e.owner?Qo:qo)&&(i=E[e.idx],l=B.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&Kn("tiles done");const d=[];for(const a of Uo.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(i=await f(a),l=B.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,o]of d)O.fillText(e,t,o);window.DEBUG&&Kn("players done"),a.setActivePlayers(Uo.getActivePlayers(e,n)),a.setIdlePlayers(Uo.getIdlePlayers(e,n)),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),window.DEBUG&&Kn("HUD done"),j()&&V.render(),Zo=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([Lt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([jt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Wt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},onSoundsVolumeChange:e=>{Ko.info("vol changed",e),localStorage.setItem("sound_volume",`${e}`),H()},replayOnSpeedUp:ne,replayOnSpeedDown:oe,replayOnPauseToggle:le,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:R,player:{background:Y(),color:Q(),name:Uo.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:K(),soundsVolume:W()},disconnect:pn.disconnect,connect:C,unload:()=>{ae.forEach((e=>{clearInterval(e)})),ie&&clearTimeout(ie),re&&re.stop()}}}var Jo=e({name:"game",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:It,ConnectionOverlay:gn,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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)},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 el={id:"game"},tl={class:"menu"},nl={class:"tabs"},ol=i("🧩 Puzzles");Jo.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",el,[p(n(u,{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"]),[[C,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",tl,[n("div",nl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[ol])),_: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 ll=e({name:"replay",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:It,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 al={id:"replay"},sl=i("Skip no action phases: "),il={class:"menu"},rl={class:"tabs"},dl=i("🧩 Puzzles");ll.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",al,[p(n(g,{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"]),[[C,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[sl,p(n("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[x,e.skipNoAction]])])]),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",il,[n("div",rl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[dl])),_:1}),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:l[12]||(l[12]=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=k({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:nt},{name:"game",path:"/g/:id",component:Jo},{name:"replay",path:"/replay/:id",component:ll}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=S(T);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=se.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 9a6f879..b2e0d27 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,7 +4,7 @@ 🧩 jigsaw.hyottoko.club - + diff --git a/src/frontend/game.ts b/src/frontend/game.ts index 03ab612..725e0c6 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -463,6 +463,9 @@ export async function main( } const playerBgColor = () => { + if (MODE === MODE_REPLAY) { + return localStorage.getItem('bg_color') || '#222222' + } return (Game.getPlayerBgColor(gameId, clientId) || localStorage.getItem('bg_color') || '#222222') From 59d0d0dc2a18fd8df35804edda8d8485c9c0ea79 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Mon, 7 Jun 2021 00:41:01 +0200 Subject: [PATCH 13/45] fix issues with player bgcolor/color and possibly name in replay --- .../assets/{index.ac3207a7.js => index.c202d09d.js} | 2 +- build/public/index.html | 2 +- src/frontend/game.ts | 10 ++++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) rename build/public/assets/{index.ac3207a7.js => index.c202d09d.js} (77%) diff --git a/build/public/assets/index.ac3207a7.js b/build/public/assets/index.c202d09d.js similarity index 77% rename from build/public/assets/index.ac3207a7.js rename to build/public/assets/index.c202d09d.js index 98a476c..1fdadc8 100644 --- a/build/public/assets/index.ac3207a7.js +++ b/build/public/assets/index.c202d09d.js @@ -1 +1 @@ -import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as x,s as C,u as k,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const P={id:"app"},I={key:0,class:"nav"},z=i("Index"),D=i("New game");T.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",P,[e.showNav?(s(),t("ul",I,[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((()=>[D])),_:1})])])):l("",!0),n(g)])};const _=864e5,E=e=>{const t=Math.floor(e/_);e%=_;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 M=1,V=1e3,N=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>E(t-e),B=E,U=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||N();return`${n} ${O(o,l)}`}}});const R={class:"game-info-text"},$=n("br",null,null,-1),G=n("br",null,null,-1),F=n("br",null,null,-1),L=i(" ↪️ Watch replay ");U.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),$,i(" 👥 "+r(e.game.players),1),G,i(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[L])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:U},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 W=n("h1",null,"Running games",-1),K=n("h1",null,"Finished games",-1);j.render=function(e,o,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(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)),K,(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 H=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});H.render=function(e,o,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y,Q,q,Z,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:H},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(Q=Y||(Y={}))[Q.Flat=0]="Flat",Q[Q.Out=1]="Out",Q[Q.In=-1]="In",(Z=q||(q={}))[Z.FINAL=0]="FINAL",Z[Z.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),s=le(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var se={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||q.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const ie={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};ie.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-a4fa5e7e");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ce,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-a4fa5e7e";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=n("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},ve=n("span",{class:"btn"},"Upload File",-1),we={class:"area-settings"},be=n("td",null,[n("label",null,"Title")],-1),xe=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ce=n("td",null,[n("label",null,"Tags")],-1),ke={class:"area-buttons"},Ae=i("🧩 Post to gallery "),Se=n("br",null,null,-1),Te=i(" + set up game");ge.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(s(),t("div",me,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",ye,[n("label",fe,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ve])]))],34),n("div",we,[n("table",null,[n("tr",null,[be,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),xe,n("tr",null,[Ce,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",ke,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ae,Se,Te],8,["disabled"])])])])};var Pe=e({name:"edit-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Ie={class:"area-image"},ze={class:"has-image"},De={class:"area-settings"},_e=n("td",null,[n("label",null,"Title")],-1),Ee=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Me=n("td",null,[n("label",null,"Tags")],-1),Ve={class:"area-buttons"};Pe.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Ie,[n("div",ze,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",De,[n("table",null,[n("tr",null,[_e,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ee,n("tr",null,[Me,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Ve,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Ne=e({name:"new-game-dialog",components:{ResponsiveImage:ie},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:q.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Oe={class:"area-image"},Be={class:"has-image"},Ue={key:0,class:"image-title"},Re={class:"area-settings"},$e=n("td",null,[n("label",null,"Pieces")],-1),Ge=n("td",null,[n("label",null,"Scoring: ")],-1),Fe=i(" Any (Score when pieces are connected to each other or on final location)"),Le=n("br",null,null,-1),je=i(" Final (Score when pieces are put to their final location)"),We=n("td",null,[n("label",null,"Shapes: ")],-1),Ke=i(" Normal"),He=n("br",null,null,-1),Ye=i(" Any (flat pieces can occur anywhere)"),Qe=n("br",null,null,-1),qe=i(" Flat (all pieces flat on all sides)"),Ze=n("td",null,[n("label",null,"Snapping: ")],-1),Xe=i(" Normal (pieces snap to final destination and to each other)"),Je=n("br",null,null,-1),et=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),tt={class:"area-buttons"};Ne.render=function(e,o,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Oe,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(s(),t("div",Ue,'"'+r(e.image.title)+'"',1)):l("",!0)]),n("div",Re,[n("table",null,[n("tr",null,[$e,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[Ge,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Fe]),Le,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),je])])]),n("tr",null,[We,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),Ke]),He,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ye]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),qe])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Xe]),Je,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),et])])])])]),n("div",tt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var nt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Pe,NewGameDialog:Ne},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${se.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const ot={class:"upload-image-teaser"},lt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),at={key:0},st=i(" Tags: "),it=i(" Sort by: "),rt=n("option",{value:"date_desc"},"Newest first",-1),dt=n("option",{value:"date_asc"},"Oldest first",-1),ct=n("option",{value:"alpha_asc"},"A-Z",-1),ut=n("option",{value:"alpha_desc"},"Z-A",-1);nt.render=function(e,o,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[n("div",ot,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),lt]),n("div",null,[e.tags.length>0?(s(),t("label",at,[st,(s(!0),t(d,null,c(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[it,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[rt,dt,ct,ut],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var pt=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 gt={class:"scores"},ht=n("div",null,"Scores",-1),mt=n("td",null,"⚡",-1),yt=n("td",null,"💤",-1);pt.render=function(e,o,l,a,i,u){return s(),t("div",gt,[ht,n("table",null,[(s(!0),t(d,null,c(e.actives,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[mt,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}},[yt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var ft=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 B(this.duration)}}});const vt={class:"timer"};ft.render=function(e,o,l,a,i,d){return s(),t("div",vt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var wt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const bt=m();y("data-v-a1d1c822");const xt=n("td",null,[n("label",null,"Background: ")],-1),Ct=n("td",null,[n("label",null,"Color: ")],-1),kt=n("td",null,[n("label",null,"Name: ")],-1),At=n("td",null,[n("label",null,"Sounds: ")],-1),St=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Tt={class:"sound-volume"};f();const Pt=bt(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[9]||(o[9]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[8]||(o[8]=u((()=>{}),["stop"]))},[n("tr",null,[xt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[Ct,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[kt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[At,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[x,e.modelValue.soundsEnabled]])])]),n("tr",null,[St,n("td",Tt,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])])])]))));wt.render=Pt,wt.__scopeId="data-v-a1d1c822";var It=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const zt={class:"preview"};It.render=function(e,o,l,a,i,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",zt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Dt=1,_t=4,Et=2,Mt=3,Vt=2,Nt=4,Ot=3,Bt=9,Ut=1,Rt=2,$t=3,Gt=4,Ft=5,Lt=6,jt=7,Wt=8,Kt=10,Ht=11,Yt=12,Qt=13,qt=14,Zt=1,Xt=2,Jt=3;const en=ae("Communication.js");let tn,nn=[],on=e=>{nn.push(e)},ln=[],an=e=>{ln.push(e)};let sn=0;const rn=e=>{sn!==e&&(sn=e,an(e))};function dn(e){if(2===sn)try{tn.send(JSON.stringify(e))}catch(t){en.info("unable to send message.. maybe because ws is invalid?")}}let cn,un;var pn={connect:function(e,t,n){return cn=0,un={},rn(3),new Promise((o=>{tn=new WebSocket(e,n+"|"+t),tn.onopen=()=>{rn(2),dn([Mt])},tn.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===_t){const e=t[1];o(e)}else{if(l!==Dt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&un[o])return void delete un[o];on(t)}}},tn.onerror=()=>{throw rn(1),"[ 2021-05-15 onerror ]"},tn.onclose=e=>{4e3===e.code||1001===e.code?rn(4):rn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${se.asQueryArgs(n)}`);return await o.json()},disconnect:function(){tn&&tn.close(4e3),cn=0,un={}},sendClientEvent:function(e){cn++,un[cn]=e,dn([Et,cn,un[cn]])},onServerChange:function(e){on=e;for(const t of nn)on(t);nn=[]},onConnectionStateChange:function(e){an=e;for(const t of ln)an(t);ln=[]},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},gn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===pn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===pn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const hn={key:0,class:"overlay connection-lost"},mn={key:0,class:"overlay-content"},yn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),fn={key:1,class:"overlay-content"},vn=n("div",null,"Connecting...",-1);gn.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",hn,[e.lostConnection?(s(),t("div",mn,[yn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",fn,[vn])):l("",!0)])):l("",!0)};var wn=e({name:"help-overlay",emits:{bgclick:null}});const bn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),xn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),Cn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),kn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),An=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Sn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Tn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),Pn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),In=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),zn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Dn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),_n=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),En=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),Mn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);wn.render=function(e,o,l,a,i,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]=u((()=>{}),["stop"]))},[bn,xn,Cn,kn,An,Sn,Tn,Pn,In,zn,Dn,_n,En,Mn])])};var Vn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),On=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Bn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Un=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Rn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),s=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),i=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:i,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function $n(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Gn={createCanvas:$n,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const o=$n(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=$n(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(),o}};const Fn=ae("Debug.js");let Ln=0,jn=0;var Wn=e=>{Ln=performance.now(),jn=e},Kn=e=>{const t=performance.now(),n=t-Ln;n>jn&&Fn.log(e+": "+n),Ln=t};function Hn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function Yn(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var Qn={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:Hn,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:Yn,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 Hn(Yn(e),Yn(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const qn=ae("PuzzleGraphics.js");function Zn(e,t){const n=se.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Xn={loadPuzzleBitmaps:async function(e){const t=await Gn.loadImageToBitmap(e.info.imageUrl),n=await Gn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){qn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=Qn.pointAdd(a,{x:o,y:0}),c=Qn.pointAdd(r,{x:0,y:o}),u=Qn.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;ose.decodePiece(Jn[e].puzzle.tiles[t]),mo=(e,t)=>ho(e,t).group,yo=(e,t)=>{const n=Jn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},fo=(e,t)=>{const n=Jn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return Qn.pointAdd(o,l)},vo=(e,t)=>ho(e,t).pos,wo=e=>{const t=Oo(e),n=Bo(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}},bo=(e,t)=>{const n=Ao(e),o=ho(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},xo=(e,t)=>ho(e,t).z,Co=(e,t)=>{for(const n of Jn[e].puzzle.tiles){const e=se.decodePiece(n);if(e.owner===t)return e.idx}return-1},ko=e=>Jn[e].puzzle.info.tileDrawSize,Ao=e=>Jn[e].puzzle.info.tileSize,So=e=>Jn[e].puzzle.data.maxGroup,To=e=>Jn[e].puzzle.data.maxZ;function Po(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Io=(e,t,n)=>{for(const o of t)go(e,o,{z:n})},zo=(e,t,n)=>{const o=vo(e,t);go(e,t,{pos:Qn.pointAdd(o,n)})},Do=(e,t,n)=>{const o=ko(e),l=wo(e),a=n;for(const s of t){const t=ho(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)zo(e,s,a)},_o=(e,t)=>ho(e,t).owner,Eo=(e,t)=>{for(const n of t)go(e,n,{owner:-1,z:1})},Mo=(e,t,n)=>{for(const o of t)go(e,o,{owner:n})};function Vo(e,t){const n=Jn[e].puzzle.tiles,o=se.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=se.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const No=(e,t)=>{const n=to(e,t);return n?n.points:0},Oo=e=>Jn[e].puzzle.info.table.width,Bo=e=>Jn[e].puzzle.info.table.height;var Uo={setGame:function(e,t){Jn[e]=t},exists:function(e){return!!Jn[e]||!1},playerExists:oo,getActivePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){oo(e,t)?uo(e,t,{ts:n}):no(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:co,getPieceCount:ao,getImageUrl:function(e){return Jn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Jn[e].puzzle.info.imageUrl=t},get:function(e){return Jn[e]||null},getAllGames:function(){return Object.values(Jn).sort(((e,t)=>ro(e.id)===ro(t.id)?t.puzzle.data.started-e.puzzle.data.started:ro(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=to(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=to(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=to(e,t);return n?n.name:null},getPlayerIndexById:eo,getPlayerIdByIndex:function(e,t){return Jn[e].players.length>t?se.decodePlayer(Jn[e].players[t]).id:null},changePlayer:uo,setPlayer:no,setPiece:function(e,t,n){Jn[e].puzzle.tiles[t]=se.encodePiece(n)},setPuzzleData:function(e,t){Jn[e].puzzle.data=t},getTableWidth:Oo,getTableHeight:Bo,getPuzzle:e=>Jn[e].puzzle,getRng:e=>Jn[e].rng.obj,getPuzzleWidth:e=>Jn[e].puzzle.info.width,getPuzzleHeight:e=>Jn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Jn[e].puzzle.tiles.map(se.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Co(e,t);return n<0?null:Jn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Jn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:ko,getFinalPiecePos:fo,getStartTs:e=>Jn[e].puzzle.data.started,getFinishTs:e=>Jn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Jn[e].puzzle,s=function(e,t){return t in Jn[e].evtInfos?Jn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([Zt,a.data])},d=t=>{i.push([Xt,se.encodePiece(ho(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=to(e,t);n&&i.push([Jt,se.encodePlayer(n)])},p=n[0];if(p===Lt){const l=n[1];uo(e,t,{bgcolor:l,ts:o}),u()}else if(p===jt){const l=n[1];uo(e,t,{color:l,ts:o}),u()}else if(p===Wt){const l=`${n[1]}`.substr(0,16);uo(e,t,{name:l,ts:o}),u()}else if(p===Bt){const l=n[1],a=n[2],s=to(e,t);if(s){const n=s.x-l,i=s.y-a;uo(e,t,{ts:o,x:n,y:i}),u()}}else if(p===Ut){const l={x:n[1],y:n[2]};uo(e,t,{d:1,ts:o}),u(),s._last_mouse_down=l;const a=((e,t)=>{const n=Jn[e].puzzle.info,o=Jn[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=To(e)+1;po(e,{maxZ:n}),r();const o=Vo(e,a);Io(e,o,To(e)),Mo(e,o,t),c(o)}s._last_mouse=l}else if(p===$t){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)uo(e,t,{x:l,y:a,ts:o}),u();else{const n=Co(e,t);if(n>=0){uo(e,t,{x:l,y:a,ts:o}),u();const r=Vo(e,n);let d=Qn.pointInBounds(i,wo(e))&&Qn.pointInBounds(s._last_mouse_down,wo(e));for(const t of r){const n=bo(e,t);if(Qn.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Do(e,r,{x:t,y:n}),c(r)}}else uo(e,t,{ts:o}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===Rt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Co(e,t);if(g>=0){const n=Vo(e,g);Mo(e,n,0),c(n);const s=vo(e,g),i=fo(e,g);let h=!1;if(io(e)===ee.REAL){for(const t of n)if(yo(e,t)){h=!0;break}}else h=!0;if(h&&Qn.pointDistance(i,s){const l=Jn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=mo(e,t),l=mo(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=vo(e,t),s=Qn.pointAdd(vo(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(Qn.pointDistance(a,s){const o=Jn[e].puzzle.tiles,l=mo(e,t),a=mo(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(po(e,{maxGroup:So(e)+1}),r(),s=So(e));if(go(e,t,{group:s}),d(t),go(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=se.decodePiece(r);i.includes(t.group)&&(go(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=Vo(e,t),((e,t)=>-1===_o(e,t))(e,n))Eo(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=xo(e,o);t>n&&(n=t)}return n})(e,l);Io(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Vo(e,g)){const o=Po(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])){a=!0;break}}if(a&&so(e)===q.ANY){const n=No(e,t)+1;uo(e,t,{d:p,ts:o,points:n}),u()}else uo(e,t,{d:p,ts:o}),u();a&&io(e)===ee.REAL&&co(e)===ao(e)&&(po(e,{finished:o}),r()),a&&l&&l(t)}}else uo(e,t,{d:p,ts:o}),u();s._last_mouse=i}else if(p===Gt){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else if(p===Ft){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else uo(e,t,{ts:o}),u();return function(e,t,n){Jn[e].evtInfos[t]=n}(e,t,s),i}};let Ro=-10,$o=20,Go=2,Fo=15;class Lo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Ro+Math.random()*$o,this.vy=-1*(Go+Math.random()*Fo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Go=t/2,Fo=t-Go;const n=1/4*this.canvas.width/(t/2);Ro=-n,$o=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Lo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Lo(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(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Gn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Zo=!0})),t}(l,Gn.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};pn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await pn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let x=()=>0;const C=async()=>{if("play"===o){const o=await pn.connect(n,e,t),l=se.decodeGame(o);Uo.setGame(l.id,l),x=()=>N()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=se.decodeGame(t.game);Uo.setGame(n.id,n),w.lastRealTs=N(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,x=()=>w.lastGameTs}}Zo=!0};await C();const k=Uo.getPieceDrawOffset(e),A=Uo.getPieceDrawSize(e),S=Uo.getPuzzleWidth(e),T=Uo.getPuzzleHeight(e),P=Uo.getTableWidth(e),I=Uo.getTableHeight(e),z={x:(P-S)/2,y:(I-T)/2},D={w:S,h:T},_={w:A,h:A},E=await Xn.loadPuzzleBitmaps(Uo.getPuzzle(e)),V=new Wo(v,Uo.getRng(e));V.init();const O=v.getContext("2d");v.classList.add("loaded");const B=Rn();B.move(-(P-v.width)/2,-(I-v.height)/2);const U=function(e,t,n,o){let l=[],a=!0,s=!1,i=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Ut,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Rt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([$t,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Gt:Ft;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([Kt]),"replay"===o&&("KeyI"===e.code&&v([Qt]),"KeyO"===e.code&&v([qt]),"KeyP"===e.code&&v([Yt])),"KeyF"===e.code&&(Qo=!Qo,Zo=!0),"KeyG"===e.code&&(qo=!qo,Zo=!0),"KeyM"===e.code&&v([Ht]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});v([Bt,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Gt,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([Ft,...e])}},setHotkeys:e=>{a=e}}}(v,window,B,o),R=Uo.getImageUrl(e),$=()=>{const t=Uo.getStartTs(e),n=Uo.getFinishTs(e),o=x();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),a.setPiecesTotal(Uo.getPieceCount(e));const G=x();a.setActivePlayers(Uo.getActivePlayers(e,G)),a.setIdlePlayers(Uo.getIdlePlayers(e,G));const F=!!Uo.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_volume");if(null===e)return 100;const t=parseInt(e,10);return isNaN(t)?100:t},K=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},H=()=>{const e=W();i.volume=e/100,i.play()},Y=()=>"replay"===o?localStorage.getItem("bg_color")||"#222222":Uo.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Q=()=>Uo.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let q="",Z="",X=!1;const J=e=>{X=e;const[t,n]=e?[q,"grab"]:[Z,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},ee=e=>{q=Gn.colorizedCanvas(r,c,e).toDataURL(),Z=Gn.colorizedCanvas(d,u,e).toDataURL(),J(X)};ee(Q());const te=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ne=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,te())},le=()=>{w.paused=!w.paused,te()},ae=[];let ie;let re;if("play"===o?ae.push(setInterval((()=>{$()}),1e3)):"replay"===o&&te(),"play"===o)pn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Jt:{const n=se.decodePlayer(a);n.id!==t&&(Uo.setPlayer(e,n.id,n),Zo=!0)}break;case Xt:{const t=se.decodePiece(a);Uo.setPiece(e,t.idx,t),Zo=!0}break;case Zt:Uo.setPuzzleData(e,a),Zo=!0}L=!!Uo.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===Vt){const t=o[1];return Uo.addPlayer(e,t,n),!0}if(o[0]===Nt){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Uo.addPlayer(e,t,n),!0}if(o[0]===Ot){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Uo.handleInput(e,t,l,n),!0}return!1};let n=w.lastGameTs;const o=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=N();if(w.paused)return w.lastRealTs=l,void(ie=setTimeout(o,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const o=w.log[w.logPointer],l=n+o[o.length-1],a=w.log[e],i=a[a.length-1],r=l+i;if(r>s){w.skipNonActionPhases&&s+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/o),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{U.createKeyEvents();for(const n of U.consumeAll())if("play"===o){const o=n[0];if(o===Bt){const e=n[1],t=n[2],o=B.worldDimToViewport({w:e,h:t});Zo=!0,B.move(o.w,o.h)}else if(o===$t){if(de&&!Uo.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Zo=!0,B.move(o,l),de=t}}else if(o===jt)ee(n[1]);else if(o===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e),J(!0)}else if(o===Rt)de=null,J(!1);else if(o===Gt){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("in",B.worldToViewport(e))}else if(o===Ft){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("out",B.worldToViewport(e))}else o===Kt?a.togglePreview():o===Ht&&a.toggleSoundsEnabled();const l=x();Uo.handleInput(e,t,n,l,(e=>{K()&&H()})).length>0&&(Zo=!0),pn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===Yt)le();else if(e===qt)oe();else if(e===Qt)ne();else if(e===Bt){const e=n[1],t=n[2];Zo=!0,B.move(e,t)}else if(e===$t){if(de){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Zo=!0,B.move(o,l),de=t}}else if(e===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e)}else if(e===Rt)de=null;else if(e===Gt){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("in",B.worldToViewport(e))}else if(e===Ft){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("out",B.worldToViewport(e))}else e===Kt&&a.togglePreview()}L=!!Uo.getFinishTs(e),j()&&(V.update(),Zo=!0)},render:async()=>{if(!Zo)return;const n=x();let l,s,i;window.DEBUG&&Wn(0),O.fillStyle=Y(),O.fillRect(0,0,v.width,v.height),window.DEBUG&&Kn("clear done"),l=B.worldToViewportRaw(z),s=B.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&Kn("board done");const r=Uo.getPiecesSortedByZIndex(e);window.DEBUG&&Kn("get tiles done"),s=B.worldDimToViewportRaw(_);for(const e of r)(-1===e.owner?Qo:qo)&&(i=E[e.idx],l=B.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&Kn("tiles done");const d=[];for(const a of Uo.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(i=await f(a),l=B.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,o]of d)O.fillText(e,t,o);window.DEBUG&&Kn("players done"),a.setActivePlayers(Uo.getActivePlayers(e,n)),a.setIdlePlayers(Uo.getIdlePlayers(e,n)),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),window.DEBUG&&Kn("HUD done"),j()&&V.render(),Zo=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([Lt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([jt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Wt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},onSoundsVolumeChange:e=>{Ko.info("vol changed",e),localStorage.setItem("sound_volume",`${e}`),H()},replayOnSpeedUp:ne,replayOnSpeedDown:oe,replayOnPauseToggle:le,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:R,player:{background:Y(),color:Q(),name:Uo.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:K(),soundsVolume:W()},disconnect:pn.disconnect,connect:C,unload:()=>{ae.forEach((e=>{clearInterval(e)})),ie&&clearTimeout(ie),re&&re.stop()}}}var Jo=e({name:"game",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:It,ConnectionOverlay:gn,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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)},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 el={id:"game"},tl={class:"menu"},nl={class:"tabs"},ol=i("🧩 Puzzles");Jo.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",el,[p(n(u,{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"]),[[C,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",tl,[n("div",nl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[ol])),_: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 ll=e({name:"replay",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:It,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 al={id:"replay"},sl=i("Skip no action phases: "),il={class:"menu"},rl={class:"tabs"},dl=i("🧩 Puzzles");ll.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",al,[p(n(g,{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"]),[[C,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[sl,p(n("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[x,e.skipNoAction]])])]),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",il,[n("div",rl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[dl])),_:1}),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:l[12]||(l[12]=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=k({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:nt},{name:"game",path:"/g/:id",component:Jo},{name:"replay",path:"/replay/:id",component:ll}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=S(T);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=se.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); +import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as x,s as C,u as k,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const I={id:"app"},P={key:0,class:"nav"},z=i("Index"),D=i("New game");T.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",I,[e.showNav?(s(),t("ul",P,[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((()=>[D])),_:1})])])):l("",!0),n(g)])};const _=864e5,E=e=>{const t=Math.floor(e/_);e%=_;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 M=1,V=1e3,N=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>E(t-e),B=E,U=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||N();return`${n} ${O(o,l)}`}}});const R={class:"game-info-text"},$=n("br",null,null,-1),G=n("br",null,null,-1),F=n("br",null,null,-1),L=i(" ↪️ Watch replay ");U.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),$,i(" 👥 "+r(e.game.players),1),G,i(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[L])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:U},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 W=n("h1",null,"Running games",-1),K=n("h1",null,"Finished games",-1);j.render=function(e,o,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(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)),K,(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 H=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});H.render=function(e,o,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y,Q,q,Z,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:H},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(Q=Y||(Y={}))[Q.Flat=0]="Flat",Q[Q.Out=1]="Out",Q[Q.In=-1]="In",(Z=q||(q={}))[Z.FINAL=0]="FINAL",Z[Z.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),s=le(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var se={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||q.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const ie={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};ie.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-a4fa5e7e");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ce,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-a4fa5e7e";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=n("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},ve=n("span",{class:"btn"},"Upload File",-1),we={class:"area-settings"},be=n("td",null,[n("label",null,"Title")],-1),xe=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ce=n("td",null,[n("label",null,"Tags")],-1),ke={class:"area-buttons"},Ae=i("🧩 Post to gallery "),Se=n("br",null,null,-1),Te=i(" + set up game");ge.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(s(),t("div",me,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",ye,[n("label",fe,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ve])]))],34),n("div",we,[n("table",null,[n("tr",null,[be,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),xe,n("tr",null,[Ce,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",ke,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ae,Se,Te],8,["disabled"])])])])};var Ie=e({name:"edit-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Pe={class:"area-image"},ze={class:"has-image"},De={class:"area-settings"},_e=n("td",null,[n("label",null,"Title")],-1),Ee=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Me=n("td",null,[n("label",null,"Tags")],-1),Ve={class:"area-buttons"};Ie.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Pe,[n("div",ze,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",De,[n("table",null,[n("tr",null,[_e,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ee,n("tr",null,[Me,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Ve,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Ne=e({name:"new-game-dialog",components:{ResponsiveImage:ie},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:q.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Oe={class:"area-image"},Be={class:"has-image"},Ue={key:0,class:"image-title"},Re={class:"area-settings"},$e=n("td",null,[n("label",null,"Pieces")],-1),Ge=n("td",null,[n("label",null,"Scoring: ")],-1),Fe=i(" Any (Score when pieces are connected to each other or on final location)"),Le=n("br",null,null,-1),je=i(" Final (Score when pieces are put to their final location)"),We=n("td",null,[n("label",null,"Shapes: ")],-1),Ke=i(" Normal"),He=n("br",null,null,-1),Ye=i(" Any (flat pieces can occur anywhere)"),Qe=n("br",null,null,-1),qe=i(" Flat (all pieces flat on all sides)"),Ze=n("td",null,[n("label",null,"Snapping: ")],-1),Xe=i(" Normal (pieces snap to final destination and to each other)"),Je=n("br",null,null,-1),et=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),tt={class:"area-buttons"};Ne.render=function(e,o,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Oe,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(s(),t("div",Ue,'"'+r(e.image.title)+'"',1)):l("",!0)]),n("div",Re,[n("table",null,[n("tr",null,[$e,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[Ge,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Fe]),Le,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),je])])]),n("tr",null,[We,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),Ke]),He,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ye]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),qe])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Xe]),Je,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),et])])])])]),n("div",tt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var nt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Ie,NewGameDialog:Ne},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${se.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const ot={class:"upload-image-teaser"},lt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),at={key:0},st=i(" Tags: "),it=i(" Sort by: "),rt=n("option",{value:"date_desc"},"Newest first",-1),dt=n("option",{value:"date_asc"},"Oldest first",-1),ct=n("option",{value:"alpha_asc"},"A-Z",-1),ut=n("option",{value:"alpha_desc"},"Z-A",-1);nt.render=function(e,o,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[n("div",ot,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),lt]),n("div",null,[e.tags.length>0?(s(),t("label",at,[st,(s(!0),t(d,null,c(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[it,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[rt,dt,ct,ut],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var pt=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 gt={class:"scores"},ht=n("div",null,"Scores",-1),mt=n("td",null,"⚡",-1),yt=n("td",null,"💤",-1);pt.render=function(e,o,l,a,i,u){return s(),t("div",gt,[ht,n("table",null,[(s(!0),t(d,null,c(e.actives,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[mt,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}},[yt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var ft=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 B(this.duration)}}});const vt={class:"timer"};ft.render=function(e,o,l,a,i,d){return s(),t("div",vt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var wt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const bt=m();y("data-v-a1d1c822");const xt=n("td",null,[n("label",null,"Background: ")],-1),Ct=n("td",null,[n("label",null,"Color: ")],-1),kt=n("td",null,[n("label",null,"Name: ")],-1),At=n("td",null,[n("label",null,"Sounds: ")],-1),St=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Tt={class:"sound-volume"};f();const It=bt(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[9]||(o[9]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[8]||(o[8]=u((()=>{}),["stop"]))},[n("tr",null,[xt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[Ct,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[kt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[At,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[x,e.modelValue.soundsEnabled]])])]),n("tr",null,[St,n("td",Tt,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])])])]))));wt.render=It,wt.__scopeId="data-v-a1d1c822";var Pt=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const zt={class:"preview"};Pt.render=function(e,o,l,a,i,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",zt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Dt=1,_t=4,Et=2,Mt=3,Vt=2,Nt=4,Ot=3,Bt=9,Ut=1,Rt=2,$t=3,Gt=4,Ft=5,Lt=6,jt=7,Wt=8,Kt=10,Ht=11,Yt=12,Qt=13,qt=14,Zt=1,Xt=2,Jt=3;const en=ae("Communication.js");let tn,nn=[],on=e=>{nn.push(e)},ln=[],an=e=>{ln.push(e)};let sn=0;const rn=e=>{sn!==e&&(sn=e,an(e))};function dn(e){if(2===sn)try{tn.send(JSON.stringify(e))}catch(t){en.info("unable to send message.. maybe because ws is invalid?")}}let cn,un;var pn={connect:function(e,t,n){return cn=0,un={},rn(3),new Promise((o=>{tn=new WebSocket(e,n+"|"+t),tn.onopen=()=>{rn(2),dn([Mt])},tn.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===_t){const e=t[1];o(e)}else{if(l!==Dt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&un[o])return void delete un[o];on(t)}}},tn.onerror=()=>{throw rn(1),"[ 2021-05-15 onerror ]"},tn.onclose=e=>{4e3===e.code||1001===e.code?rn(4):rn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${se.asQueryArgs(n)}`);return await o.json()},disconnect:function(){tn&&tn.close(4e3),cn=0,un={}},sendClientEvent:function(e){cn++,un[cn]=e,dn([Et,cn,un[cn]])},onServerChange:function(e){on=e;for(const t of nn)on(t);nn=[]},onConnectionStateChange:function(e){an=e;for(const t of ln)an(t);ln=[]},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},gn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===pn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===pn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const hn={key:0,class:"overlay connection-lost"},mn={key:0,class:"overlay-content"},yn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),fn={key:1,class:"overlay-content"},vn=n("div",null,"Connecting...",-1);gn.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",hn,[e.lostConnection?(s(),t("div",mn,[yn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",fn,[vn])):l("",!0)])):l("",!0)};var wn=e({name:"help-overlay",emits:{bgclick:null}});const bn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),xn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),Cn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),kn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),An=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Sn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Tn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),In=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Pn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),zn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Dn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),_n=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),En=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),Mn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);wn.render=function(e,o,l,a,i,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]=u((()=>{}),["stop"]))},[bn,xn,Cn,kn,An,Sn,Tn,In,Pn,zn,Dn,_n,En,Mn])])};var Vn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),On=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Bn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Un=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Rn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),s=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),i=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:i,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function $n(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Gn={createCanvas:$n,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const o=$n(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=$n(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(),o}};const Fn=ae("Debug.js");let Ln=0,jn=0;var Wn=e=>{Ln=performance.now(),jn=e},Kn=e=>{const t=performance.now(),n=t-Ln;n>jn&&Fn.log(e+": "+n),Ln=t};function Hn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function Yn(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var Qn={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:Hn,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:Yn,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 Hn(Yn(e),Yn(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const qn=ae("PuzzleGraphics.js");function Zn(e,t){const n=se.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Xn={loadPuzzleBitmaps:async function(e){const t=await Gn.loadImageToBitmap(e.info.imageUrl),n=await Gn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){qn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=Qn.pointAdd(a,{x:o,y:0}),c=Qn.pointAdd(r,{x:0,y:o}),u=Qn.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;ose.decodePiece(Jn[e].puzzle.tiles[t]),mo=(e,t)=>ho(e,t).group,yo=(e,t)=>{const n=Jn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},fo=(e,t)=>{const n=Jn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return Qn.pointAdd(o,l)},vo=(e,t)=>ho(e,t).pos,wo=e=>{const t=Oo(e),n=Bo(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}},bo=(e,t)=>{const n=Ao(e),o=ho(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},xo=(e,t)=>ho(e,t).z,Co=(e,t)=>{for(const n of Jn[e].puzzle.tiles){const e=se.decodePiece(n);if(e.owner===t)return e.idx}return-1},ko=e=>Jn[e].puzzle.info.tileDrawSize,Ao=e=>Jn[e].puzzle.info.tileSize,So=e=>Jn[e].puzzle.data.maxGroup,To=e=>Jn[e].puzzle.data.maxZ;function Io(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Po=(e,t,n)=>{for(const o of t)go(e,o,{z:n})},zo=(e,t,n)=>{const o=vo(e,t);go(e,t,{pos:Qn.pointAdd(o,n)})},Do=(e,t,n)=>{const o=ko(e),l=wo(e),a=n;for(const s of t){const t=ho(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)zo(e,s,a)},_o=(e,t)=>ho(e,t).owner,Eo=(e,t)=>{for(const n of t)go(e,n,{owner:-1,z:1})},Mo=(e,t,n)=>{for(const o of t)go(e,o,{owner:n})};function Vo(e,t){const n=Jn[e].puzzle.tiles,o=se.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=se.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const No=(e,t)=>{const n=to(e,t);return n?n.points:0},Oo=e=>Jn[e].puzzle.info.table.width,Bo=e=>Jn[e].puzzle.info.table.height;var Uo={setGame:function(e,t){Jn[e]=t},exists:function(e){return!!Jn[e]||!1},playerExists:oo,getActivePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){oo(e,t)?uo(e,t,{ts:n}):no(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:co,getPieceCount:ao,getImageUrl:function(e){return Jn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Jn[e].puzzle.info.imageUrl=t},get:function(e){return Jn[e]||null},getAllGames:function(){return Object.values(Jn).sort(((e,t)=>ro(e.id)===ro(t.id)?t.puzzle.data.started-e.puzzle.data.started:ro(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=to(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=to(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=to(e,t);return n?n.name:null},getPlayerIndexById:eo,getPlayerIdByIndex:function(e,t){return Jn[e].players.length>t?se.decodePlayer(Jn[e].players[t]).id:null},changePlayer:uo,setPlayer:no,setPiece:function(e,t,n){Jn[e].puzzle.tiles[t]=se.encodePiece(n)},setPuzzleData:function(e,t){Jn[e].puzzle.data=t},getTableWidth:Oo,getTableHeight:Bo,getPuzzle:e=>Jn[e].puzzle,getRng:e=>Jn[e].rng.obj,getPuzzleWidth:e=>Jn[e].puzzle.info.width,getPuzzleHeight:e=>Jn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Jn[e].puzzle.tiles.map(se.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Co(e,t);return n<0?null:Jn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Jn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:ko,getFinalPiecePos:fo,getStartTs:e=>Jn[e].puzzle.data.started,getFinishTs:e=>Jn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Jn[e].puzzle,s=function(e,t){return t in Jn[e].evtInfos?Jn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([Zt,a.data])},d=t=>{i.push([Xt,se.encodePiece(ho(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=to(e,t);n&&i.push([Jt,se.encodePlayer(n)])},p=n[0];if(p===Lt){const l=n[1];uo(e,t,{bgcolor:l,ts:o}),u()}else if(p===jt){const l=n[1];uo(e,t,{color:l,ts:o}),u()}else if(p===Wt){const l=`${n[1]}`.substr(0,16);uo(e,t,{name:l,ts:o}),u()}else if(p===Bt){const l=n[1],a=n[2],s=to(e,t);if(s){const n=s.x-l,i=s.y-a;uo(e,t,{ts:o,x:n,y:i}),u()}}else if(p===Ut){const l={x:n[1],y:n[2]};uo(e,t,{d:1,ts:o}),u(),s._last_mouse_down=l;const a=((e,t)=>{const n=Jn[e].puzzle.info,o=Jn[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=To(e)+1;po(e,{maxZ:n}),r();const o=Vo(e,a);Po(e,o,To(e)),Mo(e,o,t),c(o)}s._last_mouse=l}else if(p===$t){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)uo(e,t,{x:l,y:a,ts:o}),u();else{const n=Co(e,t);if(n>=0){uo(e,t,{x:l,y:a,ts:o}),u();const r=Vo(e,n);let d=Qn.pointInBounds(i,wo(e))&&Qn.pointInBounds(s._last_mouse_down,wo(e));for(const t of r){const n=bo(e,t);if(Qn.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Do(e,r,{x:t,y:n}),c(r)}}else uo(e,t,{ts:o}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===Rt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Co(e,t);if(g>=0){const n=Vo(e,g);Mo(e,n,0),c(n);const s=vo(e,g),i=fo(e,g);let h=!1;if(io(e)===ee.REAL){for(const t of n)if(yo(e,t)){h=!0;break}}else h=!0;if(h&&Qn.pointDistance(i,s){const l=Jn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=mo(e,t),l=mo(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=vo(e,t),s=Qn.pointAdd(vo(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(Qn.pointDistance(a,s){const o=Jn[e].puzzle.tiles,l=mo(e,t),a=mo(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(po(e,{maxGroup:So(e)+1}),r(),s=So(e));if(go(e,t,{group:s}),d(t),go(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=se.decodePiece(r);i.includes(t.group)&&(go(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=Vo(e,t),((e,t)=>-1===_o(e,t))(e,n))Eo(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=xo(e,o);t>n&&(n=t)}return n})(e,l);Po(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Vo(e,g)){const o=Io(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])){a=!0;break}}if(a&&so(e)===q.ANY){const n=No(e,t)+1;uo(e,t,{d:p,ts:o,points:n}),u()}else uo(e,t,{d:p,ts:o}),u();a&&io(e)===ee.REAL&&co(e)===ao(e)&&(po(e,{finished:o}),r()),a&&l&&l(t)}}else uo(e,t,{d:p,ts:o}),u();s._last_mouse=i}else if(p===Gt){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else if(p===Ft){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else uo(e,t,{ts:o}),u();return function(e,t,n){Jn[e].evtInfos[t]=n}(e,t,s),i}};let Ro=-10,$o=20,Go=2,Fo=15;class Lo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Ro+Math.random()*$o,this.vy=-1*(Go+Math.random()*Fo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Go=t/2,Fo=t-Go;const n=1/4*this.canvas.width/(t/2);Ro=-n,$o=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Lo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Lo(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(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Gn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Zo=!0})),t}(l,Gn.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};pn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await pn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let x=()=>0;const C=async()=>{if("play"===o){const o=await pn.connect(n,e,t),l=se.decodeGame(o);Uo.setGame(l.id,l),x=()=>N()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=se.decodeGame(t.game);Uo.setGame(n.id,n),w.lastRealTs=N(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,x=()=>w.lastGameTs}}Zo=!0};await C();const k=Uo.getPieceDrawOffset(e),A=Uo.getPieceDrawSize(e),S=Uo.getPuzzleWidth(e),T=Uo.getPuzzleHeight(e),I=Uo.getTableWidth(e),P=Uo.getTableHeight(e),z={x:(I-S)/2,y:(P-T)/2},D={w:S,h:T},_={w:A,h:A},E=await Xn.loadPuzzleBitmaps(Uo.getPuzzle(e)),V=new Wo(v,Uo.getRng(e));V.init();const O=v.getContext("2d");v.classList.add("loaded");const B=Rn();B.move(-(I-v.width)/2,-(P-v.height)/2);const U=function(e,t,n,o){let l=[],a=!0,s=!1,i=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Ut,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Rt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([$t,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Gt:Ft;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([Kt]),"replay"===o&&("KeyI"===e.code&&v([Qt]),"KeyO"===e.code&&v([qt]),"KeyP"===e.code&&v([Yt])),"KeyF"===e.code&&(Qo=!Qo,Zo=!0),"KeyG"===e.code&&(qo=!qo,Zo=!0),"KeyM"===e.code&&v([Ht]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});v([Bt,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Gt,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([Ft,...e])}},setHotkeys:e=>{a=e}}}(v,window,B,o),R=Uo.getImageUrl(e),$=()=>{const t=Uo.getStartTs(e),n=Uo.getFinishTs(e),o=x();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),a.setPiecesTotal(Uo.getPieceCount(e));const G=x();a.setActivePlayers(Uo.getActivePlayers(e,G)),a.setIdlePlayers(Uo.getIdlePlayers(e,G));const F=!!Uo.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_volume");if(null===e)return 100;const t=parseInt(e,10);return isNaN(t)?100:t},K=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},H=()=>{const e=W();i.volume=e/100,i.play()},Y=()=>"replay"===o?localStorage.getItem("bg_color")||"#222222":Uo.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Q=()=>"replay"===o?localStorage.getItem("player_color")||"#ffffff":Uo.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let q="",Z="",X=!1;const J=e=>{X=e;const[t,n]=e?[q,"grab"]:[Z,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},ee=e=>{q=Gn.colorizedCanvas(r,c,e).toDataURL(),Z=Gn.colorizedCanvas(d,u,e).toDataURL(),J(X)};ee(Q());const te=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ne=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,te())},le=()=>{w.paused=!w.paused,te()},ae=[];let ie;let re;if("play"===o?ae.push(setInterval((()=>{$()}),1e3)):"replay"===o&&te(),"play"===o)pn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Jt:{const n=se.decodePlayer(a);n.id!==t&&(Uo.setPlayer(e,n.id,n),Zo=!0)}break;case Xt:{const t=se.decodePiece(a);Uo.setPiece(e,t.idx,t),Zo=!0}break;case Zt:Uo.setPuzzleData(e,a),Zo=!0}L=!!Uo.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===Vt){const t=o[1];return Uo.addPlayer(e,t,n),!0}if(o[0]===Nt){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Uo.addPlayer(e,t,n),!0}if(o[0]===Ot){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Uo.handleInput(e,t,l,n),!0}return!1};let n=w.lastGameTs;const o=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=N();if(w.paused)return w.lastRealTs=l,void(ie=setTimeout(o,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const o=w.log[w.logPointer],l=n+o[o.length-1],a=w.log[e],i=a[a.length-1],r=l+i;if(r>s){w.skipNonActionPhases&&s+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/o),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{U.createKeyEvents();for(const n of U.consumeAll())if("play"===o){const o=n[0];if(o===Bt){const e=n[1],t=n[2],o=B.worldDimToViewport({w:e,h:t});Zo=!0,B.move(o.w,o.h)}else if(o===$t){if(de&&!Uo.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Zo=!0,B.move(o,l),de=t}}else if(o===jt)ee(n[1]);else if(o===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e),J(!0)}else if(o===Rt)de=null,J(!1);else if(o===Gt){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("in",B.worldToViewport(e))}else if(o===Ft){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("out",B.worldToViewport(e))}else o===Kt?a.togglePreview():o===Ht&&a.toggleSoundsEnabled();const l=x();Uo.handleInput(e,t,n,l,(e=>{K()&&H()})).length>0&&(Zo=!0),pn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===Yt)le();else if(e===qt)oe();else if(e===Qt)ne();else if(e===Bt){const e=n[1],t=n[2];Zo=!0,B.move(e,t)}else if(e===$t){if(de){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Zo=!0,B.move(o,l),de=t}}else if(e===jt)ee(n[1]);else if(e===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e),J(!0)}else if(e===Rt)de=null,J(!1);else if(e===Gt){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("in",B.worldToViewport(e))}else if(e===Ft){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("out",B.worldToViewport(e))}else e===Kt&&a.togglePreview()}L=!!Uo.getFinishTs(e),j()&&(V.update(),Zo=!0)},render:async()=>{if(!Zo)return;const n=x();let l,s,i;window.DEBUG&&Wn(0),O.fillStyle=Y(),O.fillRect(0,0,v.width,v.height),window.DEBUG&&Kn("clear done"),l=B.worldToViewportRaw(z),s=B.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&Kn("board done");const r=Uo.getPiecesSortedByZIndex(e);window.DEBUG&&Kn("get tiles done"),s=B.worldDimToViewportRaw(_);for(const e of r)(-1===e.owner?Qo:qo)&&(i=E[e.idx],l=B.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&Kn("tiles done");const d=[];for(const a of Uo.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(i=await f(a),l=B.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,o]of d)O.fillText(e,t,o);window.DEBUG&&Kn("players done"),a.setActivePlayers(Uo.getActivePlayers(e,n)),a.setIdlePlayers(Uo.getIdlePlayers(e,n)),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),window.DEBUG&&Kn("HUD done"),j()&&V.render(),Zo=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([Lt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([jt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Wt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},onSoundsVolumeChange:e=>{Ko.info("vol changed",e),localStorage.setItem("sound_volume",`${e}`),H()},replayOnSpeedUp:ne,replayOnSpeedDown:oe,replayOnPauseToggle:le,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:R,player:{background:Y(),color:Q(),name:"replay"===o?localStorage.getItem("player_name")||"#ffffff":Uo.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:K(),soundsVolume:W()},disconnect:pn.disconnect,connect:C,unload:()=>{ae.forEach((e=>{clearInterval(e)})),ie&&clearTimeout(ie),re&&re.stop()}}}var Jo=e({name:"game",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:Pt,ConnectionOverlay:gn,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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)},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 el={id:"game"},tl={class:"menu"},nl={class:"tabs"},ol=i("🧩 Puzzles");Jo.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",el,[p(n(u,{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"]),[[C,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",tl,[n("div",nl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[ol])),_: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 ll=e({name:"replay",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:Pt,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 al={id:"replay"},sl=i("Skip no action phases: "),il={class:"menu"},rl={class:"tabs"},dl=i("🧩 Puzzles");ll.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",al,[p(n(g,{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"]),[[C,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[sl,p(n("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[x,e.skipNoAction]])])]),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",il,[n("div",rl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[dl])),_:1}),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:l[12]||(l[12]=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=k({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:nt},{name:"game",path:"/g/:id",component:Jo},{name:"replay",path:"/replay/:id",component:ll}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=S(T);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=se.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 b2e0d27..ff7cb1b 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,7 +4,7 @@ 🧩 jigsaw.hyottoko.club - + diff --git a/src/frontend/game.ts b/src/frontend/game.ts index 725e0c6..cd1fec8 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -471,11 +471,17 @@ export async function main( || '#222222') } const playerColor = () => { + if (MODE === MODE_REPLAY) { + return localStorage.getItem('player_color') || '#ffffff' + } return (Game.getPlayerColor(gameId, clientId) || localStorage.getItem('player_color') || '#ffffff') } const playerName = () => { + if (MODE === MODE_REPLAY) { + return localStorage.getItem('player_name') || '#ffffff' + } return (Game.getPlayerName(gameId, clientId) || localStorage.getItem('player_name') || 'anon') @@ -763,11 +769,15 @@ export async function main( _last_mouse_down = mouse } + } else if (type === Protocol.INPUT_EV_PLAYER_COLOR) { + updatePlayerCursorColor(evt[1]) } else if (type === Protocol.INPUT_EV_MOUSE_DOWN) { const pos = { x: evt[1], y: evt[2] } _last_mouse_down = viewport.worldToViewport(pos) + updatePlayerCursorState(true) } else if (type === Protocol.INPUT_EV_MOUSE_UP) { _last_mouse_down = null + updatePlayerCursorState(false) } else if (type === Protocol.INPUT_EV_ZOOM_IN) { const pos = { x: evt[1], y: evt[2] } RERENDER = true From accd38eb022c5311966f8398ece2b9f2602c3d14 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Mon, 7 Jun 2021 00:48:22 +0200 Subject: [PATCH 14/45] make game and replay vue more similar --- .../assets/{index.c202d09d.js => index.1a1747a7.js} | 2 +- build/public/index.html | 2 +- src/frontend/views/Game.vue | 2 +- src/frontend/views/Replay.vue | 9 +++++---- 4 files changed, 8 insertions(+), 7 deletions(-) rename build/public/assets/{index.c202d09d.js => index.1a1747a7.js} (99%) diff --git a/build/public/assets/index.c202d09d.js b/build/public/assets/index.1a1747a7.js similarity index 99% rename from build/public/assets/index.c202d09d.js rename to build/public/assets/index.1a1747a7.js index 1fdadc8..e5421b5 100644 --- a/build/public/assets/index.c202d09d.js +++ b/build/public/assets/index.1a1747a7.js @@ -1 +1 @@ -import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as x,s as C,u as k,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const I={id:"app"},P={key:0,class:"nav"},z=i("Index"),D=i("New game");T.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",I,[e.showNav?(s(),t("ul",P,[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((()=>[D])),_:1})])])):l("",!0),n(g)])};const _=864e5,E=e=>{const t=Math.floor(e/_);e%=_;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 M=1,V=1e3,N=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>E(t-e),B=E,U=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||N();return`${n} ${O(o,l)}`}}});const R={class:"game-info-text"},$=n("br",null,null,-1),G=n("br",null,null,-1),F=n("br",null,null,-1),L=i(" ↪️ Watch replay ");U.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),$,i(" 👥 "+r(e.game.players),1),G,i(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[L])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:U},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 W=n("h1",null,"Running games",-1),K=n("h1",null,"Finished games",-1);j.render=function(e,o,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(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)),K,(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 H=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});H.render=function(e,o,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y,Q,q,Z,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:H},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(Q=Y||(Y={}))[Q.Flat=0]="Flat",Q[Q.Out=1]="Out",Q[Q.In=-1]="In",(Z=q||(q={}))[Z.FINAL=0]="FINAL",Z[Z.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),s=le(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var se={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||q.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const ie={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};ie.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-a4fa5e7e");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ce,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-a4fa5e7e";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=n("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},ve=n("span",{class:"btn"},"Upload File",-1),we={class:"area-settings"},be=n("td",null,[n("label",null,"Title")],-1),xe=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ce=n("td",null,[n("label",null,"Tags")],-1),ke={class:"area-buttons"},Ae=i("🧩 Post to gallery "),Se=n("br",null,null,-1),Te=i(" + set up game");ge.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(s(),t("div",me,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",ye,[n("label",fe,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ve])]))],34),n("div",we,[n("table",null,[n("tr",null,[be,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),xe,n("tr",null,[Ce,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",ke,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ae,Se,Te],8,["disabled"])])])])};var Ie=e({name:"edit-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Pe={class:"area-image"},ze={class:"has-image"},De={class:"area-settings"},_e=n("td",null,[n("label",null,"Title")],-1),Ee=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Me=n("td",null,[n("label",null,"Tags")],-1),Ve={class:"area-buttons"};Ie.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Pe,[n("div",ze,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",De,[n("table",null,[n("tr",null,[_e,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ee,n("tr",null,[Me,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Ve,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Ne=e({name:"new-game-dialog",components:{ResponsiveImage:ie},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:q.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Oe={class:"area-image"},Be={class:"has-image"},Ue={key:0,class:"image-title"},Re={class:"area-settings"},$e=n("td",null,[n("label",null,"Pieces")],-1),Ge=n("td",null,[n("label",null,"Scoring: ")],-1),Fe=i(" Any (Score when pieces are connected to each other or on final location)"),Le=n("br",null,null,-1),je=i(" Final (Score when pieces are put to their final location)"),We=n("td",null,[n("label",null,"Shapes: ")],-1),Ke=i(" Normal"),He=n("br",null,null,-1),Ye=i(" Any (flat pieces can occur anywhere)"),Qe=n("br",null,null,-1),qe=i(" Flat (all pieces flat on all sides)"),Ze=n("td",null,[n("label",null,"Snapping: ")],-1),Xe=i(" Normal (pieces snap to final destination and to each other)"),Je=n("br",null,null,-1),et=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),tt={class:"area-buttons"};Ne.render=function(e,o,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Oe,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(s(),t("div",Ue,'"'+r(e.image.title)+'"',1)):l("",!0)]),n("div",Re,[n("table",null,[n("tr",null,[$e,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[Ge,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Fe]),Le,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),je])])]),n("tr",null,[We,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),Ke]),He,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ye]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),qe])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Xe]),Je,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),et])])])])]),n("div",tt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var nt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Ie,NewGameDialog:Ne},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${se.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const ot={class:"upload-image-teaser"},lt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),at={key:0},st=i(" Tags: "),it=i(" Sort by: "),rt=n("option",{value:"date_desc"},"Newest first",-1),dt=n("option",{value:"date_asc"},"Oldest first",-1),ct=n("option",{value:"alpha_asc"},"A-Z",-1),ut=n("option",{value:"alpha_desc"},"Z-A",-1);nt.render=function(e,o,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[n("div",ot,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),lt]),n("div",null,[e.tags.length>0?(s(),t("label",at,[st,(s(!0),t(d,null,c(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[it,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[rt,dt,ct,ut],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var pt=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 gt={class:"scores"},ht=n("div",null,"Scores",-1),mt=n("td",null,"⚡",-1),yt=n("td",null,"💤",-1);pt.render=function(e,o,l,a,i,u){return s(),t("div",gt,[ht,n("table",null,[(s(!0),t(d,null,c(e.actives,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[mt,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}},[yt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var ft=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 B(this.duration)}}});const vt={class:"timer"};ft.render=function(e,o,l,a,i,d){return s(),t("div",vt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var wt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const bt=m();y("data-v-a1d1c822");const xt=n("td",null,[n("label",null,"Background: ")],-1),Ct=n("td",null,[n("label",null,"Color: ")],-1),kt=n("td",null,[n("label",null,"Name: ")],-1),At=n("td",null,[n("label",null,"Sounds: ")],-1),St=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Tt={class:"sound-volume"};f();const It=bt(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[9]||(o[9]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[8]||(o[8]=u((()=>{}),["stop"]))},[n("tr",null,[xt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[Ct,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[kt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[At,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[x,e.modelValue.soundsEnabled]])])]),n("tr",null,[St,n("td",Tt,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])])])]))));wt.render=It,wt.__scopeId="data-v-a1d1c822";var Pt=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const zt={class:"preview"};Pt.render=function(e,o,l,a,i,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",zt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Dt=1,_t=4,Et=2,Mt=3,Vt=2,Nt=4,Ot=3,Bt=9,Ut=1,Rt=2,$t=3,Gt=4,Ft=5,Lt=6,jt=7,Wt=8,Kt=10,Ht=11,Yt=12,Qt=13,qt=14,Zt=1,Xt=2,Jt=3;const en=ae("Communication.js");let tn,nn=[],on=e=>{nn.push(e)},ln=[],an=e=>{ln.push(e)};let sn=0;const rn=e=>{sn!==e&&(sn=e,an(e))};function dn(e){if(2===sn)try{tn.send(JSON.stringify(e))}catch(t){en.info("unable to send message.. maybe because ws is invalid?")}}let cn,un;var pn={connect:function(e,t,n){return cn=0,un={},rn(3),new Promise((o=>{tn=new WebSocket(e,n+"|"+t),tn.onopen=()=>{rn(2),dn([Mt])},tn.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===_t){const e=t[1];o(e)}else{if(l!==Dt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&un[o])return void delete un[o];on(t)}}},tn.onerror=()=>{throw rn(1),"[ 2021-05-15 onerror ]"},tn.onclose=e=>{4e3===e.code||1001===e.code?rn(4):rn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${se.asQueryArgs(n)}`);return await o.json()},disconnect:function(){tn&&tn.close(4e3),cn=0,un={}},sendClientEvent:function(e){cn++,un[cn]=e,dn([Et,cn,un[cn]])},onServerChange:function(e){on=e;for(const t of nn)on(t);nn=[]},onConnectionStateChange:function(e){an=e;for(const t of ln)an(t);ln=[]},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},gn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===pn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===pn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const hn={key:0,class:"overlay connection-lost"},mn={key:0,class:"overlay-content"},yn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),fn={key:1,class:"overlay-content"},vn=n("div",null,"Connecting...",-1);gn.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",hn,[e.lostConnection?(s(),t("div",mn,[yn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",fn,[vn])):l("",!0)])):l("",!0)};var wn=e({name:"help-overlay",emits:{bgclick:null}});const bn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),xn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),Cn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),kn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),An=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Sn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Tn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),In=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Pn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),zn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Dn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),_n=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),En=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),Mn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);wn.render=function(e,o,l,a,i,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]=u((()=>{}),["stop"]))},[bn,xn,Cn,kn,An,Sn,Tn,In,Pn,zn,Dn,_n,En,Mn])])};var Vn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),On=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Bn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Un=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Rn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),s=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),i=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:i,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function $n(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Gn={createCanvas:$n,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const o=$n(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=$n(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(),o}};const Fn=ae("Debug.js");let Ln=0,jn=0;var Wn=e=>{Ln=performance.now(),jn=e},Kn=e=>{const t=performance.now(),n=t-Ln;n>jn&&Fn.log(e+": "+n),Ln=t};function Hn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function Yn(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var Qn={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:Hn,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:Yn,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 Hn(Yn(e),Yn(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const qn=ae("PuzzleGraphics.js");function Zn(e,t){const n=se.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Xn={loadPuzzleBitmaps:async function(e){const t=await Gn.loadImageToBitmap(e.info.imageUrl),n=await Gn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){qn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=Qn.pointAdd(a,{x:o,y:0}),c=Qn.pointAdd(r,{x:0,y:o}),u=Qn.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;ose.decodePiece(Jn[e].puzzle.tiles[t]),mo=(e,t)=>ho(e,t).group,yo=(e,t)=>{const n=Jn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},fo=(e,t)=>{const n=Jn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return Qn.pointAdd(o,l)},vo=(e,t)=>ho(e,t).pos,wo=e=>{const t=Oo(e),n=Bo(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}},bo=(e,t)=>{const n=Ao(e),o=ho(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},xo=(e,t)=>ho(e,t).z,Co=(e,t)=>{for(const n of Jn[e].puzzle.tiles){const e=se.decodePiece(n);if(e.owner===t)return e.idx}return-1},ko=e=>Jn[e].puzzle.info.tileDrawSize,Ao=e=>Jn[e].puzzle.info.tileSize,So=e=>Jn[e].puzzle.data.maxGroup,To=e=>Jn[e].puzzle.data.maxZ;function Io(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Po=(e,t,n)=>{for(const o of t)go(e,o,{z:n})},zo=(e,t,n)=>{const o=vo(e,t);go(e,t,{pos:Qn.pointAdd(o,n)})},Do=(e,t,n)=>{const o=ko(e),l=wo(e),a=n;for(const s of t){const t=ho(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)zo(e,s,a)},_o=(e,t)=>ho(e,t).owner,Eo=(e,t)=>{for(const n of t)go(e,n,{owner:-1,z:1})},Mo=(e,t,n)=>{for(const o of t)go(e,o,{owner:n})};function Vo(e,t){const n=Jn[e].puzzle.tiles,o=se.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=se.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const No=(e,t)=>{const n=to(e,t);return n?n.points:0},Oo=e=>Jn[e].puzzle.info.table.width,Bo=e=>Jn[e].puzzle.info.table.height;var Uo={setGame:function(e,t){Jn[e]=t},exists:function(e){return!!Jn[e]||!1},playerExists:oo,getActivePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){oo(e,t)?uo(e,t,{ts:n}):no(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:co,getPieceCount:ao,getImageUrl:function(e){return Jn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Jn[e].puzzle.info.imageUrl=t},get:function(e){return Jn[e]||null},getAllGames:function(){return Object.values(Jn).sort(((e,t)=>ro(e.id)===ro(t.id)?t.puzzle.data.started-e.puzzle.data.started:ro(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=to(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=to(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=to(e,t);return n?n.name:null},getPlayerIndexById:eo,getPlayerIdByIndex:function(e,t){return Jn[e].players.length>t?se.decodePlayer(Jn[e].players[t]).id:null},changePlayer:uo,setPlayer:no,setPiece:function(e,t,n){Jn[e].puzzle.tiles[t]=se.encodePiece(n)},setPuzzleData:function(e,t){Jn[e].puzzle.data=t},getTableWidth:Oo,getTableHeight:Bo,getPuzzle:e=>Jn[e].puzzle,getRng:e=>Jn[e].rng.obj,getPuzzleWidth:e=>Jn[e].puzzle.info.width,getPuzzleHeight:e=>Jn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Jn[e].puzzle.tiles.map(se.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Co(e,t);return n<0?null:Jn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Jn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:ko,getFinalPiecePos:fo,getStartTs:e=>Jn[e].puzzle.data.started,getFinishTs:e=>Jn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Jn[e].puzzle,s=function(e,t){return t in Jn[e].evtInfos?Jn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([Zt,a.data])},d=t=>{i.push([Xt,se.encodePiece(ho(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=to(e,t);n&&i.push([Jt,se.encodePlayer(n)])},p=n[0];if(p===Lt){const l=n[1];uo(e,t,{bgcolor:l,ts:o}),u()}else if(p===jt){const l=n[1];uo(e,t,{color:l,ts:o}),u()}else if(p===Wt){const l=`${n[1]}`.substr(0,16);uo(e,t,{name:l,ts:o}),u()}else if(p===Bt){const l=n[1],a=n[2],s=to(e,t);if(s){const n=s.x-l,i=s.y-a;uo(e,t,{ts:o,x:n,y:i}),u()}}else if(p===Ut){const l={x:n[1],y:n[2]};uo(e,t,{d:1,ts:o}),u(),s._last_mouse_down=l;const a=((e,t)=>{const n=Jn[e].puzzle.info,o=Jn[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=To(e)+1;po(e,{maxZ:n}),r();const o=Vo(e,a);Po(e,o,To(e)),Mo(e,o,t),c(o)}s._last_mouse=l}else if(p===$t){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)uo(e,t,{x:l,y:a,ts:o}),u();else{const n=Co(e,t);if(n>=0){uo(e,t,{x:l,y:a,ts:o}),u();const r=Vo(e,n);let d=Qn.pointInBounds(i,wo(e))&&Qn.pointInBounds(s._last_mouse_down,wo(e));for(const t of r){const n=bo(e,t);if(Qn.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Do(e,r,{x:t,y:n}),c(r)}}else uo(e,t,{ts:o}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===Rt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Co(e,t);if(g>=0){const n=Vo(e,g);Mo(e,n,0),c(n);const s=vo(e,g),i=fo(e,g);let h=!1;if(io(e)===ee.REAL){for(const t of n)if(yo(e,t)){h=!0;break}}else h=!0;if(h&&Qn.pointDistance(i,s){const l=Jn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=mo(e,t),l=mo(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=vo(e,t),s=Qn.pointAdd(vo(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(Qn.pointDistance(a,s){const o=Jn[e].puzzle.tiles,l=mo(e,t),a=mo(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(po(e,{maxGroup:So(e)+1}),r(),s=So(e));if(go(e,t,{group:s}),d(t),go(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=se.decodePiece(r);i.includes(t.group)&&(go(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=Vo(e,t),((e,t)=>-1===_o(e,t))(e,n))Eo(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=xo(e,o);t>n&&(n=t)}return n})(e,l);Po(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Vo(e,g)){const o=Io(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])){a=!0;break}}if(a&&so(e)===q.ANY){const n=No(e,t)+1;uo(e,t,{d:p,ts:o,points:n}),u()}else uo(e,t,{d:p,ts:o}),u();a&&io(e)===ee.REAL&&co(e)===ao(e)&&(po(e,{finished:o}),r()),a&&l&&l(t)}}else uo(e,t,{d:p,ts:o}),u();s._last_mouse=i}else if(p===Gt){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else if(p===Ft){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else uo(e,t,{ts:o}),u();return function(e,t,n){Jn[e].evtInfos[t]=n}(e,t,s),i}};let Ro=-10,$o=20,Go=2,Fo=15;class Lo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Ro+Math.random()*$o,this.vy=-1*(Go+Math.random()*Fo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Go=t/2,Fo=t-Go;const n=1/4*this.canvas.width/(t/2);Ro=-n,$o=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Lo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Lo(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(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Gn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Zo=!0})),t}(l,Gn.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};pn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await pn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let x=()=>0;const C=async()=>{if("play"===o){const o=await pn.connect(n,e,t),l=se.decodeGame(o);Uo.setGame(l.id,l),x=()=>N()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=se.decodeGame(t.game);Uo.setGame(n.id,n),w.lastRealTs=N(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,x=()=>w.lastGameTs}}Zo=!0};await C();const k=Uo.getPieceDrawOffset(e),A=Uo.getPieceDrawSize(e),S=Uo.getPuzzleWidth(e),T=Uo.getPuzzleHeight(e),I=Uo.getTableWidth(e),P=Uo.getTableHeight(e),z={x:(I-S)/2,y:(P-T)/2},D={w:S,h:T},_={w:A,h:A},E=await Xn.loadPuzzleBitmaps(Uo.getPuzzle(e)),V=new Wo(v,Uo.getRng(e));V.init();const O=v.getContext("2d");v.classList.add("loaded");const B=Rn();B.move(-(I-v.width)/2,-(P-v.height)/2);const U=function(e,t,n,o){let l=[],a=!0,s=!1,i=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Ut,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Rt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([$t,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Gt:Ft;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([Kt]),"replay"===o&&("KeyI"===e.code&&v([Qt]),"KeyO"===e.code&&v([qt]),"KeyP"===e.code&&v([Yt])),"KeyF"===e.code&&(Qo=!Qo,Zo=!0),"KeyG"===e.code&&(qo=!qo,Zo=!0),"KeyM"===e.code&&v([Ht]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});v([Bt,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Gt,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([Ft,...e])}},setHotkeys:e=>{a=e}}}(v,window,B,o),R=Uo.getImageUrl(e),$=()=>{const t=Uo.getStartTs(e),n=Uo.getFinishTs(e),o=x();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),a.setPiecesTotal(Uo.getPieceCount(e));const G=x();a.setActivePlayers(Uo.getActivePlayers(e,G)),a.setIdlePlayers(Uo.getIdlePlayers(e,G));const F=!!Uo.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_volume");if(null===e)return 100;const t=parseInt(e,10);return isNaN(t)?100:t},K=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},H=()=>{const e=W();i.volume=e/100,i.play()},Y=()=>"replay"===o?localStorage.getItem("bg_color")||"#222222":Uo.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Q=()=>"replay"===o?localStorage.getItem("player_color")||"#ffffff":Uo.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let q="",Z="",X=!1;const J=e=>{X=e;const[t,n]=e?[q,"grab"]:[Z,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},ee=e=>{q=Gn.colorizedCanvas(r,c,e).toDataURL(),Z=Gn.colorizedCanvas(d,u,e).toDataURL(),J(X)};ee(Q());const te=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ne=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,te())},le=()=>{w.paused=!w.paused,te()},ae=[];let ie;let re;if("play"===o?ae.push(setInterval((()=>{$()}),1e3)):"replay"===o&&te(),"play"===o)pn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Jt:{const n=se.decodePlayer(a);n.id!==t&&(Uo.setPlayer(e,n.id,n),Zo=!0)}break;case Xt:{const t=se.decodePiece(a);Uo.setPiece(e,t.idx,t),Zo=!0}break;case Zt:Uo.setPuzzleData(e,a),Zo=!0}L=!!Uo.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===Vt){const t=o[1];return Uo.addPlayer(e,t,n),!0}if(o[0]===Nt){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Uo.addPlayer(e,t,n),!0}if(o[0]===Ot){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Uo.handleInput(e,t,l,n),!0}return!1};let n=w.lastGameTs;const o=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=N();if(w.paused)return w.lastRealTs=l,void(ie=setTimeout(o,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const o=w.log[w.logPointer],l=n+o[o.length-1],a=w.log[e],i=a[a.length-1],r=l+i;if(r>s){w.skipNonActionPhases&&s+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/o),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{U.createKeyEvents();for(const n of U.consumeAll())if("play"===o){const o=n[0];if(o===Bt){const e=n[1],t=n[2],o=B.worldDimToViewport({w:e,h:t});Zo=!0,B.move(o.w,o.h)}else if(o===$t){if(de&&!Uo.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Zo=!0,B.move(o,l),de=t}}else if(o===jt)ee(n[1]);else if(o===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e),J(!0)}else if(o===Rt)de=null,J(!1);else if(o===Gt){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("in",B.worldToViewport(e))}else if(o===Ft){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("out",B.worldToViewport(e))}else o===Kt?a.togglePreview():o===Ht&&a.toggleSoundsEnabled();const l=x();Uo.handleInput(e,t,n,l,(e=>{K()&&H()})).length>0&&(Zo=!0),pn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===Yt)le();else if(e===qt)oe();else if(e===Qt)ne();else if(e===Bt){const e=n[1],t=n[2];Zo=!0,B.move(e,t)}else if(e===$t){if(de){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Zo=!0,B.move(o,l),de=t}}else if(e===jt)ee(n[1]);else if(e===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e),J(!0)}else if(e===Rt)de=null,J(!1);else if(e===Gt){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("in",B.worldToViewport(e))}else if(e===Ft){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("out",B.worldToViewport(e))}else e===Kt&&a.togglePreview()}L=!!Uo.getFinishTs(e),j()&&(V.update(),Zo=!0)},render:async()=>{if(!Zo)return;const n=x();let l,s,i;window.DEBUG&&Wn(0),O.fillStyle=Y(),O.fillRect(0,0,v.width,v.height),window.DEBUG&&Kn("clear done"),l=B.worldToViewportRaw(z),s=B.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&Kn("board done");const r=Uo.getPiecesSortedByZIndex(e);window.DEBUG&&Kn("get tiles done"),s=B.worldDimToViewportRaw(_);for(const e of r)(-1===e.owner?Qo:qo)&&(i=E[e.idx],l=B.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&Kn("tiles done");const d=[];for(const a of Uo.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(i=await f(a),l=B.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,o]of d)O.fillText(e,t,o);window.DEBUG&&Kn("players done"),a.setActivePlayers(Uo.getActivePlayers(e,n)),a.setIdlePlayers(Uo.getIdlePlayers(e,n)),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),window.DEBUG&&Kn("HUD done"),j()&&V.render(),Zo=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([Lt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([jt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Wt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},onSoundsVolumeChange:e=>{Ko.info("vol changed",e),localStorage.setItem("sound_volume",`${e}`),H()},replayOnSpeedUp:ne,replayOnSpeedDown:oe,replayOnPauseToggle:le,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:R,player:{background:Y(),color:Q(),name:"replay"===o?localStorage.getItem("player_name")||"#ffffff":Uo.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:K(),soundsVolume:W()},disconnect:pn.disconnect,connect:C,unload:()=>{ae.forEach((e=>{clearInterval(e)})),ie&&clearTimeout(ie),re&&re.stop()}}}var Jo=e({name:"game",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:Pt,ConnectionOverlay:gn,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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)},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 el={id:"game"},tl={class:"menu"},nl={class:"tabs"},ol=i("🧩 Puzzles");Jo.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",el,[p(n(u,{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"]),[[C,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",tl,[n("div",nl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[ol])),_: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 ll=e({name:"replay",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:Pt,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 al={id:"replay"},sl=i("Skip no action phases: "),il={class:"menu"},rl={class:"tabs"},dl=i("🧩 Puzzles");ll.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",al,[p(n(g,{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"]),[[C,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[sl,p(n("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[x,e.skipNoAction]])])]),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",il,[n("div",rl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[dl])),_:1}),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:l[12]||(l[12]=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=k({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:nt},{name:"game",path:"/g/:id",component:Jo},{name:"replay",path:"/replay/:id",component:ll}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=S(T);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=se.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); +import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as x,s as C,u as k,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const I={id:"app"},P={key:0,class:"nav"},z=i("Index"),D=i("New game");T.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",I,[e.showNav?(s(),t("ul",P,[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((()=>[D])),_:1})])])):l("",!0),n(g)])};const _=864e5,E=e=>{const t=Math.floor(e/_);e%=_;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 M=1,V=1e3,N=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>E(t-e),B=E,U=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||N();return`${n} ${O(o,l)}`}}});const R={class:"game-info-text"},$=n("br",null,null,-1),G=n("br",null,null,-1),F=n("br",null,null,-1),L=i(" ↪️ Watch replay ");U.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),$,i(" 👥 "+r(e.game.players),1),G,i(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[L])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:U},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 W=n("h1",null,"Running games",-1),K=n("h1",null,"Finished games",-1);j.render=function(e,o,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(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)),K,(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 H=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});H.render=function(e,o,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y,Q,q,Z,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:H},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(Q=Y||(Y={}))[Q.Flat=0]="Flat",Q[Q.Out=1]="Out",Q[Q.In=-1]="In",(Z=q||(q={}))[Z.FINAL=0]="FINAL",Z[Z.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),s=le(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var se={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||q.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const ie={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};ie.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-a4fa5e7e");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ce,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-a4fa5e7e";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=n("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},ve=n("span",{class:"btn"},"Upload File",-1),we={class:"area-settings"},be=n("td",null,[n("label",null,"Title")],-1),xe=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ce=n("td",null,[n("label",null,"Tags")],-1),ke={class:"area-buttons"},Ae=i("🧩 Post to gallery "),Se=n("br",null,null,-1),Te=i(" + set up game");ge.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(s(),t("div",me,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",ye,[n("label",fe,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ve])]))],34),n("div",we,[n("table",null,[n("tr",null,[be,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),xe,n("tr",null,[Ce,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",ke,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ae,Se,Te],8,["disabled"])])])])};var Ie=e({name:"edit-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Pe={class:"area-image"},ze={class:"has-image"},De={class:"area-settings"},_e=n("td",null,[n("label",null,"Title")],-1),Ee=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Me=n("td",null,[n("label",null,"Tags")],-1),Ve={class:"area-buttons"};Ie.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Pe,[n("div",ze,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",De,[n("table",null,[n("tr",null,[_e,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ee,n("tr",null,[Me,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Ve,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Ne=e({name:"new-game-dialog",components:{ResponsiveImage:ie},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:q.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Oe={class:"area-image"},Be={class:"has-image"},Ue={key:0,class:"image-title"},Re={class:"area-settings"},$e=n("td",null,[n("label",null,"Pieces")],-1),Ge=n("td",null,[n("label",null,"Scoring: ")],-1),Fe=i(" Any (Score when pieces are connected to each other or on final location)"),Le=n("br",null,null,-1),je=i(" Final (Score when pieces are put to their final location)"),We=n("td",null,[n("label",null,"Shapes: ")],-1),Ke=i(" Normal"),He=n("br",null,null,-1),Ye=i(" Any (flat pieces can occur anywhere)"),Qe=n("br",null,null,-1),qe=i(" Flat (all pieces flat on all sides)"),Ze=n("td",null,[n("label",null,"Snapping: ")],-1),Xe=i(" Normal (pieces snap to final destination and to each other)"),Je=n("br",null,null,-1),et=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),tt={class:"area-buttons"};Ne.render=function(e,o,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Oe,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(s(),t("div",Ue,'"'+r(e.image.title)+'"',1)):l("",!0)]),n("div",Re,[n("table",null,[n("tr",null,[$e,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[Ge,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Fe]),Le,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),je])])]),n("tr",null,[We,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),Ke]),He,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ye]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),qe])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Xe]),Je,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),et])])])])]),n("div",tt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var nt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Ie,NewGameDialog:Ne},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${se.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const ot={class:"upload-image-teaser"},lt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),at={key:0},st=i(" Tags: "),it=i(" Sort by: "),rt=n("option",{value:"date_desc"},"Newest first",-1),dt=n("option",{value:"date_asc"},"Oldest first",-1),ct=n("option",{value:"alpha_asc"},"A-Z",-1),ut=n("option",{value:"alpha_desc"},"Z-A",-1);nt.render=function(e,o,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[n("div",ot,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),lt]),n("div",null,[e.tags.length>0?(s(),t("label",at,[st,(s(!0),t(d,null,c(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[it,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[rt,dt,ct,ut],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var pt=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 gt={class:"scores"},ht=n("div",null,"Scores",-1),mt=n("td",null,"⚡",-1),yt=n("td",null,"💤",-1);pt.render=function(e,o,l,a,i,u){return s(),t("div",gt,[ht,n("table",null,[(s(!0),t(d,null,c(e.actives,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[mt,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}},[yt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var ft=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 B(this.duration)}}});const vt={class:"timer"};ft.render=function(e,o,l,a,i,d){return s(),t("div",vt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var wt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const bt=m();y("data-v-a1d1c822");const xt=n("td",null,[n("label",null,"Background: ")],-1),Ct=n("td",null,[n("label",null,"Color: ")],-1),kt=n("td",null,[n("label",null,"Name: ")],-1),At=n("td",null,[n("label",null,"Sounds: ")],-1),St=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Tt={class:"sound-volume"};f();const It=bt(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[9]||(o[9]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[8]||(o[8]=u((()=>{}),["stop"]))},[n("tr",null,[xt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[Ct,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[kt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[At,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[x,e.modelValue.soundsEnabled]])])]),n("tr",null,[St,n("td",Tt,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])])])]))));wt.render=It,wt.__scopeId="data-v-a1d1c822";var Pt=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const zt={class:"preview"};Pt.render=function(e,o,l,a,i,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",zt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Dt=1,_t=4,Et=2,Mt=3,Vt=2,Nt=4,Ot=3,Bt=9,Ut=1,Rt=2,$t=3,Gt=4,Ft=5,Lt=6,jt=7,Wt=8,Kt=10,Ht=11,Yt=12,Qt=13,qt=14,Zt=1,Xt=2,Jt=3;const en=ae("Communication.js");let tn,nn=[],on=e=>{nn.push(e)},ln=[],an=e=>{ln.push(e)};let sn=0;const rn=e=>{sn!==e&&(sn=e,an(e))};function dn(e){if(2===sn)try{tn.send(JSON.stringify(e))}catch(t){en.info("unable to send message.. maybe because ws is invalid?")}}let cn,un;var pn={connect:function(e,t,n){return cn=0,un={},rn(3),new Promise((o=>{tn=new WebSocket(e,n+"|"+t),tn.onopen=()=>{rn(2),dn([Mt])},tn.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===_t){const e=t[1];o(e)}else{if(l!==Dt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&un[o])return void delete un[o];on(t)}}},tn.onerror=()=>{throw rn(1),"[ 2021-05-15 onerror ]"},tn.onclose=e=>{4e3===e.code||1001===e.code?rn(4):rn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${se.asQueryArgs(n)}`);return await o.json()},disconnect:function(){tn&&tn.close(4e3),cn=0,un={}},sendClientEvent:function(e){cn++,un[cn]=e,dn([Et,cn,un[cn]])},onServerChange:function(e){on=e;for(const t of nn)on(t);nn=[]},onConnectionStateChange:function(e){an=e;for(const t of ln)an(t);ln=[]},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},gn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===pn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===pn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const hn={key:0,class:"overlay connection-lost"},mn={key:0,class:"overlay-content"},yn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),fn={key:1,class:"overlay-content"},vn=n("div",null,"Connecting...",-1);gn.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",hn,[e.lostConnection?(s(),t("div",mn,[yn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",fn,[vn])):l("",!0)])):l("",!0)};var wn=e({name:"help-overlay",emits:{bgclick:null}});const bn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),xn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),Cn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),kn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),An=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Sn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Tn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),In=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Pn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),zn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Dn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),_n=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),En=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),Mn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);wn.render=function(e,o,l,a,i,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]=u((()=>{}),["stop"]))},[bn,xn,Cn,kn,An,Sn,Tn,In,Pn,zn,Dn,_n,En,Mn])])};var Vn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),On=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Bn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Un=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Rn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),s=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),i=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:i,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function $n(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Gn={createCanvas:$n,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const o=$n(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=$n(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(),o}};const Fn=ae("Debug.js");let Ln=0,jn=0;var Wn=e=>{Ln=performance.now(),jn=e},Kn=e=>{const t=performance.now(),n=t-Ln;n>jn&&Fn.log(e+": "+n),Ln=t};function Hn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function Yn(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var Qn={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:Hn,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:Yn,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 Hn(Yn(e),Yn(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const qn=ae("PuzzleGraphics.js");function Zn(e,t){const n=se.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Xn={loadPuzzleBitmaps:async function(e){const t=await Gn.loadImageToBitmap(e.info.imageUrl),n=await Gn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){qn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=Qn.pointAdd(a,{x:o,y:0}),c=Qn.pointAdd(r,{x:0,y:o}),u=Qn.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;ose.decodePiece(Jn[e].puzzle.tiles[t]),mo=(e,t)=>ho(e,t).group,yo=(e,t)=>{const n=Jn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},fo=(e,t)=>{const n=Jn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return Qn.pointAdd(o,l)},vo=(e,t)=>ho(e,t).pos,wo=e=>{const t=Oo(e),n=Bo(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}},bo=(e,t)=>{const n=Ao(e),o=ho(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},xo=(e,t)=>ho(e,t).z,Co=(e,t)=>{for(const n of Jn[e].puzzle.tiles){const e=se.decodePiece(n);if(e.owner===t)return e.idx}return-1},ko=e=>Jn[e].puzzle.info.tileDrawSize,Ao=e=>Jn[e].puzzle.info.tileSize,So=e=>Jn[e].puzzle.data.maxGroup,To=e=>Jn[e].puzzle.data.maxZ;function Io(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Po=(e,t,n)=>{for(const o of t)go(e,o,{z:n})},zo=(e,t,n)=>{const o=vo(e,t);go(e,t,{pos:Qn.pointAdd(o,n)})},Do=(e,t,n)=>{const o=ko(e),l=wo(e),a=n;for(const s of t){const t=ho(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)zo(e,s,a)},_o=(e,t)=>ho(e,t).owner,Eo=(e,t)=>{for(const n of t)go(e,n,{owner:-1,z:1})},Mo=(e,t,n)=>{for(const o of t)go(e,o,{owner:n})};function Vo(e,t){const n=Jn[e].puzzle.tiles,o=se.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=se.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const No=(e,t)=>{const n=to(e,t);return n?n.points:0},Oo=e=>Jn[e].puzzle.info.table.width,Bo=e=>Jn[e].puzzle.info.table.height;var Uo={setGame:function(e,t){Jn[e]=t},exists:function(e){return!!Jn[e]||!1},playerExists:oo,getActivePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){oo(e,t)?uo(e,t,{ts:n}):no(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:co,getPieceCount:ao,getImageUrl:function(e){return Jn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Jn[e].puzzle.info.imageUrl=t},get:function(e){return Jn[e]||null},getAllGames:function(){return Object.values(Jn).sort(((e,t)=>ro(e.id)===ro(t.id)?t.puzzle.data.started-e.puzzle.data.started:ro(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=to(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=to(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=to(e,t);return n?n.name:null},getPlayerIndexById:eo,getPlayerIdByIndex:function(e,t){return Jn[e].players.length>t?se.decodePlayer(Jn[e].players[t]).id:null},changePlayer:uo,setPlayer:no,setPiece:function(e,t,n){Jn[e].puzzle.tiles[t]=se.encodePiece(n)},setPuzzleData:function(e,t){Jn[e].puzzle.data=t},getTableWidth:Oo,getTableHeight:Bo,getPuzzle:e=>Jn[e].puzzle,getRng:e=>Jn[e].rng.obj,getPuzzleWidth:e=>Jn[e].puzzle.info.width,getPuzzleHeight:e=>Jn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Jn[e].puzzle.tiles.map(se.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Co(e,t);return n<0?null:Jn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Jn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:ko,getFinalPiecePos:fo,getStartTs:e=>Jn[e].puzzle.data.started,getFinishTs:e=>Jn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Jn[e].puzzle,s=function(e,t){return t in Jn[e].evtInfos?Jn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([Zt,a.data])},d=t=>{i.push([Xt,se.encodePiece(ho(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=to(e,t);n&&i.push([Jt,se.encodePlayer(n)])},p=n[0];if(p===Lt){const l=n[1];uo(e,t,{bgcolor:l,ts:o}),u()}else if(p===jt){const l=n[1];uo(e,t,{color:l,ts:o}),u()}else if(p===Wt){const l=`${n[1]}`.substr(0,16);uo(e,t,{name:l,ts:o}),u()}else if(p===Bt){const l=n[1],a=n[2],s=to(e,t);if(s){const n=s.x-l,i=s.y-a;uo(e,t,{ts:o,x:n,y:i}),u()}}else if(p===Ut){const l={x:n[1],y:n[2]};uo(e,t,{d:1,ts:o}),u(),s._last_mouse_down=l;const a=((e,t)=>{const n=Jn[e].puzzle.info,o=Jn[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=To(e)+1;po(e,{maxZ:n}),r();const o=Vo(e,a);Po(e,o,To(e)),Mo(e,o,t),c(o)}s._last_mouse=l}else if(p===$t){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)uo(e,t,{x:l,y:a,ts:o}),u();else{const n=Co(e,t);if(n>=0){uo(e,t,{x:l,y:a,ts:o}),u();const r=Vo(e,n);let d=Qn.pointInBounds(i,wo(e))&&Qn.pointInBounds(s._last_mouse_down,wo(e));for(const t of r){const n=bo(e,t);if(Qn.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Do(e,r,{x:t,y:n}),c(r)}}else uo(e,t,{ts:o}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===Rt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Co(e,t);if(g>=0){const n=Vo(e,g);Mo(e,n,0),c(n);const s=vo(e,g),i=fo(e,g);let h=!1;if(io(e)===ee.REAL){for(const t of n)if(yo(e,t)){h=!0;break}}else h=!0;if(h&&Qn.pointDistance(i,s){const l=Jn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=mo(e,t),l=mo(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=vo(e,t),s=Qn.pointAdd(vo(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(Qn.pointDistance(a,s){const o=Jn[e].puzzle.tiles,l=mo(e,t),a=mo(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(po(e,{maxGroup:So(e)+1}),r(),s=So(e));if(go(e,t,{group:s}),d(t),go(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=se.decodePiece(r);i.includes(t.group)&&(go(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=Vo(e,t),((e,t)=>-1===_o(e,t))(e,n))Eo(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=xo(e,o);t>n&&(n=t)}return n})(e,l);Po(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Vo(e,g)){const o=Io(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])){a=!0;break}}if(a&&so(e)===q.ANY){const n=No(e,t)+1;uo(e,t,{d:p,ts:o,points:n}),u()}else uo(e,t,{d:p,ts:o}),u();a&&io(e)===ee.REAL&&co(e)===ao(e)&&(po(e,{finished:o}),r()),a&&l&&l(t)}}else uo(e,t,{d:p,ts:o}),u();s._last_mouse=i}else if(p===Gt){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else if(p===Ft){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else uo(e,t,{ts:o}),u();return function(e,t,n){Jn[e].evtInfos[t]=n}(e,t,s),i}};let Ro=-10,$o=20,Go=2,Fo=15;class Lo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Ro+Math.random()*$o,this.vy=-1*(Go+Math.random()*Fo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Go=t/2,Fo=t-Go;const n=1/4*this.canvas.width/(t/2);Ro=-n,$o=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Lo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Lo(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(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Gn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Zo=!0})),t}(l,Gn.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};pn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await pn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let x=()=>0;const C=async()=>{if("play"===o){const o=await pn.connect(n,e,t),l=se.decodeGame(o);Uo.setGame(l.id,l),x=()=>N()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=se.decodeGame(t.game);Uo.setGame(n.id,n),w.lastRealTs=N(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,x=()=>w.lastGameTs}}Zo=!0};await C();const k=Uo.getPieceDrawOffset(e),A=Uo.getPieceDrawSize(e),S=Uo.getPuzzleWidth(e),T=Uo.getPuzzleHeight(e),I=Uo.getTableWidth(e),P=Uo.getTableHeight(e),z={x:(I-S)/2,y:(P-T)/2},D={w:S,h:T},_={w:A,h:A},E=await Xn.loadPuzzleBitmaps(Uo.getPuzzle(e)),V=new Wo(v,Uo.getRng(e));V.init();const O=v.getContext("2d");v.classList.add("loaded");const B=Rn();B.move(-(I-v.width)/2,-(P-v.height)/2);const U=function(e,t,n,o){let l=[],a=!0,s=!1,i=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Ut,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Rt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([$t,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Gt:Ft;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([Kt]),"replay"===o&&("KeyI"===e.code&&v([Qt]),"KeyO"===e.code&&v([qt]),"KeyP"===e.code&&v([Yt])),"KeyF"===e.code&&(Qo=!Qo,Zo=!0),"KeyG"===e.code&&(qo=!qo,Zo=!0),"KeyM"===e.code&&v([Ht]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});v([Bt,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Gt,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([Ft,...e])}},setHotkeys:e=>{a=e}}}(v,window,B,o),R=Uo.getImageUrl(e),$=()=>{const t=Uo.getStartTs(e),n=Uo.getFinishTs(e),o=x();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),a.setPiecesTotal(Uo.getPieceCount(e));const G=x();a.setActivePlayers(Uo.getActivePlayers(e,G)),a.setIdlePlayers(Uo.getIdlePlayers(e,G));const F=!!Uo.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_volume");if(null===e)return 100;const t=parseInt(e,10);return isNaN(t)?100:t},K=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},H=()=>{const e=W();i.volume=e/100,i.play()},Y=()=>"replay"===o?localStorage.getItem("bg_color")||"#222222":Uo.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Q=()=>"replay"===o?localStorage.getItem("player_color")||"#ffffff":Uo.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let q="",Z="",X=!1;const J=e=>{X=e;const[t,n]=e?[q,"grab"]:[Z,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},ee=e=>{q=Gn.colorizedCanvas(r,c,e).toDataURL(),Z=Gn.colorizedCanvas(d,u,e).toDataURL(),J(X)};ee(Q());const te=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ne=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,te())},le=()=>{w.paused=!w.paused,te()},ae=[];let ie;let re;if("play"===o?ae.push(setInterval((()=>{$()}),1e3)):"replay"===o&&te(),"play"===o)pn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Jt:{const n=se.decodePlayer(a);n.id!==t&&(Uo.setPlayer(e,n.id,n),Zo=!0)}break;case Xt:{const t=se.decodePiece(a);Uo.setPiece(e,t.idx,t),Zo=!0}break;case Zt:Uo.setPuzzleData(e,a),Zo=!0}L=!!Uo.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===Vt){const t=o[1];return Uo.addPlayer(e,t,n),!0}if(o[0]===Nt){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Uo.addPlayer(e,t,n),!0}if(o[0]===Ot){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Uo.handleInput(e,t,l,n),!0}return!1};let n=w.lastGameTs;const o=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=N();if(w.paused)return w.lastRealTs=l,void(ie=setTimeout(o,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const o=w.log[w.logPointer],l=n+o[o.length-1],a=w.log[e],i=a[a.length-1],r=l+i;if(r>s){w.skipNonActionPhases&&s+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/o),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{U.createKeyEvents();for(const n of U.consumeAll())if("play"===o){const o=n[0];if(o===Bt){const e=n[1],t=n[2],o=B.worldDimToViewport({w:e,h:t});Zo=!0,B.move(o.w,o.h)}else if(o===$t){if(de&&!Uo.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Zo=!0,B.move(o,l),de=t}}else if(o===jt)ee(n[1]);else if(o===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e),J(!0)}else if(o===Rt)de=null,J(!1);else if(o===Gt){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("in",B.worldToViewport(e))}else if(o===Ft){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("out",B.worldToViewport(e))}else o===Kt?a.togglePreview():o===Ht&&a.toggleSoundsEnabled();const l=x();Uo.handleInput(e,t,n,l,(e=>{K()&&H()})).length>0&&(Zo=!0),pn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===Yt)le();else if(e===qt)oe();else if(e===Qt)ne();else if(e===Bt){const e=n[1],t=n[2];Zo=!0,B.move(e,t)}else if(e===$t){if(de){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Zo=!0,B.move(o,l),de=t}}else if(e===jt)ee(n[1]);else if(e===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e),J(!0)}else if(e===Rt)de=null,J(!1);else if(e===Gt){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("in",B.worldToViewport(e))}else if(e===Ft){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("out",B.worldToViewport(e))}else e===Kt&&a.togglePreview()}L=!!Uo.getFinishTs(e),j()&&(V.update(),Zo=!0)},render:async()=>{if(!Zo)return;const n=x();let l,s,i;window.DEBUG&&Wn(0),O.fillStyle=Y(),O.fillRect(0,0,v.width,v.height),window.DEBUG&&Kn("clear done"),l=B.worldToViewportRaw(z),s=B.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&Kn("board done");const r=Uo.getPiecesSortedByZIndex(e);window.DEBUG&&Kn("get tiles done"),s=B.worldDimToViewportRaw(_);for(const e of r)(-1===e.owner?Qo:qo)&&(i=E[e.idx],l=B.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&Kn("tiles done");const d=[];for(const a of Uo.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(i=await f(a),l=B.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,o]of d)O.fillText(e,t,o);window.DEBUG&&Kn("players done"),a.setActivePlayers(Uo.getActivePlayers(e,n)),a.setIdlePlayers(Uo.getIdlePlayers(e,n)),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),window.DEBUG&&Kn("HUD done"),j()&&V.render(),Zo=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([Lt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([jt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Wt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},onSoundsVolumeChange:e=>{Ko.info("vol changed",e),localStorage.setItem("sound_volume",`${e}`),H()},replayOnSpeedUp:ne,replayOnSpeedDown:oe,replayOnPauseToggle:le,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:R,player:{background:Y(),color:Q(),name:"replay"===o?localStorage.getItem("player_name")||"#ffffff":Uo.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:K(),soundsVolume:W()},disconnect:pn.disconnect,connect:C,unload:()=>{ae.forEach((e=>{clearInterval(e)})),ie&&clearTimeout(ie),re&&re.stop()}}}var Jo=e({name:"game",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:Pt,ConnectionOverlay:gn,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 el={id:"game"},tl={class:"menu"},nl={class:"tabs"},ol=i("🧩 Puzzles");Jo.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",el,[p(n(u,{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"]),[[C,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",tl,[n("div",nl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[ol])),_: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 ll=e({name:"replay",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:Pt,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 al={id:"replay"},sl=i("Skip no action phases: "),il={class:"menu"},rl={class:"tabs"},dl=i("🧩 Puzzles");ll.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",al,[p(n(g,{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"]),[[C,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[sl,p(n("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[x,e.skipNoAction]])])]),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",il,[n("div",rl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[dl])),_:1}),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:l[12]||(l[12]=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=k({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:nt},{name:"game",path:"/g/:id",component:Jo},{name:"replay",path:"/replay/:id",component:ll}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=S(T);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=se.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 ff7cb1b..39701ad 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,7 +4,7 @@ 🧩 jigsaw.hyottoko.club - + diff --git a/src/frontend/views/Game.vue b/src/frontend/views/Game.vue index 14594c2..a6e2c0d 100644 --- a/src/frontend/views/Game.vue +++ b/src/frontend/views/Game.vue @@ -120,8 +120,8 @@ export default defineComponent({ setDuration: (v: number) => { this.duration = v }, setPiecesDone: (v: number) => { this.piecesDone = v }, setPiecesTotal: (v: number) => { this.piecesTotal = v }, - setConnectionState: (v: number) => { this.connectionState = v }, togglePreview: () => { this.toggle('preview', false) }, + setConnectionState: (v: number) => { this.connectionState = v }, toggleSoundsEnabled: () => { this.g.player.soundsEnabled = !this.g.player.soundsEnabled }, } ) diff --git a/src/frontend/views/Replay.vue b/src/frontend/views/Replay.vue index 64f1dfb..4320603 100644 --- a/src/frontend/views/Replay.vue +++ b/src/frontend/views/Replay.vue @@ -46,6 +46,7 @@ import PreviewOverlay from './../components/PreviewOverlay.vue' import HelpOverlay from './../components/HelpOverlay.vue' import { main, MODE_REPLAY } from './../game' +import { Player } from '../../common/Types' export default defineComponent({ name: 'replay', @@ -58,8 +59,8 @@ export default defineComponent({ }, data() { return { - activePlayers: [] as Array, - idlePlayers: [] as Array, + activePlayers: [] as Array, + idlePlayers: [] as Array, finished: false, duration: 0, @@ -129,8 +130,8 @@ export default defineComponent({ MODE_REPLAY, this.$el, { - setActivePlayers: (v: Array) => { this.activePlayers = v }, - setIdlePlayers: (v: Array) => { this.idlePlayers = v }, + setActivePlayers: (v: Array) => { this.activePlayers = v }, + setIdlePlayers: (v: Array) => { this.idlePlayers = v }, setFinished: (v: boolean) => { this.finished = v }, setDuration: (v: number) => { this.duration = v }, setPiecesDone: (v: number) => { this.piecesDone = v }, From 4b10fbc01bb3d33bd0de0e7a8ee24c43ae412e51 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Wed, 9 Jun 2021 09:52:58 +0200 Subject: [PATCH 15/45] log fix --- build/server/main.js | 32 +++++++++++++++----------------- src/server/GameLog.ts | 37 ++++++++++++++++++------------------- 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/build/server/main.js b/build/server/main.js index a2e2880..016694b 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -1276,13 +1276,11 @@ const idxname = (gameId) => `${DATA_DIR}/log_${gameId}.idx.log`; const create = (gameId, ts) => { const idxfile = idxname(gameId); if (!fs.existsSync(idxfile)) { - const logfile = filename(gameId, 0); - fs.appendFileSync(logfile, ""); fs.appendFileSync(idxfile, JSON.stringify({ gameId: gameId, total: 0, lastTs: ts, - currentFile: logfile, + currentFile: '', perFile: LINES_PER_LOG_FILE, })); } @@ -1296,21 +1294,21 @@ const _log = (gameId, type, ...args) => { if (!fs.existsSync(idxfile)) { return; } - const ts = args[args.length - 1]; - const otherArgs = args.slice(0, -1); - const idx = JSON.parse(fs.readFileSync(idxfile, 'utf-8')); - idx.total++; - const diff = ts - idx.lastTs; - idx.lastTs = ts; - const line = JSON.stringify([type, ...otherArgs, diff]).slice(1, -1); - fs.appendFileSync(idx.currentFile, line + "\n"); - // prepare next log file - if (idx.total % idx.perFile === 0) { - const logfile = filename(gameId, idx.total); - fs.appendFileSync(logfile, ""); - idx.currentFile = logfile; + const idxObj = JSON.parse(fs.readFileSync(idxfile, 'utf-8')); + if (idxObj.total % idxObj.perFile === 0) { + idxObj.currentFile = filename(gameId, idxObj.total); } - fs.writeFileSync(idxfile, JSON.stringify(idx)); + const tsIdx = type === Protocol.LOG_HEADER ? 3 : (args.length - 1); + const ts = args[tsIdx]; + if (type !== Protocol.LOG_HEADER) { + // for everything but header save the diff to last log entry + args[tsIdx] = ts - idxObj.lastTs; + } + const line = JSON.stringify([type, ...args]).slice(1, -1); + fs.appendFileSync(idxObj.currentFile, line + "\n"); + idxObj.total++; + idxObj.lastTs = ts; + fs.writeFileSync(idxfile, JSON.stringify(idxObj)); }; const get = (gameId, offset = 0) => { const idxfile = idxname(gameId); diff --git a/src/server/GameLog.ts b/src/server/GameLog.ts index b71e3db..859610f 100644 --- a/src/server/GameLog.ts +++ b/src/server/GameLog.ts @@ -1,4 +1,5 @@ import fs from 'fs' +import Protocol from '../common/Protocol' import Time from '../common/Time' import { Timestamp } from '../common/Types' import { logger } from './../common/Util' @@ -27,13 +28,11 @@ export const idxname = (gameId: string) => `${DATA_DIR}/log_${gameId}.idx.log` const create = (gameId: string, ts: Timestamp): void => { const idxfile = idxname(gameId) if (!fs.existsSync(idxfile)) { - const logfile = filename(gameId, 0) - fs.appendFileSync(logfile, "") fs.appendFileSync(idxfile, JSON.stringify({ gameId: gameId, total: 0, lastTs: ts, - currentFile: logfile, + currentFile: '', perFile: LINES_PER_LOG_FILE, })) } @@ -50,23 +49,23 @@ const _log = (gameId: string, type: number, ...args: Array): void => { return } - const ts: Timestamp = args[args.length - 1] - const otherArgs: any[] = args.slice(0, -1) - - const idx = JSON.parse(fs.readFileSync(idxfile, 'utf-8')) - idx.total++ - const diff = ts - idx.lastTs - idx.lastTs = ts - const line = JSON.stringify([type, ...otherArgs, diff]).slice(1, -1) - fs.appendFileSync(idx.currentFile, line + "\n") - - // prepare next log file - if (idx.total % idx.perFile === 0) { - const logfile = filename(gameId, idx.total) - fs.appendFileSync(logfile, "") - idx.currentFile = logfile + const idxObj = JSON.parse(fs.readFileSync(idxfile, 'utf-8')) + if (idxObj.total % idxObj.perFile === 0) { + idxObj.currentFile = filename(gameId, idxObj.total) } - fs.writeFileSync(idxfile, JSON.stringify(idx)) + + const tsIdx = type === Protocol.LOG_HEADER ? 3 : (args.length - 1) + const ts: Timestamp = args[tsIdx] + if (type !== Protocol.LOG_HEADER) { + // for everything but header save the diff to last log entry + args[tsIdx] = ts - idxObj.lastTs + } + const line = JSON.stringify([type, ...args]).slice(1, -1) + fs.appendFileSync(idxObj.currentFile, line + "\n") + + idxObj.total++ + idxObj.lastTs = ts + fs.writeFileSync(idxfile, JSON.stringify(idxObj)) } const get = ( From b410f400faf88afa08d16c8de44ae17aee2b88ee Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sun, 20 Jun 2021 13:40:28 +0200 Subject: [PATCH 16/45] show upload status when uploading images --- build/public/assets/index.1a1747a7.js | 1 - build/public/assets/index.e0726e0c.js | 1 + build/public/index.html | 2 +- src/frontend/components/NewImageDialog.vue | 32 +++++++++++-- src/frontend/views/NewGame.vue | 24 ++++++++-- src/frontend/xhr.ts | 56 ++++++++++++++++++++++ 6 files changed, 106 insertions(+), 10 deletions(-) delete mode 100644 build/public/assets/index.1a1747a7.js create mode 100644 build/public/assets/index.e0726e0c.js create mode 100644 src/frontend/xhr.ts diff --git a/build/public/assets/index.1a1747a7.js b/build/public/assets/index.1a1747a7.js deleted file mode 100644 index e5421b5..0000000 --- a/build/public/assets/index.1a1747a7.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as x,s as C,u as k,x as A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const I={id:"app"},P={key:0,class:"nav"},z=i("Index"),D=i("New game");T.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",I,[e.showNav?(s(),t("ul",P,[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((()=>[D])),_:1})])])):l("",!0),n(g)])};const _=864e5,E=e=>{const t=Math.floor(e/_);e%=_;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 M=1,V=1e3,N=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>E(t-e),B=E,U=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||N();return`${n} ${O(o,l)}`}}});const R={class:"game-info-text"},$=n("br",null,null,-1),G=n("br",null,null,-1),F=n("br",null,null,-1),L=i(" ↪️ Watch replay ");U.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),$,i(" 👥 "+r(e.game.players),1),G,i(" "+r(e.time(e.game.started,e.game.finished)),1),F])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[L])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:U},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 W=n("h1",null,"Running games",-1),K=n("h1",null,"Finished games",-1);j.render=function(e,o,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(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)),K,(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 H=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});H.render=function(e,o,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y,Q,q,Z,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:H},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(Q=Y||(Y={}))[Q.Flat=0]="Flat",Q[Q.Out=1]="Out",Q[Q.In=-1]="In",(Z=q||(q={}))[Z.FINAL=0]="FINAL",Z[Z.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),s=le(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var se={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||q.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const ie={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};ie.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-a4fa5e7e");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ce,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-a4fa5e7e";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=n("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},ve=n("span",{class:"btn"},"Upload File",-1),we={class:"area-settings"},be=n("td",null,[n("label",null,"Title")],-1),xe=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ce=n("td",null,[n("label",null,"Tags")],-1),ke={class:"area-buttons"},Ae=i("🧩 Post to gallery "),Se=n("br",null,null,-1),Te=i(" + set up game");ge.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(s(),t("div",me,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",ye,[n("label",fe,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ve])]))],34),n("div",we,[n("table",null,[n("tr",null,[be,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),xe,n("tr",null,[Ce,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",ke,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[Ae,Se,Te],8,["disabled"])])])])};var Ie=e({name:"edit-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Pe={class:"area-image"},ze={class:"has-image"},De={class:"area-settings"},_e=n("td",null,[n("label",null,"Title")],-1),Ee=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Me=n("td",null,[n("label",null,"Tags")],-1),Ve={class:"area-buttons"};Ie.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Pe,[n("div",ze,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",De,[n("table",null,[n("tr",null,[_e,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ee,n("tr",null,[Me,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Ve,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Ne=e({name:"new-game-dialog",components:{ResponsiveImage:ie},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:q.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Oe={class:"area-image"},Be={class:"has-image"},Ue={key:0,class:"image-title"},Re={class:"area-settings"},$e=n("td",null,[n("label",null,"Pieces")],-1),Ge=n("td",null,[n("label",null,"Scoring: ")],-1),Fe=i(" Any (Score when pieces are connected to each other or on final location)"),Le=n("br",null,null,-1),je=i(" Final (Score when pieces are put to their final location)"),We=n("td",null,[n("label",null,"Shapes: ")],-1),Ke=i(" Normal"),He=n("br",null,null,-1),Ye=i(" Any (flat pieces can occur anywhere)"),Qe=n("br",null,null,-1),qe=i(" Flat (all pieces flat on all sides)"),Ze=n("td",null,[n("label",null,"Snapping: ")],-1),Xe=i(" Normal (pieces snap to final destination and to each other)"),Je=n("br",null,null,-1),et=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),tt={class:"area-buttons"};Ne.render=function(e,o,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Oe,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(s(),t("div",Ue,'"'+r(e.image.title)+'"',1)):l("",!0)]),n("div",Re,[n("table",null,[n("tr",null,[$e,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[Ge,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Fe]),Le,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),je])])]),n("tr",null,[We,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),Ke]),He,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ye]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),qe])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Xe]),Je,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),et])])])])]),n("div",tt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var nt=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:Ie,NewGameDialog:Ne},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${se.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const ot={class:"upload-image-teaser"},lt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),at={key:0},st=i(" Tags: "),it=i(" Sort by: "),rt=n("option",{value:"date_desc"},"Newest first",-1),dt=n("option",{value:"date_asc"},"Oldest first",-1),ct=n("option",{value:"alpha_asc"},"A-Z",-1),ut=n("option",{value:"alpha_desc"},"Z-A",-1);nt.render=function(e,o,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[n("div",ot,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),lt]),n("div",null,[e.tags.length>0?(s(),t("label",at,[st,(s(!0),t(d,null,c(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[it,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[rt,dt,ct,ut],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var pt=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 gt={class:"scores"},ht=n("div",null,"Scores",-1),mt=n("td",null,"⚡",-1),yt=n("td",null,"💤",-1);pt.render=function(e,o,l,a,i,u){return s(),t("div",gt,[ht,n("table",null,[(s(!0),t(d,null,c(e.actives,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[mt,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}},[yt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var ft=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 B(this.duration)}}});const vt={class:"timer"};ft.render=function(e,o,l,a,i,d){return s(),t("div",vt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var wt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const bt=m();y("data-v-a1d1c822");const xt=n("td",null,[n("label",null,"Background: ")],-1),Ct=n("td",null,[n("label",null,"Color: ")],-1),kt=n("td",null,[n("label",null,"Name: ")],-1),At=n("td",null,[n("label",null,"Sounds: ")],-1),St=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Tt={class:"sound-volume"};f();const It=bt(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[9]||(o[9]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[8]||(o[8]=u((()=>{}),["stop"]))},[n("tr",null,[xt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[Ct,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[kt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[At,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[x,e.modelValue.soundsEnabled]])])]),n("tr",null,[St,n("td",Tt,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])])])]))));wt.render=It,wt.__scopeId="data-v-a1d1c822";var Pt=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const zt={class:"preview"};Pt.render=function(e,o,l,a,i,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",zt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Dt=1,_t=4,Et=2,Mt=3,Vt=2,Nt=4,Ot=3,Bt=9,Ut=1,Rt=2,$t=3,Gt=4,Ft=5,Lt=6,jt=7,Wt=8,Kt=10,Ht=11,Yt=12,Qt=13,qt=14,Zt=1,Xt=2,Jt=3;const en=ae("Communication.js");let tn,nn=[],on=e=>{nn.push(e)},ln=[],an=e=>{ln.push(e)};let sn=0;const rn=e=>{sn!==e&&(sn=e,an(e))};function dn(e){if(2===sn)try{tn.send(JSON.stringify(e))}catch(t){en.info("unable to send message.. maybe because ws is invalid?")}}let cn,un;var pn={connect:function(e,t,n){return cn=0,un={},rn(3),new Promise((o=>{tn=new WebSocket(e,n+"|"+t),tn.onopen=()=>{rn(2),dn([Mt])},tn.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===_t){const e=t[1];o(e)}else{if(l!==Dt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&un[o])return void delete un[o];on(t)}}},tn.onerror=()=>{throw rn(1),"[ 2021-05-15 onerror ]"},tn.onclose=e=>{4e3===e.code||1001===e.code?rn(4):rn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${se.asQueryArgs(n)}`);return await o.json()},disconnect:function(){tn&&tn.close(4e3),cn=0,un={}},sendClientEvent:function(e){cn++,un[cn]=e,dn([Et,cn,un[cn]])},onServerChange:function(e){on=e;for(const t of nn)on(t);nn=[]},onConnectionStateChange:function(e){an=e;for(const t of ln)an(t);ln=[]},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},gn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===pn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===pn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const hn={key:0,class:"overlay connection-lost"},mn={key:0,class:"overlay-content"},yn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),fn={key:1,class:"overlay-content"},vn=n("div",null,"Connecting...",-1);gn.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",hn,[e.lostConnection?(s(),t("div",mn,[yn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",fn,[vn])):l("",!0)])):l("",!0)};var wn=e({name:"help-overlay",emits:{bgclick:null}});const bn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),xn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),Cn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),kn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),An=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Sn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Tn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),In=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Pn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),zn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Dn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),_n=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),En=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),Mn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);wn.render=function(e,o,l,a,i,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]=u((()=>{}),["stop"]))},[bn,xn,Cn,kn,An,Sn,Tn,In,Pn,zn,Dn,_n,En,Mn])])};var Vn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),On=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Bn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Un=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Rn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),s=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),i=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:i,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function $n(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Gn={createCanvas:$n,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const o=$n(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=$n(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(),o}};const Fn=ae("Debug.js");let Ln=0,jn=0;var Wn=e=>{Ln=performance.now(),jn=e},Kn=e=>{const t=performance.now(),n=t-Ln;n>jn&&Fn.log(e+": "+n),Ln=t};function Hn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function Yn(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var Qn={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:Hn,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:Yn,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 Hn(Yn(e),Yn(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const qn=ae("PuzzleGraphics.js");function Zn(e,t){const n=se.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Xn={loadPuzzleBitmaps:async function(e){const t=await Gn.loadImageToBitmap(e.info.imageUrl),n=await Gn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){qn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=Qn.pointAdd(a,{x:o,y:0}),c=Qn.pointAdd(r,{x:0,y:o}),u=Qn.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;ose.decodePiece(Jn[e].puzzle.tiles[t]),mo=(e,t)=>ho(e,t).group,yo=(e,t)=>{const n=Jn[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},fo=(e,t)=>{const n=Jn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return Qn.pointAdd(o,l)},vo=(e,t)=>ho(e,t).pos,wo=e=>{const t=Oo(e),n=Bo(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}},bo=(e,t)=>{const n=Ao(e),o=ho(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},xo=(e,t)=>ho(e,t).z,Co=(e,t)=>{for(const n of Jn[e].puzzle.tiles){const e=se.decodePiece(n);if(e.owner===t)return e.idx}return-1},ko=e=>Jn[e].puzzle.info.tileDrawSize,Ao=e=>Jn[e].puzzle.info.tileSize,So=e=>Jn[e].puzzle.data.maxGroup,To=e=>Jn[e].puzzle.data.maxZ;function Io(e,t){const n=Jn[e].puzzle.info,o=se.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Po=(e,t,n)=>{for(const o of t)go(e,o,{z:n})},zo=(e,t,n)=>{const o=vo(e,t);go(e,t,{pos:Qn.pointAdd(o,n)})},Do=(e,t,n)=>{const o=ko(e),l=wo(e),a=n;for(const s of t){const t=ho(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)zo(e,s,a)},_o=(e,t)=>ho(e,t).owner,Eo=(e,t)=>{for(const n of t)go(e,n,{owner:-1,z:1})},Mo=(e,t,n)=>{for(const o of t)go(e,o,{owner:n})};function Vo(e,t){const n=Jn[e].puzzle.tiles,o=se.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=se.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const No=(e,t)=>{const n=to(e,t);return n?n.points:0},Oo=e=>Jn[e].puzzle.info.table.width,Bo=e=>Jn[e].puzzle.info.table.height;var Uo={setGame:function(e,t){Jn[e]=t},exists:function(e){return!!Jn[e]||!1},playerExists:oo,getActivePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*V;return lo(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){oo(e,t)?uo(e,t,{ts:n}):no(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:co,getPieceCount:ao,getImageUrl:function(e){return Jn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Jn[e].puzzle.info.imageUrl=t},get:function(e){return Jn[e]||null},getAllGames:function(){return Object.values(Jn).sort(((e,t)=>ro(e.id)===ro(t.id)?t.puzzle.data.started-e.puzzle.data.started:ro(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=to(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=to(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=to(e,t);return n?n.name:null},getPlayerIndexById:eo,getPlayerIdByIndex:function(e,t){return Jn[e].players.length>t?se.decodePlayer(Jn[e].players[t]).id:null},changePlayer:uo,setPlayer:no,setPiece:function(e,t,n){Jn[e].puzzle.tiles[t]=se.encodePiece(n)},setPuzzleData:function(e,t){Jn[e].puzzle.data=t},getTableWidth:Oo,getTableHeight:Bo,getPuzzle:e=>Jn[e].puzzle,getRng:e=>Jn[e].rng.obj,getPuzzleWidth:e=>Jn[e].puzzle.info.width,getPuzzleHeight:e=>Jn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Jn[e].puzzle.tiles.map(se.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Co(e,t);return n<0?null:Jn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Jn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:ko,getFinalPiecePos:fo,getStartTs:e=>Jn[e].puzzle.data.started,getFinishTs:e=>Jn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Jn[e].puzzle,s=function(e,t){return t in Jn[e].evtInfos?Jn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([Zt,a.data])},d=t=>{i.push([Xt,se.encodePiece(ho(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=to(e,t);n&&i.push([Jt,se.encodePlayer(n)])},p=n[0];if(p===Lt){const l=n[1];uo(e,t,{bgcolor:l,ts:o}),u()}else if(p===jt){const l=n[1];uo(e,t,{color:l,ts:o}),u()}else if(p===Wt){const l=`${n[1]}`.substr(0,16);uo(e,t,{name:l,ts:o}),u()}else if(p===Bt){const l=n[1],a=n[2],s=to(e,t);if(s){const n=s.x-l,i=s.y-a;uo(e,t,{ts:o,x:n,y:i}),u()}}else if(p===Ut){const l={x:n[1],y:n[2]};uo(e,t,{d:1,ts:o}),u(),s._last_mouse_down=l;const a=((e,t)=>{const n=Jn[e].puzzle.info,o=Jn[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=To(e)+1;po(e,{maxZ:n}),r();const o=Vo(e,a);Po(e,o,To(e)),Mo(e,o,t),c(o)}s._last_mouse=l}else if(p===$t){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)uo(e,t,{x:l,y:a,ts:o}),u();else{const n=Co(e,t);if(n>=0){uo(e,t,{x:l,y:a,ts:o}),u();const r=Vo(e,n);let d=Qn.pointInBounds(i,wo(e))&&Qn.pointInBounds(s._last_mouse_down,wo(e));for(const t of r){const n=bo(e,t);if(Qn.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Do(e,r,{x:t,y:n}),c(r)}}else uo(e,t,{ts:o}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===Rt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Co(e,t);if(g>=0){const n=Vo(e,g);Mo(e,n,0),c(n);const s=vo(e,g),i=fo(e,g);let h=!1;if(io(e)===ee.REAL){for(const t of n)if(yo(e,t)){h=!0;break}}else h=!0;if(h&&Qn.pointDistance(i,s){const l=Jn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=mo(e,t),l=mo(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=vo(e,t),s=Qn.pointAdd(vo(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(Qn.pointDistance(a,s){const o=Jn[e].puzzle.tiles,l=mo(e,t),a=mo(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(po(e,{maxGroup:So(e)+1}),r(),s=So(e));if(go(e,t,{group:s}),d(t),go(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=se.decodePiece(r);i.includes(t.group)&&(go(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=Vo(e,t),((e,t)=>-1===_o(e,t))(e,n))Eo(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=xo(e,o);t>n&&(n=t)}return n})(e,l);Po(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Vo(e,g)){const o=Io(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])){a=!0;break}}if(a&&so(e)===q.ANY){const n=No(e,t)+1;uo(e,t,{d:p,ts:o,points:n}),u()}else uo(e,t,{d:p,ts:o}),u();a&&io(e)===ee.REAL&&co(e)===ao(e)&&(po(e,{finished:o}),r()),a&&l&&l(t)}}else uo(e,t,{d:p,ts:o}),u();s._last_mouse=i}else if(p===Gt){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else if(p===Ft){const l=n[1],a=n[2];uo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else uo(e,t,{ts:o}),u();return function(e,t,n){Jn[e].evtInfos[t]=n}(e,t,s),i}};let Ro=-10,$o=20,Go=2,Fo=15;class Lo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Ro+Math.random()*$o,this.vy=-1*(Go+Math.random()*Fo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Go=t/2,Fo=t-Go;const n=1/4*this.canvas.width/(t/2);Ro=-n,$o=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Lo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Lo(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(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Gn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Zo=!0})),t}(l,Gn.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};pn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await pn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let x=()=>0;const C=async()=>{if("play"===o){const o=await pn.connect(n,e,t),l=se.decodeGame(o);Uo.setGame(l.id,l),x=()=>N()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=se.decodeGame(t.game);Uo.setGame(n.id,n),w.lastRealTs=N(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,x=()=>w.lastGameTs}}Zo=!0};await C();const k=Uo.getPieceDrawOffset(e),A=Uo.getPieceDrawSize(e),S=Uo.getPuzzleWidth(e),T=Uo.getPuzzleHeight(e),I=Uo.getTableWidth(e),P=Uo.getTableHeight(e),z={x:(I-S)/2,y:(P-T)/2},D={w:S,h:T},_={w:A,h:A},E=await Xn.loadPuzzleBitmaps(Uo.getPuzzle(e)),V=new Wo(v,Uo.getRng(e));V.init();const O=v.getContext("2d");v.classList.add("loaded");const B=Rn();B.move(-(I-v.width)/2,-(P-v.height)/2);const U=function(e,t,n,o){let l=[],a=!0,s=!1,i=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Ut,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Rt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([$t,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Gt:Ft;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([Kt]),"replay"===o&&("KeyI"===e.code&&v([Qt]),"KeyO"===e.code&&v([qt]),"KeyP"===e.code&&v([Yt])),"KeyF"===e.code&&(Qo=!Qo,Zo=!0),"KeyG"===e.code&&(qo=!qo,Zo=!0),"KeyM"===e.code&&v([Ht]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});v([Bt,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Gt,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([Ft,...e])}},setHotkeys:e=>{a=e}}}(v,window,B,o),R=Uo.getImageUrl(e),$=()=>{const t=Uo.getStartTs(e),n=Uo.getFinishTs(e),o=x();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),a.setPiecesTotal(Uo.getPieceCount(e));const G=x();a.setActivePlayers(Uo.getActivePlayers(e,G)),a.setIdlePlayers(Uo.getIdlePlayers(e,G));const F=!!Uo.getFinishTs(e);let L=F;const j=()=>L&&!F,W=()=>{const e=localStorage.getItem("sound_volume");if(null===e)return 100;const t=parseInt(e,10);return isNaN(t)?100:t},K=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},H=()=>{const e=W();i.volume=e/100,i.play()},Y=()=>"replay"===o?localStorage.getItem("bg_color")||"#222222":Uo.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",Q=()=>"replay"===o?localStorage.getItem("player_color")||"#ffffff":Uo.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let q="",Z="",X=!1;const J=e=>{X=e;const[t,n]=e?[q,"grab"]:[Z,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},ee=e=>{q=Gn.colorizedCanvas(r,c,e).toDataURL(),Z=Gn.colorizedCanvas(d,u,e).toDataURL(),J(X)};ee(Q());const te=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ne=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,te())},le=()=>{w.paused=!w.paused,te()},ae=[];let ie;let re;if("play"===o?ae.push(setInterval((()=>{$()}),1e3)):"replay"===o&&te(),"play"===o)pn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Jt:{const n=se.decodePlayer(a);n.id!==t&&(Uo.setPlayer(e,n.id,n),Zo=!0)}break;case Xt:{const t=se.decodePiece(a);Uo.setPiece(e,t.idx,t),Zo=!0}break;case Zt:Uo.setPuzzleData(e,a),Zo=!0}L=!!Uo.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===Vt){const t=o[1];return Uo.addPlayer(e,t,n),!0}if(o[0]===Nt){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Uo.addPlayer(e,t,n),!0}if(o[0]===Ot){const t=Uo.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Uo.handleInput(e,t,l,n),!0}return!1};let n=w.lastGameTs;const o=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=N();if(w.paused)return w.lastRealTs=l,void(ie=setTimeout(o,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const o=w.log[w.logPointer],l=n+o[o.length-1],a=w.log[e],i=a[a.length-1],r=l+i;if(r>s){w.skipNonActionPhases&&s+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/o),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{U.createKeyEvents();for(const n of U.consumeAll())if("play"===o){const o=n[0];if(o===Bt){const e=n[1],t=n[2],o=B.worldDimToViewport({w:e,h:t});Zo=!0,B.move(o.w,o.h)}else if(o===$t){if(de&&!Uo.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Zo=!0,B.move(o,l),de=t}}else if(o===jt)ee(n[1]);else if(o===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e),J(!0)}else if(o===Rt)de=null,J(!1);else if(o===Gt){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("in",B.worldToViewport(e))}else if(o===Ft){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("out",B.worldToViewport(e))}else o===Kt?a.togglePreview():o===Ht&&a.toggleSoundsEnabled();const l=x();Uo.handleInput(e,t,n,l,(e=>{K()&&H()})).length>0&&(Zo=!0),pn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===Yt)le();else if(e===qt)oe();else if(e===Qt)ne();else if(e===Bt){const e=n[1],t=n[2];Zo=!0,B.move(e,t)}else if(e===$t){if(de){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-de.x),l=Math.round(t.y-de.y);Zo=!0,B.move(o,l),de=t}}else if(e===jt)ee(n[1]);else if(e===Ut){const e={x:n[1],y:n[2]};de=B.worldToViewport(e),J(!0)}else if(e===Rt)de=null,J(!1);else if(e===Gt){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("in",B.worldToViewport(e))}else if(e===Ft){const e={x:n[1],y:n[2]};Zo=!0,B.zoom("out",B.worldToViewport(e))}else e===Kt&&a.togglePreview()}L=!!Uo.getFinishTs(e),j()&&(V.update(),Zo=!0)},render:async()=>{if(!Zo)return;const n=x();let l,s,i;window.DEBUG&&Wn(0),O.fillStyle=Y(),O.fillRect(0,0,v.width,v.height),window.DEBUG&&Kn("clear done"),l=B.worldToViewportRaw(z),s=B.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&Kn("board done");const r=Uo.getPiecesSortedByZIndex(e);window.DEBUG&&Kn("get tiles done"),s=B.worldDimToViewportRaw(_);for(const e of r)(-1===e.owner?Qo:qo)&&(i=E[e.idx],l=B.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&Kn("tiles done");const d=[];for(const a of Uo.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(i=await f(a),l=B.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,o]of d)O.fillText(e,t,o);window.DEBUG&&Kn("players done"),a.setActivePlayers(Uo.getActivePlayers(e,n)),a.setIdlePlayers(Uo.getIdlePlayers(e,n)),a.setPiecesDone(Uo.getFinishedPiecesCount(e)),window.DEBUG&&Kn("HUD done"),j()&&V.render(),Zo=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([Lt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([jt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Wt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},onSoundsVolumeChange:e=>{Ko.info("vol changed",e),localStorage.setItem("sound_volume",`${e}`),H()},replayOnSpeedUp:ne,replayOnSpeedDown:oe,replayOnPauseToggle:le,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:R,player:{background:Y(),color:Q(),name:"replay"===o?localStorage.getItem("player_name")||"#ffffff":Uo.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:K(),soundsVolume:W()},disconnect:pn.disconnect,connect:C,unload:()=>{ae.forEach((e=>{clearInterval(e)})),ie&&clearTimeout(ie),re&&re.stop()}}}var Jo=e({name:"game",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:Pt,ConnectionOverlay:gn,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 el={id:"game"},tl={class:"menu"},nl={class:"tabs"},ol=i("🧩 Puzzles");Jo.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",el,[p(n(u,{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"]),[[C,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",tl,[n("div",nl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[ol])),_: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 ll=e({name:"replay",components:{PuzzleStatus:ft,Scores:pt,SettingsOverlay:wt,PreviewOverlay:Pt,HelpOverlay:wn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await Xo(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 al={id:"replay"},sl=i("Skip no action phases: "),il={class:"menu"},rl={class:"tabs"},dl=i("🧩 Puzzles");ll.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",al,[p(n(g,{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"]),[[C,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("div",null,[n("label",null,[sl,p(n("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[x,e.skipNoAction]])])]),n("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",il,[n("div",rl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[dl])),_:1}),n("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:l[12]||(l[12]=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=k({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:nt},{name:"game",path:"/g/:id",component:Jo},{name:"replay",path:"/replay/:id",component:ll}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=S(T);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=se.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); diff --git a/build/public/assets/index.e0726e0c.js b/build/public/assets/index.e0726e0c.js new file mode 100644 index 0000000..a54c646 --- /dev/null +++ b/build/public/assets/index.e0726e0c.js @@ -0,0 +1 @@ +import{d as e,c as t,a as o,w as n,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as x,s as C,u as k,x as A,y as S}from"./vendor.684f7bc8.js";var P=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const T={id:"app"},I={key:0,class:"nav"},z=i("Index"),D=i("New game");P.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",T,[e.showNav?(s(),t("ul",I,[o("li",null,[o(p,{class:"btn",to:{name:"index"}},{default:n((()=>[z])),_:1})]),o("li",null,[o(p,{class:"btn",to:{name:"new-game"}},{default:n((()=>[D])),_:1})])])):l("",!0),o(g)])};const E=864e5,_=e=>{const t=Math.floor(e/E);e%=E;const o=Math.floor(e/36e5);e%=36e5;const n=Math.floor(e/6e4);e%=6e4;return`${t}d ${o}h ${n}m ${Math.floor(e/1e3)}s`};var M=1,V=1e3,N=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>_(t-e),U=_,B=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 o=t?"🏁":"⏳",n=e,l=t||N();return`${o} ${O(n,l)}`}}});const R={class:"game-info-text"},G=o("br",null,null,-1),$=o("br",null,null,-1),L=o("br",null,null,-1),F=i(" ↪️ Watch replay ");B.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[o(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:n((()=>[o("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),G,i(" 👥 "+r(e.game.players),1),$,i(" "+r(e.time(e.game.started,e.game.finished)),1),L])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:n((()=>[F])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:B},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 W=o("h1",null,"Running games",-1),H=o("h1",null,"Finished games",-1);j.render=function(e,n,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(s(!0),t(d,null,c(e.gamesRunning,((e,n)=>(s(),t("div",{class:"game-teaser-wrap",key:n},[o(p,{game:e},null,8,["game"])])))),128)),H,(s(!0),t(d,null,c(e.gamesFinished,((e,n)=>(s(),t("div",{class:"game-teaser-wrap",key:n},[o(p,{game:e},null,8,["game"])])))),128))])};var K=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});K.render=function(e,n,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:n[2]||(n[2]=(...t)=>e.onClick&&e.onClick(...t))},[o("div",{class:"btn edit",onClick:n[1]||(n[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y,q,Q,Z,X,J,ee,te,oe=e({name:"image-library",components:{ImageTeaser:K},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});oe.render=function(e,o,n,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((o,n)=>(s(),t(u,{image:o,onClick:t=>e.imageClicked(o),onEditClick:t=>e.imageEditClicked(o),key:n},null,8,["image","onClick","onEditClick"])))),128))])},(q=Y||(Y={}))[q.Flat=0]="Flat",q[q.Out=1]="Out",q[q.In=-1]="In",(Z=Q||(Q={}))[Z.FINAL=0]="FINAL",Z[Z.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class ne{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let o=0;o<=t.length-2;o++){const e=this.random(o,t.length-1),n=t[o];t[o]=t[e],t[e]=n}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new ne(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const o=`${e}`;return o.length>=t.length?o:t.substr(0,t.length-o.length)+o},ae=(...e)=>{const t=t=>(...o)=>{const n=new Date,l=le(n.getHours(),"00"),a=le(n.getMinutes(),"00"),s=le(n.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...o)};return{log:t("log"),error:t("error"),info:t("info")}};var se={hash:e=>{let t=0;for(let o=0;o{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",ne.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Q.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:ne.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const o=e.width/e.tileSize;return{x:t%o,y:Math.floor(t/o)}},asQueryArgs:function(e){const t=[];for(const o in e){const n=[o,e[o]].map(encodeURIComponent);t.push(n.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const ie={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};ie.render=function(e,o,n,l,a,i){return s(),t("div",{style:i.style,title:n.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-a4fa5e7e");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,n,a,i,u,m)=>(s(),t("div",null,[p(o("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":n[1]||(n[1]=t=>e.input=t),placeholder:"Plants, People",onChange:n[2]||(n[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:n[3]||(n[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:n[4]||(n[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ce,[o("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((o,n)=>(s(),t("li",{key:n,class:{active:n===e.autocomplete.idx},onClick:t=>e.addVal(o)},r(o),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((o,n)=>(s(),t("span",{key:n,class:"bit",onClick:t=>e.rm(o)},r(o)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-a4fa5e7e";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{autocompleteTags:{type:Function},uploadProgress:{type:Number},uploading:{type:String}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{uploadProgressPercent(){return this.uploadProgress?Math.round(100*this.uploadProgress):0},canPostToGallery(){return!this.uploading&&!(!this.previewUrl||!this.file)},canSetupGameClick(){return!this.uploading&&!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const o=null==(t=e.dataTransfer)?void 0:t.items;if(!o||0===o.length)return null;const n=o[0];return n.type.startsWith("image/")?n:null},onFileSelect(e){const t=e.target;if(!t.files)return;const o=t.files[0];o&&this.preview(o)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const o=t.getAsFile();return!!o&&(this.file=o,this.preview(o),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=o("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},ve=o("span",{class:"btn"},"Upload File",-1),we={class:"area-settings"},be=o("td",null,[o("label",null,"Title")],-1),xe=o("tr",null,[o("td",{colspan:"2"},[o("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ce=o("td",null,[o("label",null,"Tags")],-1),ke={class:"area-buttons"},Ae=i("🖼️ Post to gallery"),Se=i("🧩 Post to gallery "),Pe=o("br",null,null,-1),Te=i(" + set up game");ge.render=function(e,n,l,c,h,m){const y=a("responsive-image"),f=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:n[11]||(n[11]=t=>e.$emit("bgclick"))},[o("div",{class:"overlay-content",onClick:n[10]||(n[10]=u((()=>{}),["stop"]))},[o("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:n[3]||(n[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:n[4]||(n[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:n[5]||(n[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(s(),t("div",me,[o("span",{class:"remove btn",onClick:n[1]||(n[1]=t=>e.previewUrl="")},"X"),o(y,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",ye,[o("label",fe,[o("input",{type:"file",style:{display:"none"},onChange:n[2]||(n[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ve])]))],34),o("div",we,[o("table",null,[o("tr",null,[be,o("td",null,[p(o("input",{type:"text","onUpdate:modelValue":n[6]||(n[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),xe,o("tr",null,[Ce,o("td",null,[o(f,{modelValue:e.tags,"onUpdate:modelValue":n[7]||(n[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),o("div",ke,[o("button",{class:"btn",disabled:!e.canPostToGallery,onClick:n[8]||(n[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},["postToGallery"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Ae],64))],8,["disabled"]),o("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:n[9]||(n[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},["setupGame"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Se,Pe,Te],64))],8,["disabled"])])])])};var Ie=e({name:"edit-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const ze={class:"area-image"},De={class:"has-image"},Ee={class:"area-settings"},_e=o("td",null,[o("label",null,"Title")],-1),Me=o("tr",null,[o("td",{colspan:"2"},[o("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ve=o("td",null,[o("label",null,"Tags")],-1),Ne={class:"area-buttons"};Ie.render=function(e,n,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:n[5]||(n[5]=t=>e.$emit("bgclick"))},[o("div",{class:"overlay-content",onClick:n[4]||(n[4]=u((()=>{}),["stop"]))},[o("div",ze,[o("div",De,[o(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),o("div",Ee,[o("table",null,[o("tr",null,[_e,o("td",null,[p(o("input",{type:"text","onUpdate:modelValue":n[1]||(n[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Me,o("tr",null,[Ve,o("td",null,[o(h,{modelValue:e.tags,"onUpdate:modelValue":n[2]||(n[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),o("div",Ne,[o("button",{class:"btn",onClick:n[3]||(n[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Oe=e({name:"new-game-dialog",components:{ResponsiveImage:ie},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Q.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Ue={class:"area-image"},Be={class:"has-image"},Re={key:0,class:"image-title"},Ge={class:"area-settings"},$e=o("td",null,[o("label",null,"Pieces")],-1),Le=o("td",null,[o("label",null,"Scoring: ")],-1),Fe=i(" Any (Score when pieces are connected to each other or on final location)"),je=o("br",null,null,-1),We=i(" Final (Score when pieces are put to their final location)"),He=o("td",null,[o("label",null,"Shapes: ")],-1),Ke=i(" Normal"),Ye=o("br",null,null,-1),qe=i(" Any (flat pieces can occur anywhere)"),Qe=o("br",null,null,-1),Ze=i(" Flat (all pieces flat on all sides)"),Xe=o("td",null,[o("label",null,"Snapping: ")],-1),Je=i(" Normal (pieces snap to final destination and to each other)"),et=o("br",null,null,-1),tt=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),ot={class:"area-buttons"};Oe.render=function(e,n,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:n[11]||(n[11]=t=>e.$emit("bgclick"))},[o("div",{class:"overlay-content",onClick:n[10]||(n[10]=u((()=>{}),["stop"]))},[o("div",Ue,[o("div",Be,[o(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title?(s(),t("div",Re,'"'+r(e.image.title)+'"',1)):l("",!0)]),o("div",Ge,[o("table",null,[o("tr",null,[$e,o("td",null,[p(o("input",{type:"text","onUpdate:modelValue":n[1]||(n[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),o("tr",null,[Le,o("td",null,[o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[2]||(n[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Fe]),je,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[3]||(n[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),We])])]),o("tr",null,[He,o("td",null,[o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[4]||(n[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),Ke]),Ye,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[5]||(n[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),qe]),Qe,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[6]||(n[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),Ze])])]),o("tr",null,[Xe,o("td",null,[o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[7]||(n[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),Je]),et,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[8]||(n[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),tt])])])])]),o("div",ot,[o("button",{class:"btn",disabled:!e.canStartNewGame,onClick:n[9]||(n[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};const nt=async(e,t,o)=>new Promise(((n,l)=>{const a=new window.XMLHttpRequest;a.open(e,t,!0),a.withCredentials=!0;for(const e in o.headers||{})a.setRequestHeader(e,o.headers[e]);a.addEventListener("load",(function(e){n({status:this.status,text:this.responseText,json:async()=>JSON.parse(this.responseText)})})),a.addEventListener("error",(function(e){l(new Error("xhr error"))})),a.upload&&o.onUploadProgress&&a.upload.addEventListener("progress",(function(e){o.onUploadProgress&&o.onUploadProgress(e)})),a.send(o.body)}));var lt=(e,t)=>nt("post",e,t),at=e({components:{ImageLibrary:oe,NewImageDialog:ge,EditImageDialog:Ie,NewGameDialog:Oe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:"",uploading:"",uploadProgress:0}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((o=>!t.includes(o.title)&&o.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${se.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){this.uploadProgress=0;const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const o=await lt("/api/upload",{body:t,onUploadProgress:e=>{this.uploadProgress=e.loaded/e.total}});return this.uploadProgress=1,await o.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){this.uploading="postToGallery",await this.uploadImage(e),this.uploading="",this.dialog="",await this.loadImages()},async setupGameClick(e){this.uploading="setupGame";const t=await this.uploadImage(e);this.uploading="",this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const st={class:"upload-image-teaser"},it=o("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),rt={key:0},dt=i(" Tags: "),ct=i(" Sort by: "),ut=o("option",{value:"date_desc"},"Newest first",-1),pt=o("option",{value:"date_asc"},"Oldest first",-1),gt=o("option",{value:"alpha_asc"},"A-Z",-1),ht=o("option",{value:"alpha_desc"},"Z-A",-1);at.render=function(e,n,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[o("div",st,[o("div",{class:"btn btn-big",onClick:n[1]||(n[1]=t=>e.dialog="new-image")},"Upload your image"),it]),o("div",null,[e.tags.length>0?(s(),t("label",rt,[dt,(s(!0),t(d,null,c(e.relevantTags,((o,n)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(o.slug)}],key:n,onClick:t=>e.toggleTag(o)},r(o.title)+" ("+r(o.total)+")",11,["onClick"])))),128))])):l("",!0),o("label",null,[ct,p(o("select",{"onUpdate:modelValue":n[2]||(n[2]=t=>e.filters.sort=t),onChange:n[3]||(n[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[ut,pt,gt,ht],544),[[w,e.filters.sort]])])]),o(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:n[4]||(n[4]=t=>e.dialog=""),uploadProgress:e.uploadProgress,uploading:e.uploading,onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","uploadProgress","uploading","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:n[5]||(n[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:n[6]||(n[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var mt=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"},ft=o("div",null,"Scores",-1),vt=o("td",null,"⚡",-1),wt=o("td",null,"💤",-1);mt.render=function(e,n,l,a,i,u){return s(),t("div",yt,[ft,o("table",null,[(s(!0),t(d,null,c(e.actives,((e,n)=>(s(),t("tr",{key:n,style:{color:e.color}},[vt,o("td",null,r(e.name),1),o("td",null,r(e.points),1)],4)))),128)),(s(!0),t(d,null,c(e.idles,((e,n)=>(s(),t("tr",{key:n,style:{color:e.color}},[wt,o("td",null,r(e.name),1),o("td",null,r(e.points),1)],4)))),128))])])};var bt=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 U(this.duration)}}});const xt={class:"timer"};bt.render=function(e,n,l,a,i,d){return s(),t("div",xt,[o("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),o("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var Ct=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const kt=m();y("data-v-a1d1c822");const At=o("td",null,[o("label",null,"Background: ")],-1),St=o("td",null,[o("label",null,"Color: ")],-1),Pt=o("td",null,[o("label",null,"Name: ")],-1),Tt=o("td",null,[o("label",null,"Sounds: ")],-1),It=o("td",null,[o("label",null,"Sounds Volume: ")],-1),zt={class:"sound-volume"};f();const Dt=kt(((e,n,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:n[9]||(n[9]=t=>e.$emit("bgclick"))},[o("table",{class:"overlay-content settings",onClick:n[8]||(n[8]=u((()=>{}),["stop"]))},[o("tr",null,[At,o("td",null,[p(o("input",{type:"color","onUpdate:modelValue":n[1]||(n[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),o("tr",null,[St,o("td",null,[p(o("input",{type:"color","onUpdate:modelValue":n[2]||(n[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),o("tr",null,[Pt,o("td",null,[p(o("input",{type:"text",maxLength:"16","onUpdate:modelValue":n[3]||(n[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),o("tr",null,[Tt,o("td",null,[p(o("input",{type:"checkbox","onUpdate:modelValue":n[4]||(n[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[x,e.modelValue.soundsEnabled]])])]),o("tr",null,[It,o("td",zt,[o("span",{onClick:n[5]||(n[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),o("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:n[6]||(n[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),o("span",{onClick:n[7]||(n[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])])])]))));Ct.render=Dt,Ct.__scopeId="data-v-a1d1c822";var Et=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const _t={class:"preview"};Et.render=function(e,n,l,a,i,r){return s(),t("div",{class:"overlay",onClick:n[1]||(n[1]=t=>e.$emit("bgclick"))},[o("div",_t,[o("div",{class:"img",style:e.previewStyle},null,4)])])};var Mt=1,Vt=4,Nt=2,Ot=3,Ut=2,Bt=4,Rt=3,Gt=9,$t=1,Lt=2,Ft=3,jt=4,Wt=5,Ht=6,Kt=7,Yt=8,qt=10,Qt=11,Zt=12,Xt=13,Jt=14,eo=1,to=2,oo=3;const no=ae("Communication.js");let lo,ao=[],so=e=>{ao.push(e)},io=[],ro=e=>{io.push(e)};let co=0;const uo=e=>{co!==e&&(co=e,ro(e))};function po(e){if(2===co)try{lo.send(JSON.stringify(e))}catch(t){no.info("unable to send message.. maybe because ws is invalid?")}}let go,ho;var mo={connect:function(e,t,o){return go=0,ho={},uo(3),new Promise((n=>{lo=new WebSocket(e,o+"|"+t),lo.onopen=()=>{uo(2),po([Ot])},lo.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Vt){const e=t[1];n(e)}else{if(l!==Mt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],n=t[2];if(e===o&&ho[n])return void delete ho[n];so(t)}}},lo.onerror=()=>{throw uo(1),"[ 2021-05-15 onerror ]"},lo.onclose=e=>{4e3===e.code||1001===e.code?uo(4):uo(1)}}))},requestReplayData:async function(e,t){const o={gameId:e,offset:t},n=await fetch(`/api/replay-data${se.asQueryArgs(o)}`);return await n.json()},disconnect:function(){lo&&lo.close(4e3),go=0,ho={}},sendClientEvent:function(e){go++,ho[go]=e,po([Nt,go,ho[go]])},onServerChange:function(e){so=e;for(const t of ao)so(t);ao=[]},onConnectionStateChange:function(e){ro=e;for(const t of io)ro(t);io=[]},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},yo=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===mo.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===mo.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const fo={key:0,class:"overlay connection-lost"},vo={key:0,class:"overlay-content"},wo=o("div",null,"⁉️ LOST CONNECTION ⁉️",-1),bo={key:1,class:"overlay-content"},xo=o("div",null,"Connecting...",-1);yo.render=function(e,n,a,i,r,d){return e.show?(s(),t("div",fo,[e.lostConnection?(s(),t("div",vo,[wo,o("span",{class:"btn",onClick:n[1]||(n[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",bo,[xo])):l("",!0)])):l("",!0)};var Co=e({name:"help-overlay",emits:{bgclick:null}});const ko=o("tr",null,[o("td",null,"⬆️ Move up:"),o("td",null,[o("div",null,[o("kbd",null,"W"),i("/"),o("kbd",null,"↑"),i("/🖱️")])])],-1),Ao=o("tr",null,[o("td",null,"⬇️ Move down:"),o("td",null,[o("div",null,[o("kbd",null,"S"),i("/"),o("kbd",null,"↓"),i("/🖱️")])])],-1),So=o("tr",null,[o("td",null,"⬅️ Move left:"),o("td",null,[o("div",null,[o("kbd",null,"A"),i("/"),o("kbd",null,"←"),i("/🖱️")])])],-1),Po=o("tr",null,[o("td",null,"➡️ Move right:"),o("td",null,[o("div",null,[o("kbd",null,"D"),i("/"),o("kbd",null,"→"),i("/🖱️")])])],-1),To=o("tr",null,[o("td"),o("td",null,[o("div",null,[i("Move faster by holding "),o("kbd",null,"Shift")])])],-1),Io=o("tr",null,[o("td",null,"🔍+ Zoom in:"),o("td",null,[o("div",null,[o("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),zo=o("tr",null,[o("td",null,"🔍- Zoom out:"),o("td",null,[o("div",null,[o("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),Do=o("tr",null,[o("td",null,"🖼️ Toggle preview:"),o("td",null,[o("div",null,[o("kbd",null,"Space")])])],-1),Eo=o("tr",null,[o("td",null,"🧩✔️ Toggle fixed pieces:"),o("td",null,[o("div",null,[o("kbd",null,"F")])])],-1),_o=o("tr",null,[o("td",null,"🧩❓ Toggle loose pieces:"),o("td",null,[o("div",null,[o("kbd",null,"G")])])],-1),Mo=o("tr",null,[o("td",null,"🔉 Toggle sounds:"),o("td",null,[o("div",null,[o("kbd",null,"M")])])],-1),Vo=o("tr",null,[o("td",null,"⏫ Speed up (replay):"),o("td",null,[o("div",null,[o("kbd",null,"I")])])],-1),No=o("tr",null,[o("td",null,"⏬ Speed down (replay):"),o("td",null,[o("div",null,[o("kbd",null,"O")])])],-1),Oo=o("tr",null,[o("td",null,"⏸️ Pause (replay):"),o("td",null,[o("div",null,[o("kbd",null,"P")])])],-1);Co.render=function(e,n,l,a,i,r){return s(),t("div",{class:"overlay transparent",onClick:n[2]||(n[2]=t=>e.$emit("bgclick"))},[o("table",{class:"overlay-content help",onClick:n[1]||(n[1]=u((()=>{}),["stop"]))},[ko,Ao,So,Po,To,Io,zo,Do,Eo,_o,Mo,Vo,No,Oo])])};var Uo=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Bo=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Ro=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Go=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),$o=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Lo(){let e=0,t=0,o=1;const n=(n,l)=>{e+=n/o,t+=l/o},l=e=>{const t=o+.05*o*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},a=n=>({x:n.x/o-e,y:n.y/o-t}),s=n=>({x:(n.x+e)*o,y:(n.y+t)*o}),i=e=>({w:e.w*o,h:e.h*o}),r=e=>({w:e.w/o,h:e.h/o});return{getCurrentZoom:()=>o,move:n,canZoom:e=>o!=l(e),zoom:(e,t)=>((e,t)=>{if(o==e)return!1;const l=1-o/e;return n(-t.x*l,-t.y*l),o=e,!0})(l(e),t),worldToViewport:e=>{const{x:t,y:o}=s(e);return{x:Math.round(t),y:Math.round(o)}},worldToViewportRaw:s,worldDimToViewport:e=>{const{w:t,h:o}=i(e);return{w:Math.round(t),h:Math.round(o)}},worldDimToViewportRaw:i,viewportToWorld:e=>{const{x:t,y:o}=a(e);return{x:Math.round(t),y:Math.round(o)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:o}=r(e);return{w:Math.round(t),h:Math.round(o)}},viewportDimToWorldRaw:r}}function Fo(e=0,t=0){const o=document.createElement("canvas");return o.width=e,o.height=t,o}var jo={createCanvas:Fo,loadImageToBitmap:async function(e){return new Promise((t=>{const o=new Image;o.onload=()=>{createImageBitmap(o).then(t)},o.src=e}))},resizeBitmap:async function(e,t,o){const n=Fo(t,o);return n.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,o),await createImageBitmap(n)},colorizedCanvas:function(e,t,o){const n=Fo(e.width,e.height),l=n.getContext("2d");return l.save(),l.drawImage(t,0,0),l.fillStyle=o,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(),n}};const Wo=ae("Debug.js");let Ho=0,Ko=0;var Yo=e=>{Ho=performance.now(),Ko=e},qo=e=>{const t=performance.now(),o=t-Ho;o>Ko&&Wo.log(e+": "+o),Ho=t};function Qo(e,t){const o=e.x-t.x,n=e.y-t.y;return Math.sqrt(o*o+n*n)}function Zo(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var Xo={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:Qo,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:Zo,rectMoved:function(e,t,o){return{x:e.x+t,y:e.y+o,w:e.w,h:e.h}},rectCenterDistance:function(e,t){return Qo(Zo(e),Zo(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const Jo=ae("PuzzleGraphics.js");function en(e,t){const o=se.coordByPieceIdx(e,t);return{x:o.x*e.tileSize,y:o.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var tn={loadPuzzleBitmaps:async function(e){const t=await jo.loadImageToBitmap(e.info.imageUrl),o=await jo.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,o){Jo.log("start createPuzzleTileBitmaps");const n=o.tileSize,l=o.tileMarginWidth,a=o.tileDrawSize,s=n/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const o=new Path2D,a={x:l,y:l},r=Xo.pointAdd(a,{x:n,y:0}),c=Xo.pointAdd(r,{x:0,y:n}),u=Xo.pointSub(c,{x:n,y:0});if(o.moveTo(a.x,a.y),0!==e.top)for(let n=0;nse.decodePiece(on[e].puzzle.tiles[t]),vn=(e,t)=>fn(e,t).group,wn=(e,t)=>{const o=on[e].puzzle.info;return 0===t||t===o.tilesX-1||t===o.tiles-o.tilesX||t===o.tiles-1},bn=(e,t)=>{const o=on[e].puzzle.info,n={x:(o.table.width-o.width)/2,y:(o.table.height-o.height)/2},l=function(e,t){const o=on[e].puzzle.info,n=se.coordByPieceIdx(o,t),l=n.x*o.tileSize,a=n.y*o.tileSize;return{x:l,y:a}}(e,t);return Xo.pointAdd(n,l)},xn=(e,t)=>fn(e,t).pos,Cn=e=>{const t=Rn(e),o=Gn(e),n=Math.round(t/4),l=Math.round(o/4);return{x:0-n,y:0-l,w:t+2*n,h:o+2*l}},kn=(e,t)=>{const o=Tn(e),n=fn(e,t);return{x:n.pos.x,y:n.pos.y,w:o,h:o}},An=(e,t)=>fn(e,t).z,Sn=(e,t)=>{for(const o of on[e].puzzle.tiles){const e=se.decodePiece(o);if(e.owner===t)return e.idx}return-1},Pn=e=>on[e].puzzle.info.tileDrawSize,Tn=e=>on[e].puzzle.info.tileSize,In=e=>on[e].puzzle.data.maxGroup,zn=e=>on[e].puzzle.data.maxZ;function Dn(e,t){const o=on[e].puzzle.info,n=se.coordByPieceIdx(o,t);return[n.y>0?t-o.tilesX:-1,n.x0?t-1:-1]}const En=(e,t,o)=>{for(const n of t)yn(e,n,{z:o})},_n=(e,t,o)=>{const n=xn(e,t);yn(e,t,{pos:Xo.pointAdd(n,o)})},Mn=(e,t,o)=>{const n=Pn(e),l=Cn(e),a=o;for(const s of t){const t=fn(e,s);t.pos.x+o.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+n,a.x)),t.pos.y+o.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+n,a.y))}for(const s of t)_n(e,s,a)},Vn=(e,t)=>fn(e,t).owner,Nn=(e,t)=>{for(const o of t)yn(e,o,{owner:-1,z:1})},On=(e,t,o)=>{for(const n of t)yn(e,n,{owner:o})};function Un(e,t){const o=on[e].puzzle.tiles,n=se.decodePiece(o[t]),l=[];if(n.group)for(const a of o){const e=se.decodePiece(a);e.group===n.group&&l.push(e.idx)}else l.push(n.idx);return l}const Bn=(e,t)=>{const o=ln(e,t);return o?o.points:0},Rn=e=>on[e].puzzle.info.table.width,Gn=e=>on[e].puzzle.info.table.height;var $n={setGame:function(e,t){on[e]=t},exists:function(e){return!!on[e]||!1},playerExists:sn,getActivePlayers:function(e,t){const o=t-30*V;return rn(e).filter((e=>e.ts>=o))},getIdlePlayers:function(e,t){const o=t-30*V;return rn(e).filter((e=>e.ts0))},addPlayer:function(e,t,o){sn(e,t)?hn(e,t,{ts:o}):an(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,o))},getFinishedPiecesCount:gn,getPieceCount:dn,getImageUrl:function(e){return on[e].puzzle.info.imageUrl},setImageUrl:function(e,t){on[e].puzzle.info.imageUrl=t},get:function(e){return on[e]||null},getAllGames:function(){return Object.values(on).sort(((e,t)=>pn(e.id)===pn(t.id)?t.puzzle.data.started-e.puzzle.data.started:pn(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const o=ln(e,t);return o?o.bgcolor:null},getPlayerColor:(e,t)=>{const o=ln(e,t);return o?o.color:null},getPlayerName:(e,t)=>{const o=ln(e,t);return o?o.name:null},getPlayerIndexById:nn,getPlayerIdByIndex:function(e,t){return on[e].players.length>t?se.decodePlayer(on[e].players[t]).id:null},changePlayer:hn,setPlayer:an,setPiece:function(e,t,o){on[e].puzzle.tiles[t]=se.encodePiece(o)},setPuzzleData:function(e,t){on[e].puzzle.data=t},getTableWidth:Rn,getTableHeight:Gn,getPuzzle:e=>on[e].puzzle,getRng:e=>on[e].rng.obj,getPuzzleWidth:e=>on[e].puzzle.info.width,getPuzzleHeight:e=>on[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return on[e].puzzle.tiles.map(se.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const o=Sn(e,t);return o<0?null:on[e].puzzle.tiles[o]},getPieceDrawOffset:e=>on[e].puzzle.info.tileDrawOffset,getPieceDrawSize:Pn,getFinalPiecePos:bn,getStartTs:e=>on[e].puzzle.data.started,getFinishTs:e=>on[e].puzzle.data.finished,handleInput:function(e,t,o,n,l){const a=on[e].puzzle,s=function(e,t){return t in on[e].evtInfos?on[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([eo,a.data])},d=t=>{i.push([to,se.encodePiece(fn(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const o=ln(e,t);o&&i.push([oo,se.encodePlayer(o)])},p=o[0];if(p===Ht){const l=o[1];hn(e,t,{bgcolor:l,ts:n}),u()}else if(p===Kt){const l=o[1];hn(e,t,{color:l,ts:n}),u()}else if(p===Yt){const l=`${o[1]}`.substr(0,16);hn(e,t,{name:l,ts:n}),u()}else if(p===Gt){const l=o[1],a=o[2],s=ln(e,t);if(s){const o=s.x-l,i=s.y-a;hn(e,t,{ts:n,x:o,y:i}),u()}}else if(p===$t){const l={x:o[1],y:o[2]};hn(e,t,{d:1,ts:n}),u(),s._last_mouse_down=l;const a=((e,t)=>{const o=on[e].puzzle.info,n=on[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const o=zn(e)+1;mn(e,{maxZ:o}),r();const n=Un(e,a);En(e,n,zn(e)),On(e,n,t),c(n)}s._last_mouse=l}else if(p===Ft){const l=o[1],a=o[2],i={x:l,y:a};if(null===s._last_mouse_down)hn(e,t,{x:l,y:a,ts:n}),u();else{const o=Sn(e,t);if(o>=0){hn(e,t,{x:l,y:a,ts:n}),u();const r=Un(e,o);let d=Xo.pointInBounds(i,Cn(e))&&Xo.pointInBounds(s._last_mouse_down,Cn(e));for(const t of r){const o=kn(e,t);if(Xo.pointInBounds(i,o)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,o=a-s._last_mouse_down.y;Mn(e,r,{x:t,y:o}),c(r)}}else hn(e,t,{ts:n}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===Lt){const i={x:o[1],y:o[2]},p=0;s._last_mouse_down=null;const g=Sn(e,t);if(g>=0){const o=Un(e,g);On(e,o,0),c(o);const s=xn(e,g),i=bn(e,g);let h=!1;if(un(e)===ee.REAL){for(const t of o)if(wn(e,t)){h=!0;break}}else h=!0;if(h&&Xo.pointDistance(i,s){const l=on[e].puzzle.info;if(o<0)return!1;if(((e,t,o)=>{const n=vn(e,t),l=vn(e,o);return!(!n||n!==l)})(e,t,o))return!1;const a=xn(e,t),s=Xo.pointAdd(xn(e,o),{x:n[0]*l.tileSize,y:n[1]*l.tileSize});if(Xo.pointDistance(a,s){const n=on[e].puzzle.tiles,l=vn(e,t),a=vn(e,o);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(mn(e,{maxGroup:In(e)+1}),r(),s=In(e));if(yn(e,t,{group:s}),d(t),yn(e,o,{group:s}),d(o),i.length>0)for(const r of n){const t=se.decodePiece(r);i.includes(t.group)&&(yn(e,t.idx,{group:s}),d(t.idx))}})(e,t,o),l=Un(e,t),((e,t)=>-1===Vn(e,t))(e,o))Nn(e,l);else{const t=((e,t)=>{let o=0;for(const n of t){const t=An(e,n);t>o&&(o=t)}return o})(e,l);En(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Un(e,g)){const n=Dn(e,t);if(o(e,t,n[0],[0,1])||o(e,t,n[1],[-1,0])||o(e,t,n[2],[0,-1])||o(e,t,n[3],[1,0])){a=!0;break}}if(a&&cn(e)===Q.ANY){const o=Bn(e,t)+1;hn(e,t,{d:p,ts:n,points:o}),u()}else hn(e,t,{d:p,ts:n}),u();a&&un(e)===ee.REAL&&gn(e)===dn(e)&&(mn(e,{finished:n}),r()),a&&l&&l(t)}}else hn(e,t,{d:p,ts:n}),u();s._last_mouse=i}else if(p===jt){const l=o[1],a=o[2];hn(e,t,{x:l,y:a,ts:n}),u(),s._last_mouse={x:l,y:a}}else if(p===Wt){const l=o[1],a=o[2];hn(e,t,{x:l,y:a,ts:n}),u(),s._last_mouse={x:l,y:a}}else hn(e,t,{ts:n}),u();return function(e,t,o){on[e].evtInfos[t]=o}(e,t,s),i}};let Ln=-10,Fn=20,jn=2,Wn=15;class Hn{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Ln+Math.random()*Fn,this.vy=-1*(jn+Math.random()*Wn),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let o=0;o{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;jn=t/2,Wn=t-jn;const o=1/4*this.canvas.width/(t/2);Ln=-o,Fn=2*o}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Hn(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Hn(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 o=[];for(;this.particles.length>0;){const e=this.particles.shift();if(!e)break;e.update(),e.alive&&o.push(e)}this.particles=o}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(!y[t]){const o=e.d?r:d;if(e.color){const n=e.d?c:u;y[t]=await createImageBitmap(jo.colorizedCanvas(o,n,e.color))}else y[t]=o}return y[t]},v=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,el=!0})),t}(l,jo.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};mo.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const o=await mo.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...o.log),0===o.log.length&&(w.final=!0),o};let x=()=>0;const C=async()=>{if("play"===n){const n=await mo.connect(o,e,t),l=se.decodeGame(n);$n.setGame(l.id,l),x=()=>N()}else{if("replay"!==n)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const o=se.decodeGame(t.game);$n.setGame(o.id,o),w.lastRealTs=N(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,x=()=>w.lastGameTs}}el=!0};await C();const k=$n.getPieceDrawOffset(e),A=$n.getPieceDrawSize(e),S=$n.getPuzzleWidth(e),P=$n.getPuzzleHeight(e),T=$n.getTableWidth(e),I=$n.getTableHeight(e),z={x:(T-S)/2,y:(I-P)/2},D={w:S,h:P},E={w:A,h:A},_=await tn.loadPuzzleBitmaps($n.getPuzzle(e)),V=new Yn(v,$n.getRng(e));V.init();const O=v.getContext("2d");v.classList.add("loaded");const U=Lo();U.move(-(T-v.width)/2,-(I-v.height)/2);const B=function(e,t,o,n){let l=[],a=!0,s=!1,i=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const n=o.viewportToWorld({x:e,y:t});return[n.x,n.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([$t,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Lt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([Ft,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),o.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?jt:Wt;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([qt]),"replay"===n&&("KeyI"===e.code&&v([Xt]),"KeyO"===e.code&&v([Jt]),"KeyP"===e.code&&v([Zt])),"KeyF"===e.code&&(Xn=!Xn,el=!0),"KeyG"===e.code&&(Jn=!Jn,el=!0),"KeyM"===e.code&&v([Qt]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const n=(p?24:12)*Math.sqrt(o.getCurrentZoom()),l=o.viewportDimToWorld({w:e*n,h:t*n});v([Gt,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(o.canZoom("in")){const e=f||m();v([jt,...e])}}else if(u&&o.canZoom("out")){const e=f||m();v([Wt,...e])}},setHotkeys:e=>{a=e}}}(v,window,U,n),R=$n.getImageUrl(e),G=()=>{const t=$n.getStartTs(e),o=$n.getFinishTs(e),n=x();a.setFinished(!!o),a.setDuration((o||n)-t)};G(),a.setPiecesDone($n.getFinishedPiecesCount(e)),a.setPiecesTotal($n.getPieceCount(e));const $=x();a.setActivePlayers($n.getActivePlayers(e,$)),a.setIdlePlayers($n.getIdlePlayers(e,$));const L=!!$n.getFinishTs(e);let F=L;const j=()=>F&&!L,W=()=>{const e=localStorage.getItem("sound_volume");if(null===e)return 100;const t=parseInt(e,10);return isNaN(t)?100:t},H=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},K=()=>{const e=W();i.volume=e/100,i.play()},Y=()=>"replay"===n?localStorage.getItem("bg_color")||"#222222":$n.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",q=()=>"replay"===n?localStorage.getItem("player_color")||"#ffffff":$n.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let Q="",Z="",X=!1;const J=e=>{X=e;const[t,o]=e?[Q,"grab"]:[Z,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${o}`},ee=e=>{Q=jo.colorizedCanvas(r,c,e).toDataURL(),Z=jo.colorizedCanvas(d,u,e).toDataURL(),J(X)};ee(q());const te=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},oe=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,te())},le=()=>{w.paused=!w.paused,te()},ae=[];let ie;let re;if("play"===n?ae.push(setInterval((()=>{G()}),1e3)):"replay"===n&&te(),"play"===n)mo.onServerChange((o=>{o[0],o[1],o[2];const n=o[3];for(const[l,a]of n)switch(l){case oo:{const o=se.decodePlayer(a);o.id!==t&&($n.setPlayer(e,o.id,o),el=!0)}break;case to:{const t=se.decodePiece(a);$n.setPiece(e,t.idx,t),el=!0}break;case eo:$n.setPuzzleData(e,a),el=!0}F=!!$n.getFinishTs(e)}));else if("replay"===n){const t=(t,o)=>{const n=t;if(n[0]===Ut){const t=n[1];return $n.addPlayer(e,t,o),!0}if(n[0]===Bt){const t=$n.getPlayerIdByIndex(e,n[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return $n.addPlayer(e,t,o),!0}if(n[0]===Rt){const t=$n.getPlayerIdByIndex(e,n[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=n[2];return $n.handleInput(e,t,l,o),!0}return!1};let o=w.lastGameTs;const n=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=N();if(w.paused)return w.lastRealTs=l,void(ie=setTimeout(n,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const n=w.log[w.logPointer],l=o+n[n.length-1],a=w.log[e],i=a[a.length-1],r=l+i;if(r>s){w.skipNonActionPhases&&s+500*M{let t=!1;const o=e.fps||60,n=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/o,r=n*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/n),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{B.createKeyEvents();for(const o of B.consumeAll())if("play"===n){const n=o[0];if(n===Gt){const e=o[1],t=o[2],n=U.worldDimToViewport({w:e,h:t});el=!0,U.move(n.w,n.h)}else if(n===Ft){if(de&&!$n.getFirstOwnedPiece(e,t)){const e={x:o[1],y:o[2]},t=U.worldToViewport(e),n=Math.round(t.x-de.x),l=Math.round(t.y-de.y);el=!0,U.move(n,l),de=t}}else if(n===Kt)ee(o[1]);else if(n===$t){const e={x:o[1],y:o[2]};de=U.worldToViewport(e),J(!0)}else if(n===Lt)de=null,J(!1);else if(n===jt){const e={x:o[1],y:o[2]};el=!0,U.zoom("in",U.worldToViewport(e))}else if(n===Wt){const e={x:o[1],y:o[2]};el=!0,U.zoom("out",U.worldToViewport(e))}else n===qt?a.togglePreview():n===Qt&&a.toggleSoundsEnabled();const l=x();$n.handleInput(e,t,o,l,(e=>{H()&&K()})).length>0&&(el=!0),mo.sendClientEvent(o)}else if("replay"===n){const e=o[0];if(e===Zt)le();else if(e===Jt)ne();else if(e===Xt)oe();else if(e===Gt){const e=o[1],t=o[2];el=!0,U.move(e,t)}else if(e===Ft){if(de){const e={x:o[1],y:o[2]},t=U.worldToViewport(e),n=Math.round(t.x-de.x),l=Math.round(t.y-de.y);el=!0,U.move(n,l),de=t}}else if(e===Kt)ee(o[1]);else if(e===$t){const e={x:o[1],y:o[2]};de=U.worldToViewport(e),J(!0)}else if(e===Lt)de=null,J(!1);else if(e===jt){const e={x:o[1],y:o[2]};el=!0,U.zoom("in",U.worldToViewport(e))}else if(e===Wt){const e={x:o[1],y:o[2]};el=!0,U.zoom("out",U.worldToViewport(e))}else e===qt&&a.togglePreview()}F=!!$n.getFinishTs(e),j()&&(V.update(),el=!0)},render:async()=>{if(!el)return;const o=x();let l,s,i;window.DEBUG&&Yo(0),O.fillStyle=Y(),O.fillRect(0,0,v.width,v.height),window.DEBUG&&qo("clear done"),l=U.worldToViewportRaw(z),s=U.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&qo("board done");const r=$n.getPiecesSortedByZIndex(e);window.DEBUG&&qo("get tiles done"),s=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?Xn:Jn)&&(i=_[e.idx],l=U.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&qo("tiles done");const d=[];for(const a of $n.getActivePlayers(e,o))c=a,("replay"===n||c.id!==t)&&(i=await f(a),l=U.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,n]of d)O.fillText(e,t,n);window.DEBUG&&qo("players done"),a.setActivePlayers($n.getActivePlayers(e,o)),a.setIdlePlayers($n.getIdlePlayers(e,o)),a.setPiecesDone($n.getFinishedPiecesCount(e)),window.DEBUG&&qo("HUD done"),j()&&V.render(),el=!1}}),{setHotkeys:e=>{B.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),B.addEvent([Ht,e])},onColorChange:e=>{localStorage.setItem("player_color",e),B.addEvent([Kt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),B.addEvent([Yt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},onSoundsVolumeChange:e=>{qn.info("vol changed",e),localStorage.setItem("sound_volume",`${e}`),K()},replayOnSpeedUp:oe,replayOnSpeedDown:ne,replayOnPauseToggle:le,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:R,player:{background:Y(),color:q(),name:"replay"===n?localStorage.getItem("player_name")||"#ffffff":$n.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:H(),soundsVolume:W()},disconnect:mo.disconnect,connect:C,unload:()=>{ae.forEach((e=>{clearInterval(e)})),ie&&clearTimeout(ie),re&&re.stop()}}}var ol=e({name:"game",components:{PuzzleStatus:bt,Scores:mt,SettingsOverlay:Ct,PreviewOverlay:Et,ConnectionOverlay:yo,HelpOverlay:Co},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await tl(`${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},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 nl={id:"game"},ll={class:"menu"},al={class:"tabs"},sl=i("🧩 Puzzles");ol.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",nl,[p(o(u,{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"]),[[C,"settings"===e.overlay]]),p(o(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(o(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),o(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),o(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),o("div",ll,[o("div",al,[o(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:n((()=>[sl])),_:1}),o("div",{class:"opener",onClick:l[5]||(l[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),o("div",{class:"opener",onClick:l[6]||(l[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),o("div",{class:"opener",onClick:l[7]||(l[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),o(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var il=e({name:"replay",components:{PuzzleStatus:bt,Scores:mt,SettingsOverlay:Ct,PreviewOverlay:Et,HelpOverlay:Co},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await tl(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 rl={id:"replay"},dl=i("Skip no action phases: "),cl={class:"menu"},ul={class:"tabs"},pl=i("🧩 Puzzles");il.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",rl,[p(o(g,{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"]),[[C,"settings"===e.overlay]]),p(o(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(o(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),o(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:n((()=>[o("div",null,[o("div",null,r(e.replayText),1),o("div",null,[o("label",null,[dl,p(o("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[x,e.skipNoAction]])])]),o("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),o("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),o("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),o("div",cl,[o("div",ul,[o(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:n((()=>[pl])),_:1}),o("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),o("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),o("div",{class:"opener",onClick:l[12]||(l[12]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),o(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const o=k({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:at},{name:"game",path:"/g/:id",component:ol},{name:"replay",path:"/replay/:id",component:il}]});o.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const n=S(P);n.config.globalProperties.$config=t,n.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=se.uniqId(),localStorage.setItem("ID",e)),e}(),n.use(o),n.mount("#app")})(); diff --git a/build/public/index.html b/build/public/index.html index 39701ad..15064ca 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,7 +4,7 @@ 🧩 jigsaw.hyottoko.club - + diff --git a/src/frontend/components/NewImageDialog.vue b/src/frontend/components/NewImageDialog.vue index 976b7b5..0666f46 100644 --- a/src/frontend/components/NewImageDialog.vue +++ b/src/frontend/components/NewImageDialog.vue @@ -49,10 +49,21 @@ gallery", if possible!
- - + +
- @@ -75,6 +86,12 @@ export default defineComponent({ autocompleteTags: { type: Function, }, + uploadProgress: { + type: Number, + }, + uploading: { + type: String, + }, }, emits: { bgclick: null, @@ -91,10 +108,19 @@ export default defineComponent({ } }, computed: { + uploadProgressPercent (): number { + return this.uploadProgress ? Math.round(this.uploadProgress * 100) : 0 + }, canPostToGallery (): boolean { + if (this.uploading) { + return false + } return !!(this.previewUrl && this.file) }, canSetupGameClick (): boolean { + if (this.uploading) { + return false + } return !!(this.previewUrl && this.file) }, }, diff --git a/src/frontend/views/NewGame.vue b/src/frontend/views/NewGame.vue index fe30443..0b1f0cb 100644 --- a/src/frontend/views/NewGame.vue +++ b/src/frontend/views/NewGame.vue @@ -44,8 +44,11 @@ in jigsawpuzzles.io v-if="dialog==='new-image'" :autocompleteTags="autocompleteTags" @bgclick="dialog=''" + :uploadProgress="uploadProgress" + :uploading="uploading" @postToGalleryClick="postToGalleryClick" - @setupGameClick="setupGameClick" /> + @setupGameClick="setupGameClick" + /> + - +
diff --git a/build/server/main.js b/build/server/main.js index 971e133..7ac8c7c 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -1410,6 +1410,8 @@ const imageFromDb = (db, imageId) => { title: i.title, tags: getTags(db, i.id), created: i.created * 1000, + width: i.width, + height: i.height, }; }; const allImagesFromDb = (db, tagSlugs, orderBy) => { @@ -1447,8 +1449,13 @@ inner join images i on i.id = ixc.image_id ${where.sql}; title: i.title, tags: getTags(db, i.id), created: i.created * 1000, + width: i.width, + height: i.height, })); }; +/** + * @deprecated old function, now database is used + */ const allImagesFromDisk = (tags, sort) => { let images = fs.readdirSync(UPLOAD_DIR) .filter(f => f.toLowerCase().match(/\.(jpe?g|webp|png)$/)) @@ -1460,6 +1467,8 @@ const allImagesFromDisk = (tags, sort) => { title: f.replace(/\.[a-z]+$/, ''), tags: [], created: fs.statSync(`${UPLOAD_DIR}/${f}`).mtime.getTime(), + width: 0, + height: 0, // may have to fill when the function is used again })); switch (sort) { case 'alpha_asc': diff --git a/src/frontend/components/NewGameDialog.vue b/src/frontend/components/NewGameDialog.vue index 0042ade..e8862bc 100644 --- a/src/frontend/components/NewGameDialog.vue +++ b/src/frontend/components/NewGameDialog.vue @@ -6,7 +6,10 @@
-
"{{image.title}}"
+
+ "{{image.title}}" + ({{image.width}} ✕ {{image.height}}) +
@@ -18,27 +21,34 @@ - +
- + - +
- +
- + - +
- + @@ -192,4 +202,8 @@ export default defineComponent({ top: .5em; left: .5em; } + +.new-game-dialog .image-title > span { margin-right: .5em; } +.new-game-dialog .image-title > span:last-child { margin-right: 0; } +.image-title-dim { display: inline-block; white-space: no-wrap; } diff --git a/src/server/Images.ts b/src/server/Images.ts index 90889bc..8cda19a 100644 --- a/src/server/Images.ts +++ b/src/server/Images.ts @@ -26,7 +26,9 @@ interface ImageInfo url: string title: string tags: Tag[] - created: Timestamp, + created: Timestamp + width: number + height: number } const resizeImage = async (filename: string): Promise => { @@ -109,6 +111,8 @@ const imageFromDb = (db: Db, imageId: number): ImageInfo => { title: i.title, tags: getTags(db, i.id), created: i.created * 1000, + width: i.width, + height: i.height, } } @@ -153,9 +157,14 @@ inner join images i on i.id = ixc.image_id ${where.sql}; title: i.title, tags: getTags(db, i.id), created: i.created * 1000, + width: i.width, + height: i.height, })) } +/** + * @deprecated old function, now database is used + */ const allImagesFromDisk = ( tags: string[], sort: string @@ -170,6 +179,8 @@ const allImagesFromDisk = ( title: f.replace(/\.[a-z]+$/, ''), tags: [] as Tag[], created: fs.statSync(`${UPLOAD_DIR}/${f}`).mtime.getTime(), + width: 0, // may have to fill when the function is used again + height: 0, // may have to fill when the function is used again })) switch (sort) { From ac0116fc5272813720573fd0288d21073beb38c3 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sun, 4 Jul 2021 18:38:55 +0200 Subject: [PATCH 22/45] fix size / responsiveness of image dialogs --- .../assets/{index.4ee35f15.js => index.4ec065c1.js} | 0 .../{index.f00a590d.css => index.d345f13f.css} | 2 +- build/public/index.html | 4 ++-- src/frontend/components/EditImageDialog.vue | 12 +++++++++++- src/frontend/components/NewGameDialog.vue | 3 ++- src/frontend/components/NewImageDialog.vue | 3 ++- 6 files changed, 18 insertions(+), 6 deletions(-) rename build/public/assets/{index.4ee35f15.js => index.4ec065c1.js} (100%) rename build/public/assets/{index.f00a590d.css => index.d345f13f.css} (55%) diff --git a/build/public/assets/index.4ee35f15.js b/build/public/assets/index.4ec065c1.js similarity index 100% rename from build/public/assets/index.4ee35f15.js rename to build/public/assets/index.4ec065c1.js diff --git a/build/public/assets/index.f00a590d.css b/build/public/assets/index.d345f13f.css similarity index 55% rename from build/public/assets/index.f00a590d.css rename to build/public/assets/index.d345f13f.css index 58934f0..3b94f50 100644 --- a/build/public/assets/index.f00a590d.css +++ b/build/public/assets/index.d345f13f.css @@ -1 +1 @@ -:root{--main-color:#c1b19f;--main-darker-color:#4f4e4c;--link-color:#808db0;--link-hover-color:#c5cfeb;--highlight-color:#dd7e7e;--positive-color:#64a756;--input-bg-color:#262523;--bg-color:rgba(0,0,0,.7)}body,html{margin:0;background:#2b2b2b;color:var(--main-color);height:100%}*{font-family:monospace;font-size:15px}h1,h2,h3,h4{font-size:20px}a{color:var(--link-color);text-decoration:none}a:hover{color:var(--link-hover-color)}td,th{vertical-align:top}.btn{display:inline-block;background:var(--input-bg-color);color:var(--link-color);border:solid 1px #000;padding:5px 10px;box-shadow:1px 1px 2px rgba(0,0,0,.5),0 0 1px rgba(150,150,150,.4) inset;border-radius:4px;user-select:none}.btn-big{font-size:1.5em;padding:10px 20px}.btn:hover{background:#2f2e2c;color:var(--link-hover-color);border:solid 1px #111;box-shadow:0 0 1px rgba(150,150,150,.4) inset;cursor:pointer}.btn:disabled{background:#2f2e2c;color:#8c4747!important;border:solid 1px #111;box-shadow:0 0 1px rgba(150,150,150,.4) inset;cursor:not-allowed}input{background:#333230;border-radius:4px;color:var(--main-color);padding:6px 10px;border:solid 1px #000;box-shadow:0 0 3px rgba(0,0,0,.3) inset}input:focus{border:solid 1px #686767;background:var(--input-bg-color)}.scores{position:absolute;right:0;top:0;background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7)}.timer{position:absolute;left:0;top:0;background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7)}.menu{position:absolute;top:0;left:50%;transform:translateX(-50%);background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7);z-index:2}.closed{display:none}.overlay{position:fixed;top:0;left:0;right:0;bottom:0;z-index:10;background:var(--bg-color)}.overlay.transparent{background:0 0}.overlay-content{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7);z-index:1}.connection-lost .overlay-content{padding:20px;text-align:center}.preview{position:absolute;top:20px;left:20px;bottom:20px;right:20px}.preview .img{height:100%;width:100%;position:absolute;background-repeat:no-repeat;background-position:center;background-size:contain}.menu .opener{display:inline-block;margin-right:10px;color:var(--link-color)}.menu .opener:last-child{margin-right:0}.menu .opener:hover{color:var(--link-hover-color);cursor:pointer}kbd{background-color:#eee;border-radius:3px;border:1px solid #b4b4b4;box-shadow:0 1px 1px rgba(0,0,0,.2),0 2px 0 0 rgba(255,255,255,.7) inset;color:#333;display:inline-block;font-size:.85em;font-weight:700;line-height:1;padding:2px 4px;white-space:nowrap}.hint{color:var(--main-darker-color)}.bit{background:#3b3737;border-radius:.5em;padding:.25em .5em;display:inline-block;margin:0 .25em .25em 0;cursor:pointer}.bit.on{color:var(--positive-color)}.upload-image-teaser{text-align:center}.upload-image-teaser .btn{margin-bottom:.5em}table label{line-height:32px}.nav{list-style:none;padding:0}.nav li{display:inline-block;margin-right:1em}.image-list{overflow:scroll}.image-list-inner{white-space:nowrap}.imageteaser{width:150px;height:100px;display:inline-block;margin:5px;background-size:contain;background-position:center;background-repeat:no-repeat;background-color:#222;cursor:pointer}.game-teaser-wrap{display:inline-block;width:20%;padding:5px;box-sizing:border-box}.game-teaser{display:block;background-repeat:no-repeat;background-position:center;background-size:contain;position:relative;padding-top:56.25%;width:100%;background-color:#222}.game-info{position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%}.game-info-text{position:absolute;top:0;background:var(--bg-color);padding:5px}.game-replay{position:absolute;top:0;right:0}html.view-game{overflow:hidden}html.view-game body{overflow:hidden}html.view-replay{overflow:hidden}html.view-replay body{overflow:hidden}.imageteaser{position:relative}.imageteaser .edit{display:none;position:absolute}.imageteaser:hover .edit{display:inline-block}.input[data-v-a4fa5e7e]{margin-bottom:.5em}.autocomplete[data-v-a4fa5e7e]{position:relative}.autocomplete ul[data-v-a4fa5e7e]{list-style:none;padding:0;margin:0;position:absolute;left:0;right:0;background:#333230;top:-.5em}.autocomplete ul li[data-v-a4fa5e7e]{position:relative;padding:.5em .5em .5em 1.5em;cursor:pointer}.autocomplete ul li.active[data-v-a4fa5e7e]{color:var(--link-hover-color);background:var(--input-bg-color)}.autocomplete ul li.active[data-v-a4fa5e7e]:before{content:'▶';display:block;position:absolute;left:.5em}.new-image-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}@media (max-width:1400px){.new-image-dialog .overlay-content{grid-template-columns:auto;grid-template-rows:1fr min-content min-content;grid-template-areas:"image" "settings" "buttons"}.new-image-dialog .overlay-content .area-buttons .btn br{display:none}}.new-image-dialog .area-image{grid-area:image;margin:.5em;border:solid 6px transparent}.new-image-dialog .area-image.no-image{align-content:center;display:grid;text-align:center;border:solid 6px;position:relative}.new-image-dialog .area-image.droppable{border:dashed 6px}.new-image-dialog .area-image .has-image{position:relative;width:100%;height:100%}.new-image-dialog .area-image .has-image .remove{position:absolute;top:.5em;left:.5em}.new-image-dialog .area-settings{grid-area:settings}.new-image-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.new-image-dialog .area-buttons{align-self:end;grid-area:buttons}.new-image-dialog .area-buttons button{width:100%;margin-top:.5em}.new-image-dialog .upload{position:absolute;top:0;left:0;right:0;bottom:0;cursor:pointer}.new-image-dialog .upload .btn{position:absolute;top:50%;transform:translate(-50%,-50%)}.area-image .drop-target{display:none}.area-image.droppable .drop-target{pointer-events:none;position:absolute;top:0;left:0;right:0;bottom:0;z-index:3}.edit-image-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}.edit-image-dialog .area-image{grid-area:image;margin:20px}.edit-image-dialog .area-image.no-image{align-content:center;display:grid;text-align:center;border:dashed 6px;position:relative}.edit-image-dialog .area-image .has-image{position:relative;width:100%;height:100%}.edit-image-dialog .area-image .has-image .remove{position:absolute;top:.5em;left:.5em}.edit-image-dialog .area-settings{grid-area:settings}.edit-image-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.edit-image-dialog .area-buttons{align-self:end;grid-area:buttons}.edit-image-dialog .area-buttons button{width:100%;margin-top:.5em}.new-game-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}.new-game-dialog .area-image{grid-area:image;display:grid;grid-template-rows:1fr min-content;grid-template-areas:"image" "image-title";margin-right:1em}@media (max-width:1400px){.new-game-dialog .overlay-content{grid-template-columns:auto;grid-template-rows:1fr min-content min-content;grid-template-areas:"image" "settings" "buttons"}.new-game-dialog .area-image{margin-right:0}}.new-game-dialog .area-settings{grid-area:settings}.new-game-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.new-game-dialog .area-buttons{align-self:end;grid-area:buttons}.new-game-dialog .area-buttons button{width:100%}.new-game-dialog .has-image{box-sizing:border-box;grid-area:image;position:relative;width:100%;height:100%;border:solid 1px}.new-game-dialog .image-title{grid-area:image-title;text-align:center;padding:.5em 0;background:var(--main-color);color:#262523}.new-game-dialog .has-image .remove{position:absolute;top:.5em;left:.5em}.new-game-dialog .image-title>span{margin-right:.5em}.new-game-dialog .image-title>span:last-child{margin-right:0}.image-title-dim{display:inline-block;white-space:no-wrap}.sound-volume span[data-v-a1d1c822]{cursor:pointer;user-select:none}.sound-volume input[data-v-a1d1c822]{vertical-align:middle} \ No newline at end of file +:root{--main-color:#c1b19f;--main-darker-color:#4f4e4c;--link-color:#808db0;--link-hover-color:#c5cfeb;--highlight-color:#dd7e7e;--positive-color:#64a756;--input-bg-color:#262523;--bg-color:rgba(0,0,0,.7)}body,html{margin:0;background:#2b2b2b;color:var(--main-color);height:100%}*{font-family:monospace;font-size:15px}h1,h2,h3,h4{font-size:20px}a{color:var(--link-color);text-decoration:none}a:hover{color:var(--link-hover-color)}td,th{vertical-align:top}.btn{display:inline-block;background:var(--input-bg-color);color:var(--link-color);border:solid 1px #000;padding:5px 10px;box-shadow:1px 1px 2px rgba(0,0,0,.5),0 0 1px rgba(150,150,150,.4) inset;border-radius:4px;user-select:none}.btn-big{font-size:1.5em;padding:10px 20px}.btn:hover{background:#2f2e2c;color:var(--link-hover-color);border:solid 1px #111;box-shadow:0 0 1px rgba(150,150,150,.4) inset;cursor:pointer}.btn:disabled{background:#2f2e2c;color:#8c4747!important;border:solid 1px #111;box-shadow:0 0 1px rgba(150,150,150,.4) inset;cursor:not-allowed}input{background:#333230;border-radius:4px;color:var(--main-color);padding:6px 10px;border:solid 1px #000;box-shadow:0 0 3px rgba(0,0,0,.3) inset}input:focus{border:solid 1px #686767;background:var(--input-bg-color)}.scores{position:absolute;right:0;top:0;background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7)}.timer{position:absolute;left:0;top:0;background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7)}.menu{position:absolute;top:0;left:50%;transform:translateX(-50%);background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7);z-index:2}.closed{display:none}.overlay{position:fixed;top:0;left:0;right:0;bottom:0;z-index:10;background:var(--bg-color)}.overlay.transparent{background:0 0}.overlay-content{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7);z-index:1}.connection-lost .overlay-content{padding:20px;text-align:center}.preview{position:absolute;top:20px;left:20px;bottom:20px;right:20px}.preview .img{height:100%;width:100%;position:absolute;background-repeat:no-repeat;background-position:center;background-size:contain}.menu .opener{display:inline-block;margin-right:10px;color:var(--link-color)}.menu .opener:last-child{margin-right:0}.menu .opener:hover{color:var(--link-hover-color);cursor:pointer}kbd{background-color:#eee;border-radius:3px;border:1px solid #b4b4b4;box-shadow:0 1px 1px rgba(0,0,0,.2),0 2px 0 0 rgba(255,255,255,.7) inset;color:#333;display:inline-block;font-size:.85em;font-weight:700;line-height:1;padding:2px 4px;white-space:nowrap}.hint{color:var(--main-darker-color)}.bit{background:#3b3737;border-radius:.5em;padding:.25em .5em;display:inline-block;margin:0 .25em .25em 0;cursor:pointer}.bit.on{color:var(--positive-color)}.upload-image-teaser{text-align:center}.upload-image-teaser .btn{margin-bottom:.5em}table label{line-height:32px}.nav{list-style:none;padding:0}.nav li{display:inline-block;margin-right:1em}.image-list{overflow:scroll}.image-list-inner{white-space:nowrap}.imageteaser{width:150px;height:100px;display:inline-block;margin:5px;background-size:contain;background-position:center;background-repeat:no-repeat;background-color:#222;cursor:pointer}.game-teaser-wrap{display:inline-block;width:20%;padding:5px;box-sizing:border-box}.game-teaser{display:block;background-repeat:no-repeat;background-position:center;background-size:contain;position:relative;padding-top:56.25%;width:100%;background-color:#222}.game-info{position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%}.game-info-text{position:absolute;top:0;background:var(--bg-color);padding:5px}.game-replay{position:absolute;top:0;right:0}html.view-game{overflow:hidden}html.view-game body{overflow:hidden}html.view-replay{overflow:hidden}html.view-replay body{overflow:hidden}.imageteaser{position:relative}.imageteaser .edit{display:none;position:absolute}.imageteaser:hover .edit{display:inline-block}.input[data-v-a4fa5e7e]{margin-bottom:.5em}.autocomplete[data-v-a4fa5e7e]{position:relative}.autocomplete ul[data-v-a4fa5e7e]{list-style:none;padding:0;margin:0;position:absolute;left:0;right:0;background:#333230;top:-.5em}.autocomplete ul li[data-v-a4fa5e7e]{position:relative;padding:.5em .5em .5em 1.5em;cursor:pointer}.autocomplete ul li.active[data-v-a4fa5e7e]{color:var(--link-hover-color);background:var(--input-bg-color)}.autocomplete ul li.active[data-v-a4fa5e7e]:before{content:'▶';display:block;position:absolute;left:.5em}.new-image-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}@media (max-width:1400px) and (min-height:720px),(max-width:1000px){.new-image-dialog .overlay-content{grid-template-columns:auto;grid-template-rows:1fr min-content min-content;grid-template-areas:"image" "settings" "buttons"}.new-image-dialog .overlay-content .area-buttons .btn br{display:none}}.new-image-dialog .area-image{grid-area:image;margin:.5em;border:solid 6px transparent}.new-image-dialog .area-image.no-image{align-content:center;display:grid;text-align:center;border:solid 6px;position:relative}.new-image-dialog .area-image.droppable{border:dashed 6px}.new-image-dialog .area-image .has-image{position:relative;width:100%;height:100%}.new-image-dialog .area-image .has-image .remove{position:absolute;top:.5em;left:.5em}.new-image-dialog .area-settings{grid-area:settings}.new-image-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.new-image-dialog .area-buttons{align-self:end;grid-area:buttons}.new-image-dialog .area-buttons button{width:100%;margin-top:.5em}.new-image-dialog .upload{position:absolute;top:0;left:0;right:0;bottom:0;cursor:pointer}.new-image-dialog .upload .btn{position:absolute;top:50%;transform:translate(-50%,-50%)}.area-image .drop-target{display:none}.area-image.droppable .drop-target{pointer-events:none;position:absolute;top:0;left:0;right:0;bottom:0;z-index:3}.edit-image-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}@media (max-width:1400px) and (min-height:720px),(max-width:1000px){.edit-image-dialog .overlay-content{grid-template-columns:auto;grid-template-rows:1fr min-content min-content;grid-template-areas:"image" "settings" "buttons"}}.edit-image-dialog .area-image{grid-area:image;margin:20px}.edit-image-dialog .area-image.no-image{align-content:center;display:grid;text-align:center;border:dashed 6px;position:relative}.edit-image-dialog .area-image .has-image{position:relative;width:100%;height:100%}.edit-image-dialog .area-image .has-image .remove{position:absolute;top:.5em;left:.5em}.edit-image-dialog .area-settings{grid-area:settings}.edit-image-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.edit-image-dialog .area-buttons{align-self:end;grid-area:buttons}.edit-image-dialog .area-buttons button{width:100%;margin-top:.5em}.new-game-dialog .overlay-content{display:grid;grid-template-columns:auto 450px;grid-template-rows:auto;grid-template-areas:"image settings" "image buttons";height:90%;width:80%}.new-game-dialog .area-image{grid-area:image;display:grid;grid-template-rows:1fr min-content;grid-template-areas:"image" "image-title";margin-right:1em}@media (max-width:1400px) and (min-height:720px),(max-width:1000px){.new-game-dialog .overlay-content{grid-template-columns:auto;grid-template-rows:1fr min-content min-content;grid-template-areas:"image" "settings" "buttons"}.new-game-dialog .area-image{margin-right:0}}.new-game-dialog .area-settings{grid-area:settings}.new-game-dialog .area-settings table input[type=text]{width:100%;box-sizing:border-box}.new-game-dialog .area-buttons{align-self:end;grid-area:buttons}.new-game-dialog .area-buttons button{width:100%}.new-game-dialog .has-image{box-sizing:border-box;grid-area:image;position:relative;width:100%;height:100%;border:solid 1px}.new-game-dialog .image-title{grid-area:image-title;text-align:center;padding:.5em 0;background:var(--main-color);color:#262523}.new-game-dialog .has-image .remove{position:absolute;top:.5em;left:.5em}.new-game-dialog .image-title>span{margin-right:.5em}.new-game-dialog .image-title>span:last-child{margin-right:0}.image-title-dim{display:inline-block;white-space:no-wrap}.sound-volume span[data-v-a1d1c822]{cursor:pointer;user-select:none}.sound-volume input[data-v-a1d1c822]{vertical-align:middle} \ No newline at end of file diff --git a/build/public/index.html b/build/public/index.html index 46e0e01..277cded 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,9 +4,9 @@ 🧩 jigsaw.hyottoko.club - + - +
diff --git a/src/frontend/components/EditImageDialog.vue b/src/frontend/components/EditImageDialog.vue index ea8e34f..5a32ad9 100644 --- a/src/frontend/components/EditImageDialog.vue +++ b/src/frontend/components/EditImageDialog.vue @@ -96,7 +96,17 @@ export default defineComponent({ height: 90%; width: 80%; } - +@media (max-width: 1400px) and (min-height: 720px), + (max-width: 1000px) { + .edit-image-dialog .overlay-content { + grid-template-columns: auto; + grid-template-rows: 1fr min-content min-content; + grid-template-areas: + "image" + "settings" + "buttons"; + } +} .edit-image-dialog .area-image { grid-area: image; margin: 20px; diff --git a/src/frontend/components/NewGameDialog.vue b/src/frontend/components/NewGameDialog.vue index e8862bc..731c71f 100644 --- a/src/frontend/components/NewGameDialog.vue +++ b/src/frontend/components/NewGameDialog.vue @@ -152,7 +152,8 @@ export default defineComponent({ "image-title"; margin-right: 1em; } -@media (max-width: 1400px) { +@media (max-width: 1400px) and (min-height: 720px), + (max-width: 1000px) { .new-game-dialog .overlay-content { grid-template-columns: auto; grid-template-rows: 1fr min-content min-content; diff --git a/src/frontend/components/NewImageDialog.vue b/src/frontend/components/NewImageDialog.vue index 0666f46..c7cc832 100644 --- a/src/frontend/components/NewImageDialog.vue +++ b/src/frontend/components/NewImageDialog.vue @@ -210,7 +210,8 @@ export default defineComponent({ height: 90%; width: 80%; } -@media (max-width: 1400px) { +@media (max-width: 1400px) and (min-height: 720px), + (max-width: 1000px) { .new-image-dialog .overlay-content { grid-template-columns: auto; grid-template-rows: 1fr min-content min-content; From b43d45ecc6c8c426e4aebf1afae66d5041ff1498 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Tue, 6 Jul 2021 23:57:07 +0200 Subject: [PATCH 23/45] remove 'skip non action phases' toggle --- .../assets/{index.4ec065c1.js => index.31475a28.js} | 2 +- build/public/index.html | 2 +- src/frontend/game.ts | 5 ----- src/frontend/views/Replay.vue | 8 -------- 4 files changed, 2 insertions(+), 15 deletions(-) rename build/public/assets/{index.4ec065c1.js => index.31475a28.js} (80%) diff --git a/build/public/assets/index.4ec065c1.js b/build/public/assets/index.31475a28.js similarity index 80% rename from build/public/assets/index.4ec065c1.js rename to build/public/assets/index.31475a28.js index 6b3125b..89fa752 100644 --- a/build/public/assets/index.4ec065c1.js +++ b/build/public/assets/index.31475a28.js @@ -1 +1 @@ -import{d as e,c as t,a as o,w as n,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as x,s as C,u as k,x as A,y as S}from"./vendor.684f7bc8.js";var P=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const T={id:"app"},I={key:0,class:"nav"},z=i("Index"),D=i("New game");P.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",T,[e.showNav?(s(),t("ul",I,[o("li",null,[o(p,{class:"btn",to:{name:"index"}},{default:n((()=>[z])),_:1})]),o("li",null,[o(p,{class:"btn",to:{name:"new-game"}},{default:n((()=>[D])),_:1})])])):l("",!0),o(g)])};const E=864e5,_=e=>{const t=Math.floor(e/E);e%=E;const o=Math.floor(e/36e5);e%=36e5;const n=Math.floor(e/6e4);e%=6e4;return`${t}d ${o}h ${n}m ${Math.floor(e/1e3)}s`};var M=1,V=1e3,N=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>_(t-e),U=_,B=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 o=t?"🏁":"⏳",n=e,l=t||N();return`${o} ${O(n,l)}`}}});const R={class:"game-info-text"},G=o("br",null,null,-1),$=o("br",null,null,-1),L=o("br",null,null,-1),F=i(" ↪️ Watch replay ");B.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[o(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:n((()=>[o("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),G,i(" 👥 "+r(e.game.players),1),$,i(" "+r(e.time(e.game.started,e.game.finished)),1),L])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:n((()=>[F])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:B},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 W=o("h1",null,"Running games",-1),H=o("h1",null,"Finished games",-1);j.render=function(e,n,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(s(!0),t(d,null,c(e.gamesRunning,((e,n)=>(s(),t("div",{class:"game-teaser-wrap",key:n},[o(p,{game:e},null,8,["game"])])))),128)),H,(s(!0),t(d,null,c(e.gamesFinished,((e,n)=>(s(),t("div",{class:"game-teaser-wrap",key:n},[o(p,{game:e},null,8,["game"])])))),128))])};var K=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});K.render=function(e,n,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:n[2]||(n[2]=(...t)=>e.onClick&&e.onClick(...t))},[o("div",{class:"btn edit",onClick:n[1]||(n[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y,q,Q,Z,X,J,ee,te,oe=e({name:"image-library",components:{ImageTeaser:K},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});oe.render=function(e,o,n,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((o,n)=>(s(),t(u,{image:o,onClick:t=>e.imageClicked(o),onEditClick:t=>e.imageEditClicked(o),key:n},null,8,["image","onClick","onEditClick"])))),128))])},(q=Y||(Y={}))[q.Flat=0]="Flat",q[q.Out=1]="Out",q[q.In=-1]="In",(Z=Q||(Q={}))[Z.FINAL=0]="FINAL",Z[Z.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class ne{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let o=0;o<=t.length-2;o++){const e=this.random(o,t.length-1),n=t[o];t[o]=t[e],t[e]=n}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new ne(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const o=`${e}`;return o.length>=t.length?o:t.substr(0,t.length-o.length)+o},ae=(...e)=>{const t=t=>(...o)=>{const n=new Date,l=le(n.getHours(),"00"),a=le(n.getMinutes(),"00"),s=le(n.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...o)};return{log:t("log"),error:t("error"),info:t("info")}};var se={hash:e=>{let t=0;for(let o=0;o{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",ne.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Q.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:ne.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const o=e.width/e.tileSize;return{x:t%o,y:Math.floor(t/o)}},asQueryArgs:function(e){const t=[];for(const o in e){const n=[o,e[o]].map(encodeURIComponent);t.push(n.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const ie={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};ie.render=function(e,o,n,l,a,i){return s(),t("div",{style:i.style,title:n.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-a4fa5e7e");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,n,a,i,u,m)=>(s(),t("div",null,[p(o("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":n[1]||(n[1]=t=>e.input=t),placeholder:"Plants, People",onChange:n[2]||(n[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:n[3]||(n[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:n[4]||(n[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ce,[o("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((o,n)=>(s(),t("li",{key:n,class:{active:n===e.autocomplete.idx},onClick:t=>e.addVal(o)},r(o),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((o,n)=>(s(),t("span",{key:n,class:"bit",onClick:t=>e.rm(o)},r(o)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-a4fa5e7e";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{autocompleteTags:{type:Function},uploadProgress:{type:Number},uploading:{type:String}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{uploadProgressPercent(){return this.uploadProgress?Math.round(100*this.uploadProgress):0},canPostToGallery(){return!this.uploading&&!(!this.previewUrl||!this.file)},canSetupGameClick(){return!this.uploading&&!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const o=null==(t=e.dataTransfer)?void 0:t.items;if(!o||0===o.length)return null;const n=o[0];return n.type.startsWith("image/")?n:null},onFileSelect(e){const t=e.target;if(!t.files)return;const o=t.files[0];o&&this.preview(o)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const o=t.getAsFile();return!!o&&(this.file=o,this.preview(o),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=o("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},ve=o("span",{class:"btn"},"Upload File",-1),we={class:"area-settings"},be=o("td",null,[o("label",null,"Title")],-1),xe=o("tr",null,[o("td",{colspan:"2"},[o("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ce=o("td",null,[o("label",null,"Tags")],-1),ke={class:"area-buttons"},Ae=i("🖼️ Post to gallery"),Se=i("🧩 Post to gallery "),Pe=o("br",null,null,-1),Te=i(" + set up game");ge.render=function(e,n,l,c,h,m){const y=a("responsive-image"),f=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:n[11]||(n[11]=t=>e.$emit("bgclick"))},[o("div",{class:"overlay-content",onClick:n[10]||(n[10]=u((()=>{}),["stop"]))},[o("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:n[3]||(n[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:n[4]||(n[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:n[5]||(n[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(s(),t("div",me,[o("span",{class:"remove btn",onClick:n[1]||(n[1]=t=>e.previewUrl="")},"X"),o(y,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",ye,[o("label",fe,[o("input",{type:"file",style:{display:"none"},onChange:n[2]||(n[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ve])]))],34),o("div",we,[o("table",null,[o("tr",null,[be,o("td",null,[p(o("input",{type:"text","onUpdate:modelValue":n[6]||(n[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),xe,o("tr",null,[Ce,o("td",null,[o(f,{modelValue:e.tags,"onUpdate:modelValue":n[7]||(n[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),o("div",ke,[o("button",{class:"btn",disabled:!e.canPostToGallery,onClick:n[8]||(n[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},["postToGallery"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Ae],64))],8,["disabled"]),o("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:n[9]||(n[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},["setupGame"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Se,Pe,Te],64))],8,["disabled"])])])])};var Ie=e({name:"edit-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const ze={class:"area-image"},De={class:"has-image"},Ee={class:"area-settings"},_e=o("td",null,[o("label",null,"Title")],-1),Me=o("tr",null,[o("td",{colspan:"2"},[o("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ve=o("td",null,[o("label",null,"Tags")],-1),Ne={class:"area-buttons"};Ie.render=function(e,n,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:n[5]||(n[5]=t=>e.$emit("bgclick"))},[o("div",{class:"overlay-content",onClick:n[4]||(n[4]=u((()=>{}),["stop"]))},[o("div",ze,[o("div",De,[o(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),o("div",Ee,[o("table",null,[o("tr",null,[_e,o("td",null,[p(o("input",{type:"text","onUpdate:modelValue":n[1]||(n[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Me,o("tr",null,[Ve,o("td",null,[o(h,{modelValue:e.tags,"onUpdate:modelValue":n[2]||(n[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),o("div",Ne,[o("button",{class:"btn",onClick:n[3]||(n[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Oe=e({name:"new-game-dialog",components:{ResponsiveImage:ie},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Q.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Ue={class:"area-image"},Be={class:"has-image"},Re={key:0,class:"image-title"},Ge={key:0,class:"image-title-title"},$e={key:1,class:"image-title-dim"},Le={class:"area-settings"},Fe=o("td",null,[o("label",null,"Pieces")],-1),je=o("td",null,[o("label",null,"Scoring: ")],-1),We=i(" Any (Score when pieces are connected to each other or on final location)"),He=o("br",null,null,-1),Ke=i(" Final (Score when pieces are put to their final location)"),Ye=o("td",null,[o("label",null,"Shapes: ")],-1),qe=i(" Normal"),Qe=o("br",null,null,-1),Ze=i(" Any (flat pieces can occur anywhere)"),Xe=o("br",null,null,-1),Je=i(" Flat (all pieces flat on all sides)"),et=o("td",null,[o("label",null,"Snapping: ")],-1),tt=i(" Normal (pieces snap to final destination and to each other)"),ot=o("br",null,null,-1),nt=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),lt={class:"area-buttons"};Oe.render=function(e,n,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:n[11]||(n[11]=t=>e.$emit("bgclick"))},[o("div",{class:"overlay-content",onClick:n[10]||(n[10]=u((()=>{}),["stop"]))},[o("div",Ue,[o("div",Be,[o(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title||e.image.width||e.image.height?(s(),t("div",Re,[e.image.title?(s(),t("span",Ge,'"'+r(e.image.title)+'"',1)):l("",!0),e.image.width||e.image.height?(s(),t("span",$e,"("+r(e.image.width)+" ✕ "+r(e.image.height)+")",1)):l("",!0)])):l("",!0)]),o("div",Le,[o("table",null,[o("tr",null,[Fe,o("td",null,[p(o("input",{type:"text","onUpdate:modelValue":n[1]||(n[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),o("tr",null,[je,o("td",null,[o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[2]||(n[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),We]),He,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[3]||(n[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),Ke])])]),o("tr",null,[Ye,o("td",null,[o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[4]||(n[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),qe]),Qe,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[5]||(n[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ze]),Xe,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[6]||(n[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),Je])])]),o("tr",null,[et,o("td",null,[o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[7]||(n[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),tt]),ot,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[8]||(n[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),nt])])])])]),o("div",lt,[o("button",{class:"btn",disabled:!e.canStartNewGame,onClick:n[9]||(n[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};const at=async(e,t,o)=>new Promise(((n,l)=>{const a=new window.XMLHttpRequest;a.open(e,t,!0),a.withCredentials=!0;for(const e in o.headers||{})a.setRequestHeader(e,o.headers[e]);a.addEventListener("load",(function(e){n({status:this.status,text:this.responseText,json:async()=>JSON.parse(this.responseText)})})),a.addEventListener("error",(function(e){l(new Error("xhr error"))})),a.upload&&o.onUploadProgress&&a.upload.addEventListener("progress",(function(e){o.onUploadProgress&&o.onUploadProgress(e)})),a.send(o.body)}));var st=(e,t)=>at("post",e,t),it=e({components:{ImageLibrary:oe,NewImageDialog:ge,EditImageDialog:Ie,NewGameDialog:Oe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:"",uploading:"",uploadProgress:0}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((o=>!t.includes(o.title)&&o.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${se.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){this.uploadProgress=0;const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const o=await st("/api/upload",{body:t,onUploadProgress:e=>{this.uploadProgress=e.loaded/e.total}});return this.uploadProgress=1,await o.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){this.uploading="postToGallery",await this.uploadImage(e),this.uploading="",this.dialog="",await this.loadImages()},async setupGameClick(e){this.uploading="setupGame";const t=await this.uploadImage(e);this.uploading="",this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const rt={class:"upload-image-teaser"},dt=o("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),ct={key:0},ut=i(" Tags: "),pt=i(" Sort by: "),gt=o("option",{value:"date_desc"},"Newest first",-1),ht=o("option",{value:"date_asc"},"Oldest first",-1),mt=o("option",{value:"alpha_asc"},"A-Z",-1),yt=o("option",{value:"alpha_desc"},"Z-A",-1);it.render=function(e,n,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[o("div",rt,[o("div",{class:"btn btn-big",onClick:n[1]||(n[1]=t=>e.dialog="new-image")},"Upload your image"),dt]),o("div",null,[e.tags.length>0?(s(),t("label",ct,[ut,(s(!0),t(d,null,c(e.relevantTags,((o,n)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(o.slug)}],key:n,onClick:t=>e.toggleTag(o)},r(o.title)+" ("+r(o.total)+")",11,["onClick"])))),128))])):l("",!0),o("label",null,[pt,p(o("select",{"onUpdate:modelValue":n[2]||(n[2]=t=>e.filters.sort=t),onChange:n[3]||(n[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[gt,ht,mt,yt],544),[[w,e.filters.sort]])])]),o(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:n[4]||(n[4]=t=>e.dialog=""),uploadProgress:e.uploadProgress,uploading:e.uploading,onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","uploadProgress","uploading","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:n[5]||(n[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:n[6]||(n[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ft=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"},wt=o("div",null,"Scores",-1),bt=o("td",null,"⚡",-1),xt=o("td",null,"💤",-1);ft.render=function(e,n,l,a,i,u){return s(),t("div",vt,[wt,o("table",null,[(s(!0),t(d,null,c(e.actives,((e,n)=>(s(),t("tr",{key:n,style:{color:e.color}},[bt,o("td",null,r(e.name),1),o("td",null,r(e.points),1)],4)))),128)),(s(!0),t(d,null,c(e.idles,((e,n)=>(s(),t("tr",{key:n,style:{color:e.color}},[xt,o("td",null,r(e.name),1),o("td",null,r(e.points),1)],4)))),128))])])};var Ct=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 U(this.duration)}}});const kt={class:"timer"};Ct.render=function(e,n,l,a,i,d){return s(),t("div",kt,[o("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),o("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var At=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const St=m();y("data-v-a1d1c822");const Pt=o("td",null,[o("label",null,"Background: ")],-1),Tt=o("td",null,[o("label",null,"Color: ")],-1),It=o("td",null,[o("label",null,"Name: ")],-1),zt=o("td",null,[o("label",null,"Sounds: ")],-1),Dt=o("td",null,[o("label",null,"Sounds Volume: ")],-1),Et={class:"sound-volume"};f();const _t=St(((e,n,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:n[9]||(n[9]=t=>e.$emit("bgclick"))},[o("table",{class:"overlay-content settings",onClick:n[8]||(n[8]=u((()=>{}),["stop"]))},[o("tr",null,[Pt,o("td",null,[p(o("input",{type:"color","onUpdate:modelValue":n[1]||(n[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),o("tr",null,[Tt,o("td",null,[p(o("input",{type:"color","onUpdate:modelValue":n[2]||(n[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),o("tr",null,[It,o("td",null,[p(o("input",{type:"text",maxLength:"16","onUpdate:modelValue":n[3]||(n[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),o("tr",null,[zt,o("td",null,[p(o("input",{type:"checkbox","onUpdate:modelValue":n[4]||(n[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[x,e.modelValue.soundsEnabled]])])]),o("tr",null,[Dt,o("td",Et,[o("span",{onClick:n[5]||(n[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),o("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:n[6]||(n[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),o("span",{onClick:n[7]||(n[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])])])]))));At.render=_t,At.__scopeId="data-v-a1d1c822";var Mt=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const Vt={class:"preview"};Mt.render=function(e,n,l,a,i,r){return s(),t("div",{class:"overlay",onClick:n[1]||(n[1]=t=>e.$emit("bgclick"))},[o("div",Vt,[o("div",{class:"img",style:e.previewStyle},null,4)])])};var Nt=1,Ot=4,Ut=2,Bt=3,Rt=2,Gt=4,$t=3,Lt=9,Ft=1,jt=2,Wt=3,Ht=4,Kt=5,Yt=6,qt=7,Qt=8,Zt=10,Xt=11,Jt=12,eo=13,to=14,oo=1,no=2,lo=3;const ao=ae("Communication.js");let so,io=[],ro=e=>{io.push(e)},co=[],uo=e=>{co.push(e)};let po=0;const go=e=>{po!==e&&(po=e,uo(e))};function ho(e){if(2===po)try{so.send(JSON.stringify(e))}catch(t){ao.info("unable to send message.. maybe because ws is invalid?")}}let mo,yo;var fo={connect:function(e,t,o){return mo=0,yo={},go(3),new Promise((n=>{so=new WebSocket(e,o+"|"+t),so.onopen=()=>{go(2),ho([Bt])},so.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Ot){const e=t[1];n(e)}else{if(l!==Nt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],n=t[2];if(e===o&&yo[n])return void delete yo[n];ro(t)}}},so.onerror=()=>{throw go(1),"[ 2021-05-15 onerror ]"},so.onclose=e=>{4e3===e.code||1001===e.code?go(4):go(1)}}))},requestReplayData:async function(e,t){const o={gameId:e,offset:t},n=await fetch(`/api/replay-data${se.asQueryArgs(o)}`);return await n.json()},disconnect:function(){so&&so.close(4e3),mo=0,yo={}},sendClientEvent:function(e){mo++,yo[mo]=e,ho([Ut,mo,yo[mo]])},onServerChange:function(e){ro=e;for(const t of io)ro(t);io=[]},onConnectionStateChange:function(e){uo=e;for(const t of co)uo(t);co=[]},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},vo=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===fo.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===fo.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const wo={key:0,class:"overlay connection-lost"},bo={key:0,class:"overlay-content"},xo=o("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Co={key:1,class:"overlay-content"},ko=o("div",null,"Connecting...",-1);vo.render=function(e,n,a,i,r,d){return e.show?(s(),t("div",wo,[e.lostConnection?(s(),t("div",bo,[xo,o("span",{class:"btn",onClick:n[1]||(n[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",Co,[ko])):l("",!0)])):l("",!0)};var Ao=e({name:"help-overlay",emits:{bgclick:null}});const So=o("tr",null,[o("td",null,"⬆️ Move up:"),o("td",null,[o("div",null,[o("kbd",null,"W"),i("/"),o("kbd",null,"↑"),i("/🖱️")])])],-1),Po=o("tr",null,[o("td",null,"⬇️ Move down:"),o("td",null,[o("div",null,[o("kbd",null,"S"),i("/"),o("kbd",null,"↓"),i("/🖱️")])])],-1),To=o("tr",null,[o("td",null,"⬅️ Move left:"),o("td",null,[o("div",null,[o("kbd",null,"A"),i("/"),o("kbd",null,"←"),i("/🖱️")])])],-1),Io=o("tr",null,[o("td",null,"➡️ Move right:"),o("td",null,[o("div",null,[o("kbd",null,"D"),i("/"),o("kbd",null,"→"),i("/🖱️")])])],-1),zo=o("tr",null,[o("td"),o("td",null,[o("div",null,[i("Move faster by holding "),o("kbd",null,"Shift")])])],-1),Do=o("tr",null,[o("td",null,"🔍+ Zoom in:"),o("td",null,[o("div",null,[o("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Eo=o("tr",null,[o("td",null,"🔍- Zoom out:"),o("td",null,[o("div",null,[o("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),_o=o("tr",null,[o("td",null,"🖼️ Toggle preview:"),o("td",null,[o("div",null,[o("kbd",null,"Space")])])],-1),Mo=o("tr",null,[o("td",null,"🧩✔️ Toggle fixed pieces:"),o("td",null,[o("div",null,[o("kbd",null,"F")])])],-1),Vo=o("tr",null,[o("td",null,"🧩❓ Toggle loose pieces:"),o("td",null,[o("div",null,[o("kbd",null,"G")])])],-1),No=o("tr",null,[o("td",null,"🔉 Toggle sounds:"),o("td",null,[o("div",null,[o("kbd",null,"M")])])],-1),Oo=o("tr",null,[o("td",null,"⏫ Speed up (replay):"),o("td",null,[o("div",null,[o("kbd",null,"I")])])],-1),Uo=o("tr",null,[o("td",null,"⏬ Speed down (replay):"),o("td",null,[o("div",null,[o("kbd",null,"O")])])],-1),Bo=o("tr",null,[o("td",null,"⏸️ Pause (replay):"),o("td",null,[o("div",null,[o("kbd",null,"P")])])],-1);Ao.render=function(e,n,l,a,i,r){return s(),t("div",{class:"overlay transparent",onClick:n[2]||(n[2]=t=>e.$emit("bgclick"))},[o("table",{class:"overlay-content help",onClick:n[1]||(n[1]=u((()=>{}),["stop"]))},[So,Po,To,Io,zo,Do,Eo,_o,Mo,Vo,No,Oo,Uo,Bo])])};var Ro=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Go=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),$o=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Lo=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Fo=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function jo(){let e=0,t=0,o=1;const n=(n,l)=>{e+=n/o,t+=l/o},l=e=>{const t=o+.05*o*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},a=n=>({x:n.x/o-e,y:n.y/o-t}),s=n=>({x:(n.x+e)*o,y:(n.y+t)*o}),i=e=>({w:e.w*o,h:e.h*o}),r=e=>({w:e.w/o,h:e.h/o});return{getCurrentZoom:()=>o,move:n,canZoom:e=>o!=l(e),zoom:(e,t)=>((e,t)=>{if(o==e)return!1;const l=1-o/e;return n(-t.x*l,-t.y*l),o=e,!0})(l(e),t),worldToViewport:e=>{const{x:t,y:o}=s(e);return{x:Math.round(t),y:Math.round(o)}},worldToViewportRaw:s,worldDimToViewport:e=>{const{w:t,h:o}=i(e);return{w:Math.round(t),h:Math.round(o)}},worldDimToViewportRaw:i,viewportToWorld:e=>{const{x:t,y:o}=a(e);return{x:Math.round(t),y:Math.round(o)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:o}=r(e);return{w:Math.round(t),h:Math.round(o)}},viewportDimToWorldRaw:r}}function Wo(e=0,t=0){const o=document.createElement("canvas");return o.width=e,o.height=t,o}var Ho={createCanvas:Wo,loadImageToBitmap:async function(e){return new Promise((t=>{const o=new Image;o.onload=()=>{createImageBitmap(o).then(t)},o.src=e}))},resizeBitmap:async function(e,t,o){const n=Wo(t,o);return n.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,o),await createImageBitmap(n)},colorizedCanvas:function(e,t,o){const n=Wo(e.width,e.height),l=n.getContext("2d");return l.save(),l.drawImage(t,0,0),l.fillStyle=o,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(),n}};const Ko=ae("Debug.js");let Yo=0,qo=0;var Qo=e=>{Yo=performance.now(),qo=e},Zo=e=>{const t=performance.now(),o=t-Yo;o>qo&&Ko.log(e+": "+o),Yo=t};function Xo(e,t){const o=e.x-t.x,n=e.y-t.y;return Math.sqrt(o*o+n*n)}function Jo(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var en={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:Xo,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:Jo,rectMoved:function(e,t,o){return{x:e.x+t,y:e.y+o,w:e.w,h:e.h}},rectCenterDistance:function(e,t){return Xo(Jo(e),Jo(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const tn=ae("PuzzleGraphics.js");function on(e,t){const o=se.coordByPieceIdx(e,t);return{x:o.x*e.tileSize,y:o.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var nn={loadPuzzleBitmaps:async function(e){const t=await Ho.loadImageToBitmap(e.info.imageUrl),o=await Ho.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,o){tn.log("start createPuzzleTileBitmaps");const n=o.tileSize,l=o.tileMarginWidth,a=o.tileDrawSize,s=n/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const o=new Path2D,a={x:l,y:l},r=en.pointAdd(a,{x:n,y:0}),c=en.pointAdd(r,{x:0,y:n}),u=en.pointSub(c,{x:n,y:0});if(o.moveTo(a.x,a.y),0!==e.top)for(let n=0;nse.decodePiece(ln[e].puzzle.tiles[t]),bn=(e,t)=>wn(e,t).group,xn=(e,t)=>{const o=ln[e].puzzle.info;return 0===t||t===o.tilesX-1||t===o.tiles-o.tilesX||t===o.tiles-1},Cn=(e,t)=>{const o=ln[e].puzzle.info,n={x:(o.table.width-o.width)/2,y:(o.table.height-o.height)/2},l=function(e,t){const o=ln[e].puzzle.info,n=se.coordByPieceIdx(o,t),l=n.x*o.tileSize,a=n.y*o.tileSize;return{x:l,y:a}}(e,t);return en.pointAdd(n,l)},kn=(e,t)=>wn(e,t).pos,An=e=>{const t=$n(e),o=Ln(e),n=Math.round(t/4),l=Math.round(o/4);return{x:0-n,y:0-l,w:t+2*n,h:o+2*l}},Sn=(e,t)=>{const o=zn(e),n=wn(e,t);return{x:n.pos.x,y:n.pos.y,w:o,h:o}},Pn=(e,t)=>wn(e,t).z,Tn=(e,t)=>{for(const o of ln[e].puzzle.tiles){const e=se.decodePiece(o);if(e.owner===t)return e.idx}return-1},In=e=>ln[e].puzzle.info.tileDrawSize,zn=e=>ln[e].puzzle.info.tileSize,Dn=e=>ln[e].puzzle.data.maxGroup,En=e=>ln[e].puzzle.data.maxZ;function _n(e,t){const o=ln[e].puzzle.info,n=se.coordByPieceIdx(o,t);return[n.y>0?t-o.tilesX:-1,n.x0?t-1:-1]}const Mn=(e,t,o)=>{for(const n of t)vn(e,n,{z:o})},Vn=(e,t,o)=>{const n=kn(e,t);vn(e,t,{pos:en.pointAdd(n,o)})},Nn=(e,t,o)=>{const n=In(e),l=An(e),a=o;for(const s of t){const t=wn(e,s);t.pos.x+o.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+n,a.x)),t.pos.y+o.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+n,a.y))}for(const s of t)Vn(e,s,a)},On=(e,t)=>wn(e,t).owner,Un=(e,t)=>{for(const o of t)vn(e,o,{owner:-1,z:1})},Bn=(e,t,o)=>{for(const n of t)vn(e,n,{owner:o})};function Rn(e,t){const o=ln[e].puzzle.tiles,n=se.decodePiece(o[t]),l=[];if(n.group)for(const a of o){const e=se.decodePiece(a);e.group===n.group&&l.push(e.idx)}else l.push(n.idx);return l}const Gn=(e,t)=>{const o=sn(e,t);return o?o.points:0},$n=e=>ln[e].puzzle.info.table.width,Ln=e=>ln[e].puzzle.info.table.height;var Fn={setGame:function(e,t){ln[e]=t},exists:function(e){return!!ln[e]||!1},playerExists:dn,getActivePlayers:function(e,t){const o=t-30*V;return cn(e).filter((e=>e.ts>=o))},getIdlePlayers:function(e,t){const o=t-30*V;return cn(e).filter((e=>e.ts0))},addPlayer:function(e,t,o){dn(e,t)?yn(e,t,{ts:o}):rn(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,o))},getFinishedPiecesCount:mn,getPieceCount:un,getImageUrl:function(e){return ln[e].puzzle.info.imageUrl},setImageUrl:function(e,t){ln[e].puzzle.info.imageUrl=t},get:function(e){return ln[e]||null},getAllGames:function(){return Object.values(ln).sort(((e,t)=>hn(e.id)===hn(t.id)?t.puzzle.data.started-e.puzzle.data.started:hn(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const o=sn(e,t);return o?o.bgcolor:null},getPlayerColor:(e,t)=>{const o=sn(e,t);return o?o.color:null},getPlayerName:(e,t)=>{const o=sn(e,t);return o?o.name:null},getPlayerIndexById:an,getPlayerIdByIndex:function(e,t){return ln[e].players.length>t?se.decodePlayer(ln[e].players[t]).id:null},changePlayer:yn,setPlayer:rn,setPiece:function(e,t,o){ln[e].puzzle.tiles[t]=se.encodePiece(o)},setPuzzleData:function(e,t){ln[e].puzzle.data=t},getTableWidth:$n,getTableHeight:Ln,getPuzzle:e=>ln[e].puzzle,getRng:e=>ln[e].rng.obj,getPuzzleWidth:e=>ln[e].puzzle.info.width,getPuzzleHeight:e=>ln[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return ln[e].puzzle.tiles.map(se.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const o=Tn(e,t);return o<0?null:ln[e].puzzle.tiles[o]},getPieceDrawOffset:e=>ln[e].puzzle.info.tileDrawOffset,getPieceDrawSize:In,getFinalPiecePos:Cn,getStartTs:e=>ln[e].puzzle.data.started,getFinishTs:e=>ln[e].puzzle.data.finished,handleInput:function(e,t,o,n,l){const a=ln[e].puzzle,s=function(e,t){return t in ln[e].evtInfos?ln[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([oo,a.data])},d=t=>{i.push([no,se.encodePiece(wn(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const o=sn(e,t);o&&i.push([lo,se.encodePlayer(o)])},p=o[0];if(p===Yt){const l=o[1];yn(e,t,{bgcolor:l,ts:n}),u()}else if(p===qt){const l=o[1];yn(e,t,{color:l,ts:n}),u()}else if(p===Qt){const l=`${o[1]}`.substr(0,16);yn(e,t,{name:l,ts:n}),u()}else if(p===Lt){const l=o[1],a=o[2],s=sn(e,t);if(s){const o=s.x-l,i=s.y-a;yn(e,t,{ts:n,x:o,y:i}),u()}}else if(p===Ft){const l={x:o[1],y:o[2]};yn(e,t,{d:1,ts:n}),u(),s._last_mouse_down=l;const a=((e,t)=>{const o=ln[e].puzzle.info,n=ln[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const o=En(e)+1;fn(e,{maxZ:o}),r();const n=Rn(e,a);Mn(e,n,En(e)),Bn(e,n,t),c(n)}s._last_mouse=l}else if(p===Wt){const l=o[1],a=o[2],i={x:l,y:a};if(null===s._last_mouse_down)yn(e,t,{x:l,y:a,ts:n}),u();else{const o=Tn(e,t);if(o>=0){yn(e,t,{x:l,y:a,ts:n}),u();const r=Rn(e,o);let d=en.pointInBounds(i,An(e))&&en.pointInBounds(s._last_mouse_down,An(e));for(const t of r){const o=Sn(e,t);if(en.pointInBounds(i,o)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,o=a-s._last_mouse_down.y;Nn(e,r,{x:t,y:o}),c(r)}}else yn(e,t,{ts:n}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===jt){const i={x:o[1],y:o[2]},p=0;s._last_mouse_down=null;const g=Tn(e,t);if(g>=0){const o=Rn(e,g);Bn(e,o,0),c(o);const s=kn(e,g),i=Cn(e,g);let h=!1;if(gn(e)===ee.REAL){for(const t of o)if(xn(e,t)){h=!0;break}}else h=!0;if(h&&en.pointDistance(i,s){const l=ln[e].puzzle.info;if(o<0)return!1;if(((e,t,o)=>{const n=bn(e,t),l=bn(e,o);return!(!n||n!==l)})(e,t,o))return!1;const a=kn(e,t),s=en.pointAdd(kn(e,o),{x:n[0]*l.tileSize,y:n[1]*l.tileSize});if(en.pointDistance(a,s){const n=ln[e].puzzle.tiles,l=bn(e,t),a=bn(e,o);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(fn(e,{maxGroup:Dn(e)+1}),r(),s=Dn(e));if(vn(e,t,{group:s}),d(t),vn(e,o,{group:s}),d(o),i.length>0)for(const r of n){const t=se.decodePiece(r);i.includes(t.group)&&(vn(e,t.idx,{group:s}),d(t.idx))}})(e,t,o),l=Rn(e,t),((e,t)=>-1===On(e,t))(e,o))Un(e,l);else{const t=((e,t)=>{let o=0;for(const n of t){const t=Pn(e,n);t>o&&(o=t)}return o})(e,l);Mn(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Rn(e,g)){const n=_n(e,t);if(o(e,t,n[0],[0,1])||o(e,t,n[1],[-1,0])||o(e,t,n[2],[0,-1])||o(e,t,n[3],[1,0])){a=!0;break}}if(a&&pn(e)===Q.ANY){const o=Gn(e,t)+1;yn(e,t,{d:p,ts:n,points:o}),u()}else yn(e,t,{d:p,ts:n}),u();a&&gn(e)===ee.REAL&&mn(e)===un(e)&&(fn(e,{finished:n}),r()),a&&l&&l(t)}}else yn(e,t,{d:p,ts:n}),u();s._last_mouse=i}else if(p===Ht){const l=o[1],a=o[2];yn(e,t,{x:l,y:a,ts:n}),u(),s._last_mouse={x:l,y:a}}else if(p===Kt){const l=o[1],a=o[2];yn(e,t,{x:l,y:a,ts:n}),u(),s._last_mouse={x:l,y:a}}else yn(e,t,{ts:n}),u();return function(e,t,o){ln[e].evtInfos[t]=o}(e,t,s),i}};let jn=-10,Wn=20,Hn=2,Kn=15;class Yn{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=jn+Math.random()*Wn,this.vy=-1*(Hn+Math.random()*Kn),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let o=0;o{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Hn=t/2,Kn=t-Hn;const o=1/4*this.canvas.width/(t/2);jn=-o,Wn=2*o}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Yn(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Yn(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 o=[];for(;this.particles.length>0;){const e=this.particles.shift();if(!e)break;e.update(),e.alive&&o.push(e)}this.particles=o}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(!y[t]){const o=e.d?r:d;if(e.color){const n=e.d?c:u;y[t]=await createImageBitmap(Ho.colorizedCanvas(o,n,e.color))}else y[t]=o}return y[t]},v=function(e,t){return t.width=window.innerWidth,t.height=window.innerHeight,e.appendChild(t),window.addEventListener("resize",(()=>{t.width=window.innerWidth,t.height=window.innerHeight,ol=!0})),t}(l,Ho.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};fo.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const o=await fo.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...o.log),0===o.log.length&&(w.final=!0),o};let x=()=>0;const C=async()=>{if("play"===n){const n=await fo.connect(o,e,t),l=se.decodeGame(n);Fn.setGame(l.id,l),x=()=>N()}else{if("replay"!==n)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const o=se.decodeGame(t.game);Fn.setGame(o.id,o),w.lastRealTs=N(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,x=()=>w.lastGameTs}}ol=!0};await C();const k=Fn.getPieceDrawOffset(e),A=Fn.getPieceDrawSize(e),S=Fn.getPuzzleWidth(e),P=Fn.getPuzzleHeight(e),T=Fn.getTableWidth(e),I=Fn.getTableHeight(e),z={x:(T-S)/2,y:(I-P)/2},D={w:S,h:P},E={w:A,h:A},_=await nn.loadPuzzleBitmaps(Fn.getPuzzle(e)),V=new Qn(v,Fn.getRng(e));V.init();const O=v.getContext("2d");v.classList.add("loaded");const U=jo();U.move(-(T-v.width)/2,-(I-v.height)/2);const B=function(e,t,o,n){let l=[],a=!0,s=!1,i=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const n=o.viewportToWorld({x:e,y:t});return[n.x,n.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Ft,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([jt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([Wt,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),o.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Ht:Kt;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([Zt]),"replay"===n&&("KeyI"===e.code&&v([eo]),"KeyO"===e.code&&v([to]),"KeyP"===e.code&&v([Jt])),"KeyF"===e.code&&(el=!el,ol=!0),"KeyG"===e.code&&(tl=!tl,ol=!0),"KeyM"===e.code&&v([Xt]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const n=(p?24:12)*Math.sqrt(o.getCurrentZoom()),l=o.viewportDimToWorld({w:e*n,h:t*n});v([Lt,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(o.canZoom("in")){const e=f||m();v([Ht,...e])}}else if(u&&o.canZoom("out")){const e=f||m();v([Kt,...e])}},setHotkeys:e=>{a=e}}}(v,window,U,n),R=Fn.getImageUrl(e),G=()=>{const t=Fn.getStartTs(e),o=Fn.getFinishTs(e),n=x();a.setFinished(!!o),a.setDuration((o||n)-t)};G(),a.setPiecesDone(Fn.getFinishedPiecesCount(e)),a.setPiecesTotal(Fn.getPieceCount(e));const $=x();a.setActivePlayers(Fn.getActivePlayers(e,$)),a.setIdlePlayers(Fn.getIdlePlayers(e,$));const L=!!Fn.getFinishTs(e);let F=L;const j=()=>F&&!L,W=()=>{const e=localStorage.getItem("sound_volume");if(null===e)return 100;const t=parseInt(e,10);return isNaN(t)?100:t},H=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},K=()=>{const e=W();i.volume=e/100,i.play()},Y=()=>"replay"===n?localStorage.getItem("bg_color")||"#222222":Fn.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",q=()=>"replay"===n?localStorage.getItem("player_color")||"#ffffff":Fn.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let Q="",Z="",X=!1;const J=e=>{X=e;const[t,o]=e?[Q,"grab"]:[Z,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${o}`},ee=e=>{Q=Ho.colorizedCanvas(r,c,e).toDataURL(),Z=Ho.colorizedCanvas(d,u,e).toDataURL(),J(X)};ee(q());const te=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},oe=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,te())},le=()=>{w.paused=!w.paused,te()},ae=[];let ie;let re;if("play"===n?ae.push(setInterval((()=>{G()}),1e3)):"replay"===n&&te(),"play"===n)fo.onServerChange((o=>{o[0],o[1],o[2];const n=o[3];for(const[l,a]of n)switch(l){case lo:{const o=se.decodePlayer(a);o.id!==t&&(Fn.setPlayer(e,o.id,o),ol=!0)}break;case no:{const t=se.decodePiece(a);Fn.setPiece(e,t.idx,t),ol=!0}break;case oo:Fn.setPuzzleData(e,a),ol=!0}F=!!Fn.getFinishTs(e)}));else if("replay"===n){const t=(t,o)=>{const n=t;if(n[0]===Rt){const t=n[1];return Fn.addPlayer(e,t,o),!0}if(n[0]===Gt){const t=Fn.getPlayerIdByIndex(e,n[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Fn.addPlayer(e,t,o),!0}if(n[0]===$t){const t=Fn.getPlayerIdByIndex(e,n[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=n[2];return Fn.handleInput(e,t,l,o),!0}return!1};let o=w.lastGameTs;const n=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=N();if(w.paused)return w.lastRealTs=l,void(ie=setTimeout(n,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const n=w.log[w.logPointer],l=o+n[n.length-1],a=w.log[e],i=a[a.length-1],r=l+i;if(r>s){w.skipNonActionPhases&&s+500*M{let t=!1;const o=e.fps||60,n=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/o,r=n*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/n),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{B.createKeyEvents();for(const o of B.consumeAll())if("play"===n){const n=o[0];if(n===Lt){const e=o[1],t=o[2],n=U.worldDimToViewport({w:e,h:t});ol=!0,U.move(n.w,n.h)}else if(n===Wt){if(de&&!Fn.getFirstOwnedPiece(e,t)){const e={x:o[1],y:o[2]},t=U.worldToViewport(e),n=Math.round(t.x-de.x),l=Math.round(t.y-de.y);ol=!0,U.move(n,l),de=t}}else if(n===qt)ee(o[1]);else if(n===Ft){const e={x:o[1],y:o[2]};de=U.worldToViewport(e),J(!0)}else if(n===jt)de=null,J(!1);else if(n===Ht){const e={x:o[1],y:o[2]};ol=!0,U.zoom("in",U.worldToViewport(e))}else if(n===Kt){const e={x:o[1],y:o[2]};ol=!0,U.zoom("out",U.worldToViewport(e))}else n===Zt?a.togglePreview():n===Xt&&a.toggleSoundsEnabled();const l=x();Fn.handleInput(e,t,o,l,(e=>{H()&&K()})).length>0&&(ol=!0),fo.sendClientEvent(o)}else if("replay"===n){const e=o[0];if(e===Jt)le();else if(e===to)ne();else if(e===eo)oe();else if(e===Lt){const e=o[1],t=o[2];ol=!0,U.move(e,t)}else if(e===Wt){if(de){const e={x:o[1],y:o[2]},t=U.worldToViewport(e),n=Math.round(t.x-de.x),l=Math.round(t.y-de.y);ol=!0,U.move(n,l),de=t}}else if(e===qt)ee(o[1]);else if(e===Ft){const e={x:o[1],y:o[2]};de=U.worldToViewport(e),J(!0)}else if(e===jt)de=null,J(!1);else if(e===Ht){const e={x:o[1],y:o[2]};ol=!0,U.zoom("in",U.worldToViewport(e))}else if(e===Kt){const e={x:o[1],y:o[2]};ol=!0,U.zoom("out",U.worldToViewport(e))}else e===Zt&&a.togglePreview()}F=!!Fn.getFinishTs(e),j()&&(V.update(),ol=!0)},render:async()=>{if(!ol)return;const o=x();let l,s,i;window.DEBUG&&Qo(0),O.fillStyle=Y(),O.fillRect(0,0,v.width,v.height),window.DEBUG&&Zo("clear done"),l=U.worldToViewportRaw(z),s=U.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&Zo("board done");const r=Fn.getPiecesSortedByZIndex(e);window.DEBUG&&Zo("get tiles done"),s=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?el:tl)&&(i=_[e.idx],l=U.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&Zo("tiles done");const d=[];for(const a of Fn.getActivePlayers(e,o))c=a,("replay"===n||c.id!==t)&&(i=await f(a),l=U.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,n]of d)O.fillText(e,t,n);window.DEBUG&&Zo("players done"),a.setActivePlayers(Fn.getActivePlayers(e,o)),a.setIdlePlayers(Fn.getIdlePlayers(e,o)),a.setPiecesDone(Fn.getFinishedPiecesCount(e)),window.DEBUG&&Zo("HUD done"),j()&&V.render(),ol=!1}}),{setHotkeys:e=>{B.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),B.addEvent([Yt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),B.addEvent([qt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),B.addEvent([Qt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},onSoundsVolumeChange:e=>{Zn.info("vol changed",e),localStorage.setItem("sound_volume",`${e}`),K()},replayOnSpeedUp:oe,replayOnSpeedDown:ne,replayOnPauseToggle:le,replayOnSkipToggle:()=>{w.skipNonActionPhases=!w.skipNonActionPhases},previewImageUrl:R,player:{background:Y(),color:q(),name:"replay"===n?localStorage.getItem("player_name")||"#ffffff":Fn.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:H(),soundsVolume:W()},disconnect:fo.disconnect,connect:C,unload:()=>{ae.forEach((e=>{clearInterval(e)})),ie&&clearTimeout(ie),re&&re.stop()}}}var ll=e({name:"game",components:{PuzzleStatus:Ct,Scores:ft,SettingsOverlay:At,PreviewOverlay:Mt,ConnectionOverlay:vo,HelpOverlay:Ao},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await nl(`${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},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 al={id:"game"},sl={class:"menu"},il={class:"tabs"},rl=i("🧩 Puzzles");ll.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",al,[p(o(u,{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"]),[[C,"settings"===e.overlay]]),p(o(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(o(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),o(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),o(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),o("div",sl,[o("div",il,[o(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:n((()=>[rl])),_:1}),o("div",{class:"opener",onClick:l[5]||(l[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),o("div",{class:"opener",onClick:l[6]||(l[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),o("div",{class:"opener",onClick:l[7]||(l[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),o(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var dl=e({name:"replay",components:{PuzzleStatus:Ct,Scores:ft,SettingsOverlay:At,PreviewOverlay:Mt,HelpOverlay:Ao},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,skipNoAction:!0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},replayOnSkipToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await nl(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 cl={id:"replay"},ul=i("Skip no action phases: "),pl={class:"menu"},gl={class:"tabs"},hl=i("🧩 Puzzles");dl.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",cl,[p(o(g,{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"]),[[C,"settings"===e.overlay]]),p(o(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(o(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),o(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:n((()=>[o("div",null,[o("div",null,r(e.replayText),1),o("div",null,[o("label",null,[ul,p(o("input",{type:"checkbox","onUpdate:modelValue":l[5]||(l[5]=t=>e.skipNoAction=t),onChange:l[6]||(l[6]=t=>e.g.replayOnSkipToggle())},null,544),[[x,e.skipNoAction]])])]),o("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnSpeedUp())},"⏫"),o("button",{class:"btn",onClick:l[8]||(l[8]=t=>e.g.replayOnSpeedDown())},"⏬"),o("button",{class:"btn",onClick:l[9]||(l[9]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),o("div",pl,[o("div",gl,[o(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:n((()=>[hl])),_:1}),o("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("preview",!1))},"🖼️ Preview"),o("div",{class:"opener",onClick:l[11]||(l[11]=t=>e.toggle("settings",!0))},"🛠️ Settings"),o("div",{class:"opener",onClick:l[12]||(l[12]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),o(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const o=k({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:it},{name:"game",path:"/g/:id",component:ll},{name:"replay",path:"/replay/:id",component:dl}]});o.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const n=S(P);n.config.globalProperties.$config=t,n.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=se.uniqId(),localStorage.setItem("ID",e)),e}(),n.use(o),n.mount("#app")})(); +import{d as e,c as t,a as o,w as n,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as x,s as C,u as k,x as A,y as S}from"./vendor.684f7bc8.js";var P=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const T={id:"app"},I={key:0,class:"nav"},z=i("Index"),D=i("New game");P.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",T,[e.showNav?(s(),t("ul",I,[o("li",null,[o(p,{class:"btn",to:{name:"index"}},{default:n((()=>[z])),_:1})]),o("li",null,[o(p,{class:"btn",to:{name:"new-game"}},{default:n((()=>[D])),_:1})])])):l("",!0),o(g)])};const E=864e5,_=e=>{const t=Math.floor(e/E);e%=E;const o=Math.floor(e/36e5);e%=36e5;const n=Math.floor(e/6e4);e%=6e4;return`${t}d ${o}h ${n}m ${Math.floor(e/1e3)}s`};var M=1,V=1e3,N=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>_(t-e),B=_,U=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 o=t?"🏁":"⏳",n=e,l=t||N();return`${o} ${O(n,l)}`}}});const R={class:"game-info-text"},G=o("br",null,null,-1),$=o("br",null,null,-1),L=o("br",null,null,-1),F=i(" ↪️ Watch replay ");U.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[o(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:n((()=>[o("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),G,i(" 👥 "+r(e.game.players),1),$,i(" "+r(e.time(e.game.started,e.game.finished)),1),L])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:n((()=>[F])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:U},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 W=o("h1",null,"Running games",-1),H=o("h1",null,"Finished games",-1);j.render=function(e,n,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(s(!0),t(d,null,c(e.gamesRunning,((e,n)=>(s(),t("div",{class:"game-teaser-wrap",key:n},[o(p,{game:e},null,8,["game"])])))),128)),H,(s(!0),t(d,null,c(e.gamesFinished,((e,n)=>(s(),t("div",{class:"game-teaser-wrap",key:n},[o(p,{game:e},null,8,["game"])])))),128))])};var K=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});K.render=function(e,n,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:n[2]||(n[2]=(...t)=>e.onClick&&e.onClick(...t))},[o("div",{class:"btn edit",onClick:n[1]||(n[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y,q,Q,Z,X,J,ee,te,oe=e({name:"image-library",components:{ImageTeaser:K},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});oe.render=function(e,o,n,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((o,n)=>(s(),t(u,{image:o,onClick:t=>e.imageClicked(o),onEditClick:t=>e.imageEditClicked(o),key:n},null,8,["image","onClick","onEditClick"])))),128))])},(q=Y||(Y={}))[q.Flat=0]="Flat",q[q.Out=1]="Out",q[q.In=-1]="In",(Z=Q||(Q={}))[Z.FINAL=0]="FINAL",Z[Z.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class ne{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let o=0;o<=t.length-2;o++){const e=this.random(o,t.length-1),n=t[o];t[o]=t[e],t[e]=n}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new ne(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const o=`${e}`;return o.length>=t.length?o:t.substr(0,t.length-o.length)+o},ae=(...e)=>{const t=t=>(...o)=>{const n=new Date,l=le(n.getHours(),"00"),a=le(n.getMinutes(),"00"),s=le(n.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...o)};return{log:t("log"),error:t("error"),info:t("info")}};var se={hash:e=>{let t=0;for(let o=0;o{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",ne.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Q.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:ne.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const o=e.width/e.tileSize;return{x:t%o,y:Math.floor(t/o)}},asQueryArgs:function(e){const t=[];for(const o in e){const n=[o,e[o]].map(encodeURIComponent);t.push(n.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const ie={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};ie.render=function(e,o,n,l,a,i){return s(),t("div",{style:i.style,title:n.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-a4fa5e7e");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,n,a,i,u,m)=>(s(),t("div",null,[p(o("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":n[1]||(n[1]=t=>e.input=t),placeholder:"Plants, People",onChange:n[2]||(n[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:n[3]||(n[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:n[4]||(n[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ce,[o("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((o,n)=>(s(),t("li",{key:n,class:{active:n===e.autocomplete.idx},onClick:t=>e.addVal(o)},r(o),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((o,n)=>(s(),t("span",{key:n,class:"bit",onClick:t=>e.rm(o)},r(o)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-a4fa5e7e";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{autocompleteTags:{type:Function},uploadProgress:{type:Number},uploading:{type:String}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{uploadProgressPercent(){return this.uploadProgress?Math.round(100*this.uploadProgress):0},canPostToGallery(){return!this.uploading&&!(!this.previewUrl||!this.file)},canSetupGameClick(){return!this.uploading&&!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const o=null==(t=e.dataTransfer)?void 0:t.items;if(!o||0===o.length)return null;const n=o[0];return n.type.startsWith("image/")?n:null},onFileSelect(e){const t=e.target;if(!t.files)return;const o=t.files[0];o&&this.preview(o)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const o=t.getAsFile();return!!o&&(this.file=o,this.preview(o),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=o("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},ve=o("span",{class:"btn"},"Upload File",-1),we={class:"area-settings"},be=o("td",null,[o("label",null,"Title")],-1),xe=o("tr",null,[o("td",{colspan:"2"},[o("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ce=o("td",null,[o("label",null,"Tags")],-1),ke={class:"area-buttons"},Ae=i("🖼️ Post to gallery"),Se=i("🧩 Post to gallery "),Pe=o("br",null,null,-1),Te=i(" + set up game");ge.render=function(e,n,l,c,h,m){const y=a("responsive-image"),f=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:n[11]||(n[11]=t=>e.$emit("bgclick"))},[o("div",{class:"overlay-content",onClick:n[10]||(n[10]=u((()=>{}),["stop"]))},[o("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:n[3]||(n[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:n[4]||(n[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:n[5]||(n[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(s(),t("div",me,[o("span",{class:"remove btn",onClick:n[1]||(n[1]=t=>e.previewUrl="")},"X"),o(y,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",ye,[o("label",fe,[o("input",{type:"file",style:{display:"none"},onChange:n[2]||(n[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ve])]))],34),o("div",we,[o("table",null,[o("tr",null,[be,o("td",null,[p(o("input",{type:"text","onUpdate:modelValue":n[6]||(n[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),xe,o("tr",null,[Ce,o("td",null,[o(f,{modelValue:e.tags,"onUpdate:modelValue":n[7]||(n[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),o("div",ke,[o("button",{class:"btn",disabled:!e.canPostToGallery,onClick:n[8]||(n[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},["postToGallery"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Ae],64))],8,["disabled"]),o("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:n[9]||(n[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},["setupGame"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Se,Pe,Te],64))],8,["disabled"])])])])};var Ie=e({name:"edit-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const ze={class:"area-image"},De={class:"has-image"},Ee={class:"area-settings"},_e=o("td",null,[o("label",null,"Title")],-1),Me=o("tr",null,[o("td",{colspan:"2"},[o("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ve=o("td",null,[o("label",null,"Tags")],-1),Ne={class:"area-buttons"};Ie.render=function(e,n,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:n[5]||(n[5]=t=>e.$emit("bgclick"))},[o("div",{class:"overlay-content",onClick:n[4]||(n[4]=u((()=>{}),["stop"]))},[o("div",ze,[o("div",De,[o(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),o("div",Ee,[o("table",null,[o("tr",null,[_e,o("td",null,[p(o("input",{type:"text","onUpdate:modelValue":n[1]||(n[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Me,o("tr",null,[Ve,o("td",null,[o(h,{modelValue:e.tags,"onUpdate:modelValue":n[2]||(n[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),o("div",Ne,[o("button",{class:"btn",onClick:n[3]||(n[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Oe=e({name:"new-game-dialog",components:{ResponsiveImage:ie},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Q.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Be={class:"area-image"},Ue={class:"has-image"},Re={key:0,class:"image-title"},Ge={key:0,class:"image-title-title"},$e={key:1,class:"image-title-dim"},Le={class:"area-settings"},Fe=o("td",null,[o("label",null,"Pieces")],-1),je=o("td",null,[o("label",null,"Scoring: ")],-1),We=i(" Any (Score when pieces are connected to each other or on final location)"),He=o("br",null,null,-1),Ke=i(" Final (Score when pieces are put to their final location)"),Ye=o("td",null,[o("label",null,"Shapes: ")],-1),qe=i(" Normal"),Qe=o("br",null,null,-1),Ze=i(" Any (flat pieces can occur anywhere)"),Xe=o("br",null,null,-1),Je=i(" Flat (all pieces flat on all sides)"),et=o("td",null,[o("label",null,"Snapping: ")],-1),tt=i(" Normal (pieces snap to final destination and to each other)"),ot=o("br",null,null,-1),nt=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),lt={class:"area-buttons"};Oe.render=function(e,n,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:n[11]||(n[11]=t=>e.$emit("bgclick"))},[o("div",{class:"overlay-content",onClick:n[10]||(n[10]=u((()=>{}),["stop"]))},[o("div",Be,[o("div",Ue,[o(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title||e.image.width||e.image.height?(s(),t("div",Re,[e.image.title?(s(),t("span",Ge,'"'+r(e.image.title)+'"',1)):l("",!0),e.image.width||e.image.height?(s(),t("span",$e,"("+r(e.image.width)+" ✕ "+r(e.image.height)+")",1)):l("",!0)])):l("",!0)]),o("div",Le,[o("table",null,[o("tr",null,[Fe,o("td",null,[p(o("input",{type:"text","onUpdate:modelValue":n[1]||(n[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),o("tr",null,[je,o("td",null,[o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[2]||(n[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),We]),He,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[3]||(n[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),Ke])])]),o("tr",null,[Ye,o("td",null,[o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[4]||(n[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),qe]),Qe,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[5]||(n[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ze]),Xe,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[6]||(n[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),Je])])]),o("tr",null,[et,o("td",null,[o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[7]||(n[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),tt]),ot,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[8]||(n[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),nt])])])])]),o("div",lt,[o("button",{class:"btn",disabled:!e.canStartNewGame,onClick:n[9]||(n[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};const at=async(e,t,o)=>new Promise(((n,l)=>{const a=new window.XMLHttpRequest;a.open(e,t,!0),a.withCredentials=!0;for(const e in o.headers||{})a.setRequestHeader(e,o.headers[e]);a.addEventListener("load",(function(e){n({status:this.status,text:this.responseText,json:async()=>JSON.parse(this.responseText)})})),a.addEventListener("error",(function(e){l(new Error("xhr error"))})),a.upload&&o.onUploadProgress&&a.upload.addEventListener("progress",(function(e){o.onUploadProgress&&o.onUploadProgress(e)})),a.send(o.body)}));var st=(e,t)=>at("post",e,t),it=e({components:{ImageLibrary:oe,NewImageDialog:ge,EditImageDialog:Ie,NewGameDialog:Oe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:"",uploading:"",uploadProgress:0}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((o=>!t.includes(o.title)&&o.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${se.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){this.uploadProgress=0;const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const o=await st("/api/upload",{body:t,onUploadProgress:e=>{this.uploadProgress=e.loaded/e.total}});return this.uploadProgress=1,await o.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){this.uploading="postToGallery",await this.uploadImage(e),this.uploading="",this.dialog="",await this.loadImages()},async setupGameClick(e){this.uploading="setupGame";const t=await this.uploadImage(e);this.uploading="",this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const rt={class:"upload-image-teaser"},dt=o("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),ct={key:0},ut=i(" Tags: "),pt=i(" Sort by: "),gt=o("option",{value:"date_desc"},"Newest first",-1),ht=o("option",{value:"date_asc"},"Oldest first",-1),mt=o("option",{value:"alpha_asc"},"A-Z",-1),yt=o("option",{value:"alpha_desc"},"Z-A",-1);it.render=function(e,n,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[o("div",rt,[o("div",{class:"btn btn-big",onClick:n[1]||(n[1]=t=>e.dialog="new-image")},"Upload your image"),dt]),o("div",null,[e.tags.length>0?(s(),t("label",ct,[ut,(s(!0),t(d,null,c(e.relevantTags,((o,n)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(o.slug)}],key:n,onClick:t=>e.toggleTag(o)},r(o.title)+" ("+r(o.total)+")",11,["onClick"])))),128))])):l("",!0),o("label",null,[pt,p(o("select",{"onUpdate:modelValue":n[2]||(n[2]=t=>e.filters.sort=t),onChange:n[3]||(n[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[gt,ht,mt,yt],544),[[w,e.filters.sort]])])]),o(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:n[4]||(n[4]=t=>e.dialog=""),uploadProgress:e.uploadProgress,uploading:e.uploading,onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","uploadProgress","uploading","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:n[5]||(n[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:n[6]||(n[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ft=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"},wt=o("div",null,"Scores",-1),bt=o("td",null,"⚡",-1),xt=o("td",null,"💤",-1);ft.render=function(e,n,l,a,i,u){return s(),t("div",vt,[wt,o("table",null,[(s(!0),t(d,null,c(e.actives,((e,n)=>(s(),t("tr",{key:n,style:{color:e.color}},[bt,o("td",null,r(e.name),1),o("td",null,r(e.points),1)],4)))),128)),(s(!0),t(d,null,c(e.idles,((e,n)=>(s(),t("tr",{key:n,style:{color:e.color}},[xt,o("td",null,r(e.name),1),o("td",null,r(e.points),1)],4)))),128))])])};var Ct=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 B(this.duration)}}});const kt={class:"timer"};Ct.render=function(e,n,l,a,i,d){return s(),t("div",kt,[o("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),o("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var At=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const St=m();y("data-v-a1d1c822");const Pt=o("td",null,[o("label",null,"Background: ")],-1),Tt=o("td",null,[o("label",null,"Color: ")],-1),It=o("td",null,[o("label",null,"Name: ")],-1),zt=o("td",null,[o("label",null,"Sounds: ")],-1),Dt=o("td",null,[o("label",null,"Sounds Volume: ")],-1),Et={class:"sound-volume"};f();const _t=St(((e,n,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:n[9]||(n[9]=t=>e.$emit("bgclick"))},[o("table",{class:"overlay-content settings",onClick:n[8]||(n[8]=u((()=>{}),["stop"]))},[o("tr",null,[Pt,o("td",null,[p(o("input",{type:"color","onUpdate:modelValue":n[1]||(n[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),o("tr",null,[Tt,o("td",null,[p(o("input",{type:"color","onUpdate:modelValue":n[2]||(n[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),o("tr",null,[It,o("td",null,[p(o("input",{type:"text",maxLength:"16","onUpdate:modelValue":n[3]||(n[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),o("tr",null,[zt,o("td",null,[p(o("input",{type:"checkbox","onUpdate:modelValue":n[4]||(n[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[x,e.modelValue.soundsEnabled]])])]),o("tr",null,[Dt,o("td",Et,[o("span",{onClick:n[5]||(n[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),o("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:n[6]||(n[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),o("span",{onClick:n[7]||(n[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])])])]))));At.render=_t,At.__scopeId="data-v-a1d1c822";var Mt=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const Vt={class:"preview"};Mt.render=function(e,n,l,a,i,r){return s(),t("div",{class:"overlay",onClick:n[1]||(n[1]=t=>e.$emit("bgclick"))},[o("div",Vt,[o("div",{class:"img",style:e.previewStyle},null,4)])])};var Nt=1,Ot=4,Bt=2,Ut=3,Rt=2,Gt=4,$t=3,Lt=9,Ft=1,jt=2,Wt=3,Ht=4,Kt=5,Yt=6,qt=7,Qt=8,Zt=10,Xt=11,Jt=12,eo=13,to=14,oo=1,no=2,lo=3;const ao=ae("Communication.js");let so,io=[],ro=e=>{io.push(e)},co=[],uo=e=>{co.push(e)};let po=0;const go=e=>{po!==e&&(po=e,uo(e))};function ho(e){if(2===po)try{so.send(JSON.stringify(e))}catch(t){ao.info("unable to send message.. maybe because ws is invalid?")}}let mo,yo;var fo={connect:function(e,t,o){return mo=0,yo={},go(3),new Promise((n=>{so=new WebSocket(e,o+"|"+t),so.onopen=()=>{go(2),ho([Ut])},so.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Ot){const e=t[1];n(e)}else{if(l!==Nt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],n=t[2];if(e===o&&yo[n])return void delete yo[n];ro(t)}}},so.onerror=()=>{throw go(1),"[ 2021-05-15 onerror ]"},so.onclose=e=>{4e3===e.code||1001===e.code?go(4):go(1)}}))},requestReplayData:async function(e,t){const o={gameId:e,offset:t},n=await fetch(`/api/replay-data${se.asQueryArgs(o)}`);return await n.json()},disconnect:function(){so&&so.close(4e3),mo=0,yo={}},sendClientEvent:function(e){mo++,yo[mo]=e,ho([Bt,mo,yo[mo]])},onServerChange:function(e){ro=e;for(const t of io)ro(t);io=[]},onConnectionStateChange:function(e){uo=e;for(const t of co)uo(t);co=[]},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},vo=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===fo.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===fo.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const wo={key:0,class:"overlay connection-lost"},bo={key:0,class:"overlay-content"},xo=o("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Co={key:1,class:"overlay-content"},ko=o("div",null,"Connecting...",-1);vo.render=function(e,n,a,i,r,d){return e.show?(s(),t("div",wo,[e.lostConnection?(s(),t("div",bo,[xo,o("span",{class:"btn",onClick:n[1]||(n[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",Co,[ko])):l("",!0)])):l("",!0)};var Ao=e({name:"help-overlay",emits:{bgclick:null}});const So=o("tr",null,[o("td",null,"⬆️ Move up:"),o("td",null,[o("div",null,[o("kbd",null,"W"),i("/"),o("kbd",null,"↑"),i("/🖱️")])])],-1),Po=o("tr",null,[o("td",null,"⬇️ Move down:"),o("td",null,[o("div",null,[o("kbd",null,"S"),i("/"),o("kbd",null,"↓"),i("/🖱️")])])],-1),To=o("tr",null,[o("td",null,"⬅️ Move left:"),o("td",null,[o("div",null,[o("kbd",null,"A"),i("/"),o("kbd",null,"←"),i("/🖱️")])])],-1),Io=o("tr",null,[o("td",null,"➡️ Move right:"),o("td",null,[o("div",null,[o("kbd",null,"D"),i("/"),o("kbd",null,"→"),i("/🖱️")])])],-1),zo=o("tr",null,[o("td"),o("td",null,[o("div",null,[i("Move faster by holding "),o("kbd",null,"Shift")])])],-1),Do=o("tr",null,[o("td",null,"🔍+ Zoom in:"),o("td",null,[o("div",null,[o("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Eo=o("tr",null,[o("td",null,"🔍- Zoom out:"),o("td",null,[o("div",null,[o("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),_o=o("tr",null,[o("td",null,"🖼️ Toggle preview:"),o("td",null,[o("div",null,[o("kbd",null,"Space")])])],-1),Mo=o("tr",null,[o("td",null,"🧩✔️ Toggle fixed pieces:"),o("td",null,[o("div",null,[o("kbd",null,"F")])])],-1),Vo=o("tr",null,[o("td",null,"🧩❓ Toggle loose pieces:"),o("td",null,[o("div",null,[o("kbd",null,"G")])])],-1),No=o("tr",null,[o("td",null,"🔉 Toggle sounds:"),o("td",null,[o("div",null,[o("kbd",null,"M")])])],-1),Oo=o("tr",null,[o("td",null,"⏫ Speed up (replay):"),o("td",null,[o("div",null,[o("kbd",null,"I")])])],-1),Bo=o("tr",null,[o("td",null,"⏬ Speed down (replay):"),o("td",null,[o("div",null,[o("kbd",null,"O")])])],-1),Uo=o("tr",null,[o("td",null,"⏸️ Pause (replay):"),o("td",null,[o("div",null,[o("kbd",null,"P")])])],-1);Ao.render=function(e,n,l,a,i,r){return s(),t("div",{class:"overlay transparent",onClick:n[2]||(n[2]=t=>e.$emit("bgclick"))},[o("table",{class:"overlay-content help",onClick:n[1]||(n[1]=u((()=>{}),["stop"]))},[So,Po,To,Io,zo,Do,Eo,_o,Mo,Vo,No,Oo,Bo,Uo])])};var Ro=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Go=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),$o=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Lo=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Fo=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function jo(){let e=0,t=0,o=1;const n=(n,l)=>{e+=n/o,t+=l/o},l=e=>{const t=o+.05*o*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},a=n=>({x:n.x/o-e,y:n.y/o-t}),s=n=>({x:(n.x+e)*o,y:(n.y+t)*o}),i=e=>({w:e.w*o,h:e.h*o}),r=e=>({w:e.w/o,h:e.h/o});return{getCurrentZoom:()=>o,move:n,canZoom:e=>o!=l(e),zoom:(e,t)=>((e,t)=>{if(o==e)return!1;const l=1-o/e;return n(-t.x*l,-t.y*l),o=e,!0})(l(e),t),worldToViewport:e=>{const{x:t,y:o}=s(e);return{x:Math.round(t),y:Math.round(o)}},worldToViewportRaw:s,worldDimToViewport:e=>{const{w:t,h:o}=i(e);return{w:Math.round(t),h:Math.round(o)}},worldDimToViewportRaw:i,viewportToWorld:e=>{const{x:t,y:o}=a(e);return{x:Math.round(t),y:Math.round(o)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:o}=r(e);return{w:Math.round(t),h:Math.round(o)}},viewportDimToWorldRaw:r}}function Wo(e=0,t=0){const o=document.createElement("canvas");return o.width=e,o.height=t,o}var Ho={createCanvas:Wo,loadImageToBitmap:async function(e){return new Promise((t=>{const o=new Image;o.onload=()=>{createImageBitmap(o).then(t)},o.src=e}))},resizeBitmap:async function(e,t,o){const n=Wo(t,o);return n.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,o),await createImageBitmap(n)},colorizedCanvas:function(e,t,o){const n=Wo(e.width,e.height),l=n.getContext("2d");return l.save(),l.drawImage(t,0,0),l.fillStyle=o,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(),n}};const Ko=ae("Debug.js");let Yo=0,qo=0;var Qo=e=>{Yo=performance.now(),qo=e},Zo=e=>{const t=performance.now(),o=t-Yo;o>qo&&Ko.log(e+": "+o),Yo=t};function Xo(e,t){const o=e.x-t.x,n=e.y-t.y;return Math.sqrt(o*o+n*n)}function Jo(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var en={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:Xo,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:Jo,rectMoved:function(e,t,o){return{x:e.x+t,y:e.y+o,w:e.w,h:e.h}},rectCenterDistance:function(e,t){return Xo(Jo(e),Jo(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const tn=ae("PuzzleGraphics.js");function on(e,t){const o=se.coordByPieceIdx(e,t);return{x:o.x*e.tileSize,y:o.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var nn={loadPuzzleBitmaps:async function(e){const t=await Ho.loadImageToBitmap(e.info.imageUrl),o=await Ho.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,o){tn.log("start createPuzzleTileBitmaps");const n=o.tileSize,l=o.tileMarginWidth,a=o.tileDrawSize,s=n/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const o=new Path2D,a={x:l,y:l},r=en.pointAdd(a,{x:n,y:0}),c=en.pointAdd(r,{x:0,y:n}),u=en.pointSub(c,{x:n,y:0});if(o.moveTo(a.x,a.y),0!==e.top)for(let n=0;nse.decodePiece(ln[e].puzzle.tiles[t]),bn=(e,t)=>wn(e,t).group,xn=(e,t)=>{const o=ln[e].puzzle.info;return 0===t||t===o.tilesX-1||t===o.tiles-o.tilesX||t===o.tiles-1},Cn=(e,t)=>{const o=ln[e].puzzle.info,n={x:(o.table.width-o.width)/2,y:(o.table.height-o.height)/2},l=function(e,t){const o=ln[e].puzzle.info,n=se.coordByPieceIdx(o,t),l=n.x*o.tileSize,a=n.y*o.tileSize;return{x:l,y:a}}(e,t);return en.pointAdd(n,l)},kn=(e,t)=>wn(e,t).pos,An=e=>{const t=$n(e),o=Ln(e),n=Math.round(t/4),l=Math.round(o/4);return{x:0-n,y:0-l,w:t+2*n,h:o+2*l}},Sn=(e,t)=>{const o=zn(e),n=wn(e,t);return{x:n.pos.x,y:n.pos.y,w:o,h:o}},Pn=(e,t)=>wn(e,t).z,Tn=(e,t)=>{for(const o of ln[e].puzzle.tiles){const e=se.decodePiece(o);if(e.owner===t)return e.idx}return-1},In=e=>ln[e].puzzle.info.tileDrawSize,zn=e=>ln[e].puzzle.info.tileSize,Dn=e=>ln[e].puzzle.data.maxGroup,En=e=>ln[e].puzzle.data.maxZ;function _n(e,t){const o=ln[e].puzzle.info,n=se.coordByPieceIdx(o,t);return[n.y>0?t-o.tilesX:-1,n.x0?t-1:-1]}const Mn=(e,t,o)=>{for(const n of t)vn(e,n,{z:o})},Vn=(e,t,o)=>{const n=kn(e,t);vn(e,t,{pos:en.pointAdd(n,o)})},Nn=(e,t,o)=>{const n=In(e),l=An(e),a=o;for(const s of t){const t=wn(e,s);t.pos.x+o.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+n,a.x)),t.pos.y+o.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+n,a.y))}for(const s of t)Vn(e,s,a)},On=(e,t)=>wn(e,t).owner,Bn=(e,t)=>{for(const o of t)vn(e,o,{owner:-1,z:1})},Un=(e,t,o)=>{for(const n of t)vn(e,n,{owner:o})};function Rn(e,t){const o=ln[e].puzzle.tiles,n=se.decodePiece(o[t]),l=[];if(n.group)for(const a of o){const e=se.decodePiece(a);e.group===n.group&&l.push(e.idx)}else l.push(n.idx);return l}const Gn=(e,t)=>{const o=sn(e,t);return o?o.points:0},$n=e=>ln[e].puzzle.info.table.width,Ln=e=>ln[e].puzzle.info.table.height;var Fn={setGame:function(e,t){ln[e]=t},exists:function(e){return!!ln[e]||!1},playerExists:dn,getActivePlayers:function(e,t){const o=t-30*V;return cn(e).filter((e=>e.ts>=o))},getIdlePlayers:function(e,t){const o=t-30*V;return cn(e).filter((e=>e.ts0))},addPlayer:function(e,t,o){dn(e,t)?yn(e,t,{ts:o}):rn(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,o))},getFinishedPiecesCount:mn,getPieceCount:un,getImageUrl:function(e){return ln[e].puzzle.info.imageUrl},setImageUrl:function(e,t){ln[e].puzzle.info.imageUrl=t},get:function(e){return ln[e]||null},getAllGames:function(){return Object.values(ln).sort(((e,t)=>hn(e.id)===hn(t.id)?t.puzzle.data.started-e.puzzle.data.started:hn(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const o=sn(e,t);return o?o.bgcolor:null},getPlayerColor:(e,t)=>{const o=sn(e,t);return o?o.color:null},getPlayerName:(e,t)=>{const o=sn(e,t);return o?o.name:null},getPlayerIndexById:an,getPlayerIdByIndex:function(e,t){return ln[e].players.length>t?se.decodePlayer(ln[e].players[t]).id:null},changePlayer:yn,setPlayer:rn,setPiece:function(e,t,o){ln[e].puzzle.tiles[t]=se.encodePiece(o)},setPuzzleData:function(e,t){ln[e].puzzle.data=t},getTableWidth:$n,getTableHeight:Ln,getPuzzle:e=>ln[e].puzzle,getRng:e=>ln[e].rng.obj,getPuzzleWidth:e=>ln[e].puzzle.info.width,getPuzzleHeight:e=>ln[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return ln[e].puzzle.tiles.map(se.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const o=Tn(e,t);return o<0?null:ln[e].puzzle.tiles[o]},getPieceDrawOffset:e=>ln[e].puzzle.info.tileDrawOffset,getPieceDrawSize:In,getFinalPiecePos:Cn,getStartTs:e=>ln[e].puzzle.data.started,getFinishTs:e=>ln[e].puzzle.data.finished,handleInput:function(e,t,o,n,l){const a=ln[e].puzzle,s=function(e,t){return t in ln[e].evtInfos?ln[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([oo,a.data])},d=t=>{i.push([no,se.encodePiece(wn(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const o=sn(e,t);o&&i.push([lo,se.encodePlayer(o)])},p=o[0];if(p===Yt){const l=o[1];yn(e,t,{bgcolor:l,ts:n}),u()}else if(p===qt){const l=o[1];yn(e,t,{color:l,ts:n}),u()}else if(p===Qt){const l=`${o[1]}`.substr(0,16);yn(e,t,{name:l,ts:n}),u()}else if(p===Lt){const l=o[1],a=o[2],s=sn(e,t);if(s){const o=s.x-l,i=s.y-a;yn(e,t,{ts:n,x:o,y:i}),u()}}else if(p===Ft){const l={x:o[1],y:o[2]};yn(e,t,{d:1,ts:n}),u(),s._last_mouse_down=l;const a=((e,t)=>{const o=ln[e].puzzle.info,n=ln[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const o=En(e)+1;fn(e,{maxZ:o}),r();const n=Rn(e,a);Mn(e,n,En(e)),Un(e,n,t),c(n)}s._last_mouse=l}else if(p===Wt){const l=o[1],a=o[2],i={x:l,y:a};if(null===s._last_mouse_down)yn(e,t,{x:l,y:a,ts:n}),u();else{const o=Tn(e,t);if(o>=0){yn(e,t,{x:l,y:a,ts:n}),u();const r=Rn(e,o);let d=en.pointInBounds(i,An(e))&&en.pointInBounds(s._last_mouse_down,An(e));for(const t of r){const o=Sn(e,t);if(en.pointInBounds(i,o)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,o=a-s._last_mouse_down.y;Nn(e,r,{x:t,y:o}),c(r)}}else yn(e,t,{ts:n}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===jt){const i={x:o[1],y:o[2]},p=0;s._last_mouse_down=null;const g=Tn(e,t);if(g>=0){const o=Rn(e,g);Un(e,o,0),c(o);const s=kn(e,g),i=Cn(e,g);let h=!1;if(gn(e)===ee.REAL){for(const t of o)if(xn(e,t)){h=!0;break}}else h=!0;if(h&&en.pointDistance(i,s){const l=ln[e].puzzle.info;if(o<0)return!1;if(((e,t,o)=>{const n=bn(e,t),l=bn(e,o);return!(!n||n!==l)})(e,t,o))return!1;const a=kn(e,t),s=en.pointAdd(kn(e,o),{x:n[0]*l.tileSize,y:n[1]*l.tileSize});if(en.pointDistance(a,s){const n=ln[e].puzzle.tiles,l=bn(e,t),a=bn(e,o);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(fn(e,{maxGroup:Dn(e)+1}),r(),s=Dn(e));if(vn(e,t,{group:s}),d(t),vn(e,o,{group:s}),d(o),i.length>0)for(const r of n){const t=se.decodePiece(r);i.includes(t.group)&&(vn(e,t.idx,{group:s}),d(t.idx))}})(e,t,o),l=Rn(e,t),((e,t)=>-1===On(e,t))(e,o))Bn(e,l);else{const t=((e,t)=>{let o=0;for(const n of t){const t=Pn(e,n);t>o&&(o=t)}return o})(e,l);Mn(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Rn(e,g)){const n=_n(e,t);if(o(e,t,n[0],[0,1])||o(e,t,n[1],[-1,0])||o(e,t,n[2],[0,-1])||o(e,t,n[3],[1,0])){a=!0;break}}if(a&&pn(e)===Q.ANY){const o=Gn(e,t)+1;yn(e,t,{d:p,ts:n,points:o}),u()}else yn(e,t,{d:p,ts:n}),u();a&&gn(e)===ee.REAL&&mn(e)===un(e)&&(fn(e,{finished:n}),r()),a&&l&&l(t)}}else yn(e,t,{d:p,ts:n}),u();s._last_mouse=i}else if(p===Ht){const l=o[1],a=o[2];yn(e,t,{x:l,y:a,ts:n}),u(),s._last_mouse={x:l,y:a}}else if(p===Kt){const l=o[1],a=o[2];yn(e,t,{x:l,y:a,ts:n}),u(),s._last_mouse={x:l,y:a}}else yn(e,t,{ts:n}),u();return function(e,t,o){ln[e].evtInfos[t]=o}(e,t,s),i}};let jn=-10,Wn=20,Hn=2,Kn=15;class Yn{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=jn+Math.random()*Wn,this.vy=-1*(Hn+Math.random()*Kn),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let o=0;o{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Hn=t/2,Kn=t-Hn;const o=1/4*this.canvas.width/(t/2);jn=-o,Wn=2*o}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Yn(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Yn(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 o=[];for(;this.particles.length>0;){const e=this.particles.shift();if(!e)break;e.update(),e.alive&&o.push(e)}this.particles=o}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(!y[t]){const o=e.d?r:d;if(e.color){const n=e.d?c:u;y[t]=await createImageBitmap(Ho.colorizedCanvas(o,n,e.color))}else y[t]=o}return y[t]},v=function(e,t){return t.width=window.innerWidth,t.height=window.innerHeight,e.appendChild(t),window.addEventListener("resize",(()=>{t.width=window.innerWidth,t.height=window.innerHeight,ol=!0})),t}(l,Ho.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};fo.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const o=await fo.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...o.log),0===o.log.length&&(w.final=!0),o};let x=()=>0;const C=async()=>{if("play"===n){const n=await fo.connect(o,e,t),l=se.decodeGame(n);Fn.setGame(l.id,l),x=()=>N()}else{if("replay"!==n)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const o=se.decodeGame(t.game);Fn.setGame(o.id,o),w.lastRealTs=N(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,x=()=>w.lastGameTs}}ol=!0};await C();const k=Fn.getPieceDrawOffset(e),A=Fn.getPieceDrawSize(e),S=Fn.getPuzzleWidth(e),P=Fn.getPuzzleHeight(e),T=Fn.getTableWidth(e),I=Fn.getTableHeight(e),z={x:(T-S)/2,y:(I-P)/2},D={w:S,h:P},E={w:A,h:A},_=await nn.loadPuzzleBitmaps(Fn.getPuzzle(e)),V=new Qn(v,Fn.getRng(e));V.init();const O=v.getContext("2d");v.classList.add("loaded");const B=jo();B.move(-(T-v.width)/2,-(I-v.height)/2);const U=function(e,t,o,n){let l=[],a=!0,s=!1,i=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const n=o.viewportToWorld({x:e,y:t});return[n.x,n.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Ft,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([jt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([Wt,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),o.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Ht:Kt;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([Zt]),"replay"===n&&("KeyI"===e.code&&v([eo]),"KeyO"===e.code&&v([to]),"KeyP"===e.code&&v([Jt])),"KeyF"===e.code&&(el=!el,ol=!0),"KeyG"===e.code&&(tl=!tl,ol=!0),"KeyM"===e.code&&v([Xt]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const n=(p?24:12)*Math.sqrt(o.getCurrentZoom()),l=o.viewportDimToWorld({w:e*n,h:t*n});v([Lt,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(o.canZoom("in")){const e=f||m();v([Ht,...e])}}else if(u&&o.canZoom("out")){const e=f||m();v([Kt,...e])}},setHotkeys:e=>{a=e}}}(v,window,B,n),R=Fn.getImageUrl(e),G=()=>{const t=Fn.getStartTs(e),o=Fn.getFinishTs(e),n=x();a.setFinished(!!o),a.setDuration((o||n)-t)};G(),a.setPiecesDone(Fn.getFinishedPiecesCount(e)),a.setPiecesTotal(Fn.getPieceCount(e));const $=x();a.setActivePlayers(Fn.getActivePlayers(e,$)),a.setIdlePlayers(Fn.getIdlePlayers(e,$));const L=!!Fn.getFinishTs(e);let F=L;const j=()=>F&&!L,W=()=>{const e=localStorage.getItem("sound_volume");if(null===e)return 100;const t=parseInt(e,10);return isNaN(t)?100:t},H=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},K=()=>{const e=W();i.volume=e/100,i.play()},Y=()=>"replay"===n?localStorage.getItem("bg_color")||"#222222":Fn.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",q=()=>"replay"===n?localStorage.getItem("player_color")||"#ffffff":Fn.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let Q="",Z="",X=!1;const J=e=>{X=e;const[t,o]=e?[Q,"grab"]:[Z,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${o}`},ee=e=>{Q=Ho.colorizedCanvas(r,c,e).toDataURL(),Z=Ho.colorizedCanvas(d,u,e).toDataURL(),J(X)};ee(q());const te=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},oe=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,te())},le=()=>{w.paused=!w.paused,te()},ae=[];let ie;let re;if("play"===n?ae.push(setInterval((()=>{G()}),1e3)):"replay"===n&&te(),"play"===n)fo.onServerChange((o=>{o[0],o[1],o[2];const n=o[3];for(const[l,a]of n)switch(l){case lo:{const o=se.decodePlayer(a);o.id!==t&&(Fn.setPlayer(e,o.id,o),ol=!0)}break;case no:{const t=se.decodePiece(a);Fn.setPiece(e,t.idx,t),ol=!0}break;case oo:Fn.setPuzzleData(e,a),ol=!0}F=!!Fn.getFinishTs(e)}));else if("replay"===n){const t=(t,o)=>{const n=t;if(n[0]===Rt){const t=n[1];return Fn.addPlayer(e,t,o),!0}if(n[0]===Gt){const t=Fn.getPlayerIdByIndex(e,n[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Fn.addPlayer(e,t,o),!0}if(n[0]===$t){const t=Fn.getPlayerIdByIndex(e,n[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=n[2];return Fn.handleInput(e,t,l,o),!0}return!1};let o=w.lastGameTs;const n=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=N();if(w.paused)return w.lastRealTs=l,void(ie=setTimeout(n,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const n=w.log[w.logPointer],l=o+n[n.length-1],a=w.log[e],i=a[a.length-1],r=l+i;if(r>s){s+500*M{let t=!1;const o=e.fps||60,n=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/o,r=n*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/n),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{U.createKeyEvents();for(const o of U.consumeAll())if("play"===n){const n=o[0];if(n===Lt){const e=o[1],t=o[2],n=B.worldDimToViewport({w:e,h:t});ol=!0,B.move(n.w,n.h)}else if(n===Wt){if(de&&!Fn.getFirstOwnedPiece(e,t)){const e={x:o[1],y:o[2]},t=B.worldToViewport(e),n=Math.round(t.x-de.x),l=Math.round(t.y-de.y);ol=!0,B.move(n,l),de=t}}else if(n===qt)ee(o[1]);else if(n===Ft){const e={x:o[1],y:o[2]};de=B.worldToViewport(e),J(!0)}else if(n===jt)de=null,J(!1);else if(n===Ht){const e={x:o[1],y:o[2]};ol=!0,B.zoom("in",B.worldToViewport(e))}else if(n===Kt){const e={x:o[1],y:o[2]};ol=!0,B.zoom("out",B.worldToViewport(e))}else n===Zt?a.togglePreview():n===Xt&&a.toggleSoundsEnabled();const l=x();Fn.handleInput(e,t,o,l,(e=>{H()&&K()})).length>0&&(ol=!0),fo.sendClientEvent(o)}else if("replay"===n){const e=o[0];if(e===Jt)le();else if(e===to)ne();else if(e===eo)oe();else if(e===Lt){const e=o[1],t=o[2];ol=!0,B.move(e,t)}else if(e===Wt){if(de){const e={x:o[1],y:o[2]},t=B.worldToViewport(e),n=Math.round(t.x-de.x),l=Math.round(t.y-de.y);ol=!0,B.move(n,l),de=t}}else if(e===qt)ee(o[1]);else if(e===Ft){const e={x:o[1],y:o[2]};de=B.worldToViewport(e),J(!0)}else if(e===jt)de=null,J(!1);else if(e===Ht){const e={x:o[1],y:o[2]};ol=!0,B.zoom("in",B.worldToViewport(e))}else if(e===Kt){const e={x:o[1],y:o[2]};ol=!0,B.zoom("out",B.worldToViewport(e))}else e===Zt&&a.togglePreview()}F=!!Fn.getFinishTs(e),j()&&(V.update(),ol=!0)},render:async()=>{if(!ol)return;const o=x();let l,s,i;window.DEBUG&&Qo(0),O.fillStyle=Y(),O.fillRect(0,0,v.width,v.height),window.DEBUG&&Zo("clear done"),l=B.worldToViewportRaw(z),s=B.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&Zo("board done");const r=Fn.getPiecesSortedByZIndex(e);window.DEBUG&&Zo("get tiles done"),s=B.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?el:tl)&&(i=_[e.idx],l=B.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&Zo("tiles done");const d=[];for(const a of Fn.getActivePlayers(e,o))c=a,("replay"===n||c.id!==t)&&(i=await f(a),l=B.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,n]of d)O.fillText(e,t,n);window.DEBUG&&Zo("players done"),a.setActivePlayers(Fn.getActivePlayers(e,o)),a.setIdlePlayers(Fn.getIdlePlayers(e,o)),a.setPiecesDone(Fn.getFinishedPiecesCount(e)),window.DEBUG&&Zo("HUD done"),j()&&V.render(),ol=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([Yt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([qt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Qt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},onSoundsVolumeChange:e=>{Zn.info("vol changed",e),localStorage.setItem("sound_volume",`${e}`),K()},replayOnSpeedUp:oe,replayOnSpeedDown:ne,replayOnPauseToggle:le,previewImageUrl:R,player:{background:Y(),color:q(),name:"replay"===n?localStorage.getItem("player_name")||"#ffffff":Fn.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:H(),soundsVolume:W()},disconnect:fo.disconnect,connect:C,unload:()=>{ae.forEach((e=>{clearInterval(e)})),ie&&clearTimeout(ie),re&&re.stop()}}}var ll=e({name:"game",components:{PuzzleStatus:Ct,Scores:ft,SettingsOverlay:At,PreviewOverlay:Mt,ConnectionOverlay:vo,HelpOverlay:Ao},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await nl(`${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},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 al={id:"game"},sl={class:"menu"},il={class:"tabs"},rl=i("🧩 Puzzles");ll.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",al,[p(o(u,{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"]),[[C,"settings"===e.overlay]]),p(o(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(o(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),o(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),o(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),o("div",sl,[o("div",il,[o(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:n((()=>[rl])),_:1}),o("div",{class:"opener",onClick:l[5]||(l[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),o("div",{class:"opener",onClick:l[6]||(l[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),o("div",{class:"opener",onClick:l[7]||(l[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),o(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var dl=e({name:"replay",components:{PuzzleStatus:Ct,Scores:ft,SettingsOverlay:At,PreviewOverlay:Mt,HelpOverlay:Ao},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await nl(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 cl={id:"replay"},ul={class:"menu"},pl={class:"tabs"},gl=i("🧩 Puzzles");dl.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",cl,[p(o(g,{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"]),[[C,"settings"===e.overlay]]),p(o(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(o(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),o(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:n((()=>[o("div",null,[o("div",null,r(e.replayText),1),o("button",{class:"btn",onClick:l[5]||(l[5]=t=>e.g.replayOnSpeedUp())},"⏫"),o("button",{class:"btn",onClick:l[6]||(l[6]=t=>e.g.replayOnSpeedDown())},"⏬"),o("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),o("div",ul,[o("div",pl,[o(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:n((()=>[gl])),_:1}),o("div",{class:"opener",onClick:l[8]||(l[8]=t=>e.toggle("preview",!1))},"🖼️ Preview"),o("div",{class:"opener",onClick:l[9]||(l[9]=t=>e.toggle("settings",!0))},"🛠️ Settings"),o("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),o(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const o=k({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:it},{name:"game",path:"/g/:id",component:ll},{name:"replay",path:"/replay/:id",component:dl}]});o.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const n=S(P);n.config.globalProperties.$config=t,n.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=se.uniqId(),localStorage.setItem("ID",e)),e}(),n.use(o),n.mount("#app")})(); diff --git a/build/public/index.html b/build/public/index.html index 277cded..b9375dd 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,7 +4,7 @@ 🧩 jigsaw.hyottoko.club - + diff --git a/src/frontend/game.ts b/src/frontend/game.ts index cd1fec8..c5ea865 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -528,10 +528,6 @@ export async function main( doSetSpeedStatus() } - const replayOnSkipToggle = () => { - REPLAY.skipNonActionPhases = !REPLAY.skipNonActionPhases - } - const intervals: NodeJS.Timeout[] = [] let to: NodeJS.Timeout const clearIntervals = () => { @@ -926,7 +922,6 @@ export async function main( replayOnSpeedUp, replayOnSpeedDown, replayOnPauseToggle, - replayOnSkipToggle, previewImageUrl, player: { background: playerBgColor(), diff --git a/src/frontend/views/Replay.vue b/src/frontend/views/Replay.vue index 4320603..5840e49 100644 --- a/src/frontend/views/Replay.vue +++ b/src/frontend/views/Replay.vue @@ -12,12 +12,6 @@ >
{{replayText}}
-
- -
@@ -66,7 +60,6 @@ export default defineComponent({ duration: 0, piecesDone: 0, piecesTotal: 0, - skipNoAction: true, overlay: '', @@ -90,7 +83,6 @@ export default defineComponent({ replayOnSpeedUp: () => {}, replayOnSpeedDown: () => {}, replayOnPauseToggle: () => {}, - replayOnSkipToggle: () => {}, connect: () => {}, disconnect: () => {}, unload: () => {}, From b44ccbf8193a92e37b5bbcdfc7b601aeb50b1dbc Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Wed, 7 Jul 2021 09:58:06 +0200 Subject: [PATCH 24/45] rename some labels --- build/public/assets/index.31475a28.js | 1 - build/public/assets/index.e0d48db2.js | 1 + build/public/index.html | 2 +- src/frontend/App.vue | 2 +- src/frontend/views/Game.vue | 2 +- src/frontend/views/Replay.vue | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 build/public/assets/index.31475a28.js create mode 100644 build/public/assets/index.e0d48db2.js diff --git a/build/public/assets/index.31475a28.js b/build/public/assets/index.31475a28.js deleted file mode 100644 index 89fa752..0000000 --- a/build/public/assets/index.31475a28.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as o,w as n,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as x,s as C,u as k,x as A,y as S}from"./vendor.684f7bc8.js";var P=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const T={id:"app"},I={key:0,class:"nav"},z=i("Index"),D=i("New game");P.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",T,[e.showNav?(s(),t("ul",I,[o("li",null,[o(p,{class:"btn",to:{name:"index"}},{default:n((()=>[z])),_:1})]),o("li",null,[o(p,{class:"btn",to:{name:"new-game"}},{default:n((()=>[D])),_:1})])])):l("",!0),o(g)])};const E=864e5,_=e=>{const t=Math.floor(e/E);e%=E;const o=Math.floor(e/36e5);e%=36e5;const n=Math.floor(e/6e4);e%=6e4;return`${t}d ${o}h ${n}m ${Math.floor(e/1e3)}s`};var M=1,V=1e3,N=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>_(t-e),B=_,U=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 o=t?"🏁":"⏳",n=e,l=t||N();return`${o} ${O(n,l)}`}}});const R={class:"game-info-text"},G=o("br",null,null,-1),$=o("br",null,null,-1),L=o("br",null,null,-1),F=i(" ↪️ Watch replay ");U.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[o(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:n((()=>[o("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),G,i(" 👥 "+r(e.game.players),1),$,i(" "+r(e.time(e.game.started,e.game.finished)),1),L])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:n((()=>[F])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:U},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 W=o("h1",null,"Running games",-1),H=o("h1",null,"Finished games",-1);j.render=function(e,n,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(s(!0),t(d,null,c(e.gamesRunning,((e,n)=>(s(),t("div",{class:"game-teaser-wrap",key:n},[o(p,{game:e},null,8,["game"])])))),128)),H,(s(!0),t(d,null,c(e.gamesFinished,((e,n)=>(s(),t("div",{class:"game-teaser-wrap",key:n},[o(p,{game:e},null,8,["game"])])))),128))])};var K=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});K.render=function(e,n,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:n[2]||(n[2]=(...t)=>e.onClick&&e.onClick(...t))},[o("div",{class:"btn edit",onClick:n[1]||(n[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y,q,Q,Z,X,J,ee,te,oe=e({name:"image-library",components:{ImageTeaser:K},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});oe.render=function(e,o,n,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((o,n)=>(s(),t(u,{image:o,onClick:t=>e.imageClicked(o),onEditClick:t=>e.imageEditClicked(o),key:n},null,8,["image","onClick","onEditClick"])))),128))])},(q=Y||(Y={}))[q.Flat=0]="Flat",q[q.Out=1]="Out",q[q.In=-1]="In",(Z=Q||(Q={}))[Z.FINAL=0]="FINAL",Z[Z.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class ne{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let o=0;o<=t.length-2;o++){const e=this.random(o,t.length-1),n=t[o];t[o]=t[e],t[e]=n}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new ne(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const o=`${e}`;return o.length>=t.length?o:t.substr(0,t.length-o.length)+o},ae=(...e)=>{const t=t=>(...o)=>{const n=new Date,l=le(n.getHours(),"00"),a=le(n.getMinutes(),"00"),s=le(n.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...o)};return{log:t("log"),error:t("error"),info:t("info")}};var se={hash:e=>{let t=0;for(let o=0;o{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",ne.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Q.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:ne.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const o=e.width/e.tileSize;return{x:t%o,y:Math.floor(t/o)}},asQueryArgs:function(e){const t=[];for(const o in e){const n=[o,e[o]].map(encodeURIComponent);t.push(n.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const ie={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};ie.render=function(e,o,n,l,a,i){return s(),t("div",{style:i.style,title:n.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-a4fa5e7e");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,n,a,i,u,m)=>(s(),t("div",null,[p(o("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":n[1]||(n[1]=t=>e.input=t),placeholder:"Plants, People",onChange:n[2]||(n[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:n[3]||(n[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:n[4]||(n[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ce,[o("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((o,n)=>(s(),t("li",{key:n,class:{active:n===e.autocomplete.idx},onClick:t=>e.addVal(o)},r(o),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((o,n)=>(s(),t("span",{key:n,class:"bit",onClick:t=>e.rm(o)},r(o)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-a4fa5e7e";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{autocompleteTags:{type:Function},uploadProgress:{type:Number},uploading:{type:String}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{uploadProgressPercent(){return this.uploadProgress?Math.round(100*this.uploadProgress):0},canPostToGallery(){return!this.uploading&&!(!this.previewUrl||!this.file)},canSetupGameClick(){return!this.uploading&&!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const o=null==(t=e.dataTransfer)?void 0:t.items;if(!o||0===o.length)return null;const n=o[0];return n.type.startsWith("image/")?n:null},onFileSelect(e){const t=e.target;if(!t.files)return;const o=t.files[0];o&&this.preview(o)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const o=t.getAsFile();return!!o&&(this.file=o,this.preview(o),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=o("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},ve=o("span",{class:"btn"},"Upload File",-1),we={class:"area-settings"},be=o("td",null,[o("label",null,"Title")],-1),xe=o("tr",null,[o("td",{colspan:"2"},[o("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ce=o("td",null,[o("label",null,"Tags")],-1),ke={class:"area-buttons"},Ae=i("🖼️ Post to gallery"),Se=i("🧩 Post to gallery "),Pe=o("br",null,null,-1),Te=i(" + set up game");ge.render=function(e,n,l,c,h,m){const y=a("responsive-image"),f=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:n[11]||(n[11]=t=>e.$emit("bgclick"))},[o("div",{class:"overlay-content",onClick:n[10]||(n[10]=u((()=>{}),["stop"]))},[o("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:n[3]||(n[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:n[4]||(n[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:n[5]||(n[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(s(),t("div",me,[o("span",{class:"remove btn",onClick:n[1]||(n[1]=t=>e.previewUrl="")},"X"),o(y,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",ye,[o("label",fe,[o("input",{type:"file",style:{display:"none"},onChange:n[2]||(n[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ve])]))],34),o("div",we,[o("table",null,[o("tr",null,[be,o("td",null,[p(o("input",{type:"text","onUpdate:modelValue":n[6]||(n[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),xe,o("tr",null,[Ce,o("td",null,[o(f,{modelValue:e.tags,"onUpdate:modelValue":n[7]||(n[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),o("div",ke,[o("button",{class:"btn",disabled:!e.canPostToGallery,onClick:n[8]||(n[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},["postToGallery"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Ae],64))],8,["disabled"]),o("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:n[9]||(n[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},["setupGame"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Se,Pe,Te],64))],8,["disabled"])])])])};var Ie=e({name:"edit-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const ze={class:"area-image"},De={class:"has-image"},Ee={class:"area-settings"},_e=o("td",null,[o("label",null,"Title")],-1),Me=o("tr",null,[o("td",{colspan:"2"},[o("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ve=o("td",null,[o("label",null,"Tags")],-1),Ne={class:"area-buttons"};Ie.render=function(e,n,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:n[5]||(n[5]=t=>e.$emit("bgclick"))},[o("div",{class:"overlay-content",onClick:n[4]||(n[4]=u((()=>{}),["stop"]))},[o("div",ze,[o("div",De,[o(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),o("div",Ee,[o("table",null,[o("tr",null,[_e,o("td",null,[p(o("input",{type:"text","onUpdate:modelValue":n[1]||(n[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Me,o("tr",null,[Ve,o("td",null,[o(h,{modelValue:e.tags,"onUpdate:modelValue":n[2]||(n[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),o("div",Ne,[o("button",{class:"btn",onClick:n[3]||(n[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Oe=e({name:"new-game-dialog",components:{ResponsiveImage:ie},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Q.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Be={class:"area-image"},Ue={class:"has-image"},Re={key:0,class:"image-title"},Ge={key:0,class:"image-title-title"},$e={key:1,class:"image-title-dim"},Le={class:"area-settings"},Fe=o("td",null,[o("label",null,"Pieces")],-1),je=o("td",null,[o("label",null,"Scoring: ")],-1),We=i(" Any (Score when pieces are connected to each other or on final location)"),He=o("br",null,null,-1),Ke=i(" Final (Score when pieces are put to their final location)"),Ye=o("td",null,[o("label",null,"Shapes: ")],-1),qe=i(" Normal"),Qe=o("br",null,null,-1),Ze=i(" Any (flat pieces can occur anywhere)"),Xe=o("br",null,null,-1),Je=i(" Flat (all pieces flat on all sides)"),et=o("td",null,[o("label",null,"Snapping: ")],-1),tt=i(" Normal (pieces snap to final destination and to each other)"),ot=o("br",null,null,-1),nt=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),lt={class:"area-buttons"};Oe.render=function(e,n,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:n[11]||(n[11]=t=>e.$emit("bgclick"))},[o("div",{class:"overlay-content",onClick:n[10]||(n[10]=u((()=>{}),["stop"]))},[o("div",Be,[o("div",Ue,[o(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title||e.image.width||e.image.height?(s(),t("div",Re,[e.image.title?(s(),t("span",Ge,'"'+r(e.image.title)+'"',1)):l("",!0),e.image.width||e.image.height?(s(),t("span",$e,"("+r(e.image.width)+" ✕ "+r(e.image.height)+")",1)):l("",!0)])):l("",!0)]),o("div",Le,[o("table",null,[o("tr",null,[Fe,o("td",null,[p(o("input",{type:"text","onUpdate:modelValue":n[1]||(n[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),o("tr",null,[je,o("td",null,[o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[2]||(n[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),We]),He,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[3]||(n[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),Ke])])]),o("tr",null,[Ye,o("td",null,[o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[4]||(n[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),qe]),Qe,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[5]||(n[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ze]),Xe,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[6]||(n[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),Je])])]),o("tr",null,[et,o("td",null,[o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[7]||(n[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),tt]),ot,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[8]||(n[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),nt])])])])]),o("div",lt,[o("button",{class:"btn",disabled:!e.canStartNewGame,onClick:n[9]||(n[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};const at=async(e,t,o)=>new Promise(((n,l)=>{const a=new window.XMLHttpRequest;a.open(e,t,!0),a.withCredentials=!0;for(const e in o.headers||{})a.setRequestHeader(e,o.headers[e]);a.addEventListener("load",(function(e){n({status:this.status,text:this.responseText,json:async()=>JSON.parse(this.responseText)})})),a.addEventListener("error",(function(e){l(new Error("xhr error"))})),a.upload&&o.onUploadProgress&&a.upload.addEventListener("progress",(function(e){o.onUploadProgress&&o.onUploadProgress(e)})),a.send(o.body)}));var st=(e,t)=>at("post",e,t),it=e({components:{ImageLibrary:oe,NewImageDialog:ge,EditImageDialog:Ie,NewGameDialog:Oe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:"",uploading:"",uploadProgress:0}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((o=>!t.includes(o.title)&&o.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${se.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){this.uploadProgress=0;const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const o=await st("/api/upload",{body:t,onUploadProgress:e=>{this.uploadProgress=e.loaded/e.total}});return this.uploadProgress=1,await o.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){this.uploading="postToGallery",await this.uploadImage(e),this.uploading="",this.dialog="",await this.loadImages()},async setupGameClick(e){this.uploading="setupGame";const t=await this.uploadImage(e);this.uploading="",this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const rt={class:"upload-image-teaser"},dt=o("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),ct={key:0},ut=i(" Tags: "),pt=i(" Sort by: "),gt=o("option",{value:"date_desc"},"Newest first",-1),ht=o("option",{value:"date_asc"},"Oldest first",-1),mt=o("option",{value:"alpha_asc"},"A-Z",-1),yt=o("option",{value:"alpha_desc"},"Z-A",-1);it.render=function(e,n,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[o("div",rt,[o("div",{class:"btn btn-big",onClick:n[1]||(n[1]=t=>e.dialog="new-image")},"Upload your image"),dt]),o("div",null,[e.tags.length>0?(s(),t("label",ct,[ut,(s(!0),t(d,null,c(e.relevantTags,((o,n)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(o.slug)}],key:n,onClick:t=>e.toggleTag(o)},r(o.title)+" ("+r(o.total)+")",11,["onClick"])))),128))])):l("",!0),o("label",null,[pt,p(o("select",{"onUpdate:modelValue":n[2]||(n[2]=t=>e.filters.sort=t),onChange:n[3]||(n[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[gt,ht,mt,yt],544),[[w,e.filters.sort]])])]),o(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:n[4]||(n[4]=t=>e.dialog=""),uploadProgress:e.uploadProgress,uploading:e.uploading,onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","uploadProgress","uploading","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:n[5]||(n[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:n[6]||(n[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ft=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"},wt=o("div",null,"Scores",-1),bt=o("td",null,"⚡",-1),xt=o("td",null,"💤",-1);ft.render=function(e,n,l,a,i,u){return s(),t("div",vt,[wt,o("table",null,[(s(!0),t(d,null,c(e.actives,((e,n)=>(s(),t("tr",{key:n,style:{color:e.color}},[bt,o("td",null,r(e.name),1),o("td",null,r(e.points),1)],4)))),128)),(s(!0),t(d,null,c(e.idles,((e,n)=>(s(),t("tr",{key:n,style:{color:e.color}},[xt,o("td",null,r(e.name),1),o("td",null,r(e.points),1)],4)))),128))])])};var Ct=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 B(this.duration)}}});const kt={class:"timer"};Ct.render=function(e,n,l,a,i,d){return s(),t("div",kt,[o("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),o("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var At=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const St=m();y("data-v-a1d1c822");const Pt=o("td",null,[o("label",null,"Background: ")],-1),Tt=o("td",null,[o("label",null,"Color: ")],-1),It=o("td",null,[o("label",null,"Name: ")],-1),zt=o("td",null,[o("label",null,"Sounds: ")],-1),Dt=o("td",null,[o("label",null,"Sounds Volume: ")],-1),Et={class:"sound-volume"};f();const _t=St(((e,n,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:n[9]||(n[9]=t=>e.$emit("bgclick"))},[o("table",{class:"overlay-content settings",onClick:n[8]||(n[8]=u((()=>{}),["stop"]))},[o("tr",null,[Pt,o("td",null,[p(o("input",{type:"color","onUpdate:modelValue":n[1]||(n[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),o("tr",null,[Tt,o("td",null,[p(o("input",{type:"color","onUpdate:modelValue":n[2]||(n[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),o("tr",null,[It,o("td",null,[p(o("input",{type:"text",maxLength:"16","onUpdate:modelValue":n[3]||(n[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),o("tr",null,[zt,o("td",null,[p(o("input",{type:"checkbox","onUpdate:modelValue":n[4]||(n[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[x,e.modelValue.soundsEnabled]])])]),o("tr",null,[Dt,o("td",Et,[o("span",{onClick:n[5]||(n[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),o("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:n[6]||(n[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),o("span",{onClick:n[7]||(n[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])])])]))));At.render=_t,At.__scopeId="data-v-a1d1c822";var Mt=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const Vt={class:"preview"};Mt.render=function(e,n,l,a,i,r){return s(),t("div",{class:"overlay",onClick:n[1]||(n[1]=t=>e.$emit("bgclick"))},[o("div",Vt,[o("div",{class:"img",style:e.previewStyle},null,4)])])};var Nt=1,Ot=4,Bt=2,Ut=3,Rt=2,Gt=4,$t=3,Lt=9,Ft=1,jt=2,Wt=3,Ht=4,Kt=5,Yt=6,qt=7,Qt=8,Zt=10,Xt=11,Jt=12,eo=13,to=14,oo=1,no=2,lo=3;const ao=ae("Communication.js");let so,io=[],ro=e=>{io.push(e)},co=[],uo=e=>{co.push(e)};let po=0;const go=e=>{po!==e&&(po=e,uo(e))};function ho(e){if(2===po)try{so.send(JSON.stringify(e))}catch(t){ao.info("unable to send message.. maybe because ws is invalid?")}}let mo,yo;var fo={connect:function(e,t,o){return mo=0,yo={},go(3),new Promise((n=>{so=new WebSocket(e,o+"|"+t),so.onopen=()=>{go(2),ho([Ut])},so.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Ot){const e=t[1];n(e)}else{if(l!==Nt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],n=t[2];if(e===o&&yo[n])return void delete yo[n];ro(t)}}},so.onerror=()=>{throw go(1),"[ 2021-05-15 onerror ]"},so.onclose=e=>{4e3===e.code||1001===e.code?go(4):go(1)}}))},requestReplayData:async function(e,t){const o={gameId:e,offset:t},n=await fetch(`/api/replay-data${se.asQueryArgs(o)}`);return await n.json()},disconnect:function(){so&&so.close(4e3),mo=0,yo={}},sendClientEvent:function(e){mo++,yo[mo]=e,ho([Bt,mo,yo[mo]])},onServerChange:function(e){ro=e;for(const t of io)ro(t);io=[]},onConnectionStateChange:function(e){uo=e;for(const t of co)uo(t);co=[]},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},vo=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===fo.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===fo.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const wo={key:0,class:"overlay connection-lost"},bo={key:0,class:"overlay-content"},xo=o("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Co={key:1,class:"overlay-content"},ko=o("div",null,"Connecting...",-1);vo.render=function(e,n,a,i,r,d){return e.show?(s(),t("div",wo,[e.lostConnection?(s(),t("div",bo,[xo,o("span",{class:"btn",onClick:n[1]||(n[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",Co,[ko])):l("",!0)])):l("",!0)};var Ao=e({name:"help-overlay",emits:{bgclick:null}});const So=o("tr",null,[o("td",null,"⬆️ Move up:"),o("td",null,[o("div",null,[o("kbd",null,"W"),i("/"),o("kbd",null,"↑"),i("/🖱️")])])],-1),Po=o("tr",null,[o("td",null,"⬇️ Move down:"),o("td",null,[o("div",null,[o("kbd",null,"S"),i("/"),o("kbd",null,"↓"),i("/🖱️")])])],-1),To=o("tr",null,[o("td",null,"⬅️ Move left:"),o("td",null,[o("div",null,[o("kbd",null,"A"),i("/"),o("kbd",null,"←"),i("/🖱️")])])],-1),Io=o("tr",null,[o("td",null,"➡️ Move right:"),o("td",null,[o("div",null,[o("kbd",null,"D"),i("/"),o("kbd",null,"→"),i("/🖱️")])])],-1),zo=o("tr",null,[o("td"),o("td",null,[o("div",null,[i("Move faster by holding "),o("kbd",null,"Shift")])])],-1),Do=o("tr",null,[o("td",null,"🔍+ Zoom in:"),o("td",null,[o("div",null,[o("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Eo=o("tr",null,[o("td",null,"🔍- Zoom out:"),o("td",null,[o("div",null,[o("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),_o=o("tr",null,[o("td",null,"🖼️ Toggle preview:"),o("td",null,[o("div",null,[o("kbd",null,"Space")])])],-1),Mo=o("tr",null,[o("td",null,"🧩✔️ Toggle fixed pieces:"),o("td",null,[o("div",null,[o("kbd",null,"F")])])],-1),Vo=o("tr",null,[o("td",null,"🧩❓ Toggle loose pieces:"),o("td",null,[o("div",null,[o("kbd",null,"G")])])],-1),No=o("tr",null,[o("td",null,"🔉 Toggle sounds:"),o("td",null,[o("div",null,[o("kbd",null,"M")])])],-1),Oo=o("tr",null,[o("td",null,"⏫ Speed up (replay):"),o("td",null,[o("div",null,[o("kbd",null,"I")])])],-1),Bo=o("tr",null,[o("td",null,"⏬ Speed down (replay):"),o("td",null,[o("div",null,[o("kbd",null,"O")])])],-1),Uo=o("tr",null,[o("td",null,"⏸️ Pause (replay):"),o("td",null,[o("div",null,[o("kbd",null,"P")])])],-1);Ao.render=function(e,n,l,a,i,r){return s(),t("div",{class:"overlay transparent",onClick:n[2]||(n[2]=t=>e.$emit("bgclick"))},[o("table",{class:"overlay-content help",onClick:n[1]||(n[1]=u((()=>{}),["stop"]))},[So,Po,To,Io,zo,Do,Eo,_o,Mo,Vo,No,Oo,Bo,Uo])])};var Ro=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Go=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),$o=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Lo=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Fo=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function jo(){let e=0,t=0,o=1;const n=(n,l)=>{e+=n/o,t+=l/o},l=e=>{const t=o+.05*o*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},a=n=>({x:n.x/o-e,y:n.y/o-t}),s=n=>({x:(n.x+e)*o,y:(n.y+t)*o}),i=e=>({w:e.w*o,h:e.h*o}),r=e=>({w:e.w/o,h:e.h/o});return{getCurrentZoom:()=>o,move:n,canZoom:e=>o!=l(e),zoom:(e,t)=>((e,t)=>{if(o==e)return!1;const l=1-o/e;return n(-t.x*l,-t.y*l),o=e,!0})(l(e),t),worldToViewport:e=>{const{x:t,y:o}=s(e);return{x:Math.round(t),y:Math.round(o)}},worldToViewportRaw:s,worldDimToViewport:e=>{const{w:t,h:o}=i(e);return{w:Math.round(t),h:Math.round(o)}},worldDimToViewportRaw:i,viewportToWorld:e=>{const{x:t,y:o}=a(e);return{x:Math.round(t),y:Math.round(o)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:o}=r(e);return{w:Math.round(t),h:Math.round(o)}},viewportDimToWorldRaw:r}}function Wo(e=0,t=0){const o=document.createElement("canvas");return o.width=e,o.height=t,o}var Ho={createCanvas:Wo,loadImageToBitmap:async function(e){return new Promise((t=>{const o=new Image;o.onload=()=>{createImageBitmap(o).then(t)},o.src=e}))},resizeBitmap:async function(e,t,o){const n=Wo(t,o);return n.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,o),await createImageBitmap(n)},colorizedCanvas:function(e,t,o){const n=Wo(e.width,e.height),l=n.getContext("2d");return l.save(),l.drawImage(t,0,0),l.fillStyle=o,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(),n}};const Ko=ae("Debug.js");let Yo=0,qo=0;var Qo=e=>{Yo=performance.now(),qo=e},Zo=e=>{const t=performance.now(),o=t-Yo;o>qo&&Ko.log(e+": "+o),Yo=t};function Xo(e,t){const o=e.x-t.x,n=e.y-t.y;return Math.sqrt(o*o+n*n)}function Jo(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var en={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:Xo,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:Jo,rectMoved:function(e,t,o){return{x:e.x+t,y:e.y+o,w:e.w,h:e.h}},rectCenterDistance:function(e,t){return Xo(Jo(e),Jo(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const tn=ae("PuzzleGraphics.js");function on(e,t){const o=se.coordByPieceIdx(e,t);return{x:o.x*e.tileSize,y:o.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var nn={loadPuzzleBitmaps:async function(e){const t=await Ho.loadImageToBitmap(e.info.imageUrl),o=await Ho.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,o){tn.log("start createPuzzleTileBitmaps");const n=o.tileSize,l=o.tileMarginWidth,a=o.tileDrawSize,s=n/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const o=new Path2D,a={x:l,y:l},r=en.pointAdd(a,{x:n,y:0}),c=en.pointAdd(r,{x:0,y:n}),u=en.pointSub(c,{x:n,y:0});if(o.moveTo(a.x,a.y),0!==e.top)for(let n=0;nse.decodePiece(ln[e].puzzle.tiles[t]),bn=(e,t)=>wn(e,t).group,xn=(e,t)=>{const o=ln[e].puzzle.info;return 0===t||t===o.tilesX-1||t===o.tiles-o.tilesX||t===o.tiles-1},Cn=(e,t)=>{const o=ln[e].puzzle.info,n={x:(o.table.width-o.width)/2,y:(o.table.height-o.height)/2},l=function(e,t){const o=ln[e].puzzle.info,n=se.coordByPieceIdx(o,t),l=n.x*o.tileSize,a=n.y*o.tileSize;return{x:l,y:a}}(e,t);return en.pointAdd(n,l)},kn=(e,t)=>wn(e,t).pos,An=e=>{const t=$n(e),o=Ln(e),n=Math.round(t/4),l=Math.round(o/4);return{x:0-n,y:0-l,w:t+2*n,h:o+2*l}},Sn=(e,t)=>{const o=zn(e),n=wn(e,t);return{x:n.pos.x,y:n.pos.y,w:o,h:o}},Pn=(e,t)=>wn(e,t).z,Tn=(e,t)=>{for(const o of ln[e].puzzle.tiles){const e=se.decodePiece(o);if(e.owner===t)return e.idx}return-1},In=e=>ln[e].puzzle.info.tileDrawSize,zn=e=>ln[e].puzzle.info.tileSize,Dn=e=>ln[e].puzzle.data.maxGroup,En=e=>ln[e].puzzle.data.maxZ;function _n(e,t){const o=ln[e].puzzle.info,n=se.coordByPieceIdx(o,t);return[n.y>0?t-o.tilesX:-1,n.x0?t-1:-1]}const Mn=(e,t,o)=>{for(const n of t)vn(e,n,{z:o})},Vn=(e,t,o)=>{const n=kn(e,t);vn(e,t,{pos:en.pointAdd(n,o)})},Nn=(e,t,o)=>{const n=In(e),l=An(e),a=o;for(const s of t){const t=wn(e,s);t.pos.x+o.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+n,a.x)),t.pos.y+o.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+n,a.y))}for(const s of t)Vn(e,s,a)},On=(e,t)=>wn(e,t).owner,Bn=(e,t)=>{for(const o of t)vn(e,o,{owner:-1,z:1})},Un=(e,t,o)=>{for(const n of t)vn(e,n,{owner:o})};function Rn(e,t){const o=ln[e].puzzle.tiles,n=se.decodePiece(o[t]),l=[];if(n.group)for(const a of o){const e=se.decodePiece(a);e.group===n.group&&l.push(e.idx)}else l.push(n.idx);return l}const Gn=(e,t)=>{const o=sn(e,t);return o?o.points:0},$n=e=>ln[e].puzzle.info.table.width,Ln=e=>ln[e].puzzle.info.table.height;var Fn={setGame:function(e,t){ln[e]=t},exists:function(e){return!!ln[e]||!1},playerExists:dn,getActivePlayers:function(e,t){const o=t-30*V;return cn(e).filter((e=>e.ts>=o))},getIdlePlayers:function(e,t){const o=t-30*V;return cn(e).filter((e=>e.ts0))},addPlayer:function(e,t,o){dn(e,t)?yn(e,t,{ts:o}):rn(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,o))},getFinishedPiecesCount:mn,getPieceCount:un,getImageUrl:function(e){return ln[e].puzzle.info.imageUrl},setImageUrl:function(e,t){ln[e].puzzle.info.imageUrl=t},get:function(e){return ln[e]||null},getAllGames:function(){return Object.values(ln).sort(((e,t)=>hn(e.id)===hn(t.id)?t.puzzle.data.started-e.puzzle.data.started:hn(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const o=sn(e,t);return o?o.bgcolor:null},getPlayerColor:(e,t)=>{const o=sn(e,t);return o?o.color:null},getPlayerName:(e,t)=>{const o=sn(e,t);return o?o.name:null},getPlayerIndexById:an,getPlayerIdByIndex:function(e,t){return ln[e].players.length>t?se.decodePlayer(ln[e].players[t]).id:null},changePlayer:yn,setPlayer:rn,setPiece:function(e,t,o){ln[e].puzzle.tiles[t]=se.encodePiece(o)},setPuzzleData:function(e,t){ln[e].puzzle.data=t},getTableWidth:$n,getTableHeight:Ln,getPuzzle:e=>ln[e].puzzle,getRng:e=>ln[e].rng.obj,getPuzzleWidth:e=>ln[e].puzzle.info.width,getPuzzleHeight:e=>ln[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return ln[e].puzzle.tiles.map(se.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const o=Tn(e,t);return o<0?null:ln[e].puzzle.tiles[o]},getPieceDrawOffset:e=>ln[e].puzzle.info.tileDrawOffset,getPieceDrawSize:In,getFinalPiecePos:Cn,getStartTs:e=>ln[e].puzzle.data.started,getFinishTs:e=>ln[e].puzzle.data.finished,handleInput:function(e,t,o,n,l){const a=ln[e].puzzle,s=function(e,t){return t in ln[e].evtInfos?ln[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([oo,a.data])},d=t=>{i.push([no,se.encodePiece(wn(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const o=sn(e,t);o&&i.push([lo,se.encodePlayer(o)])},p=o[0];if(p===Yt){const l=o[1];yn(e,t,{bgcolor:l,ts:n}),u()}else if(p===qt){const l=o[1];yn(e,t,{color:l,ts:n}),u()}else if(p===Qt){const l=`${o[1]}`.substr(0,16);yn(e,t,{name:l,ts:n}),u()}else if(p===Lt){const l=o[1],a=o[2],s=sn(e,t);if(s){const o=s.x-l,i=s.y-a;yn(e,t,{ts:n,x:o,y:i}),u()}}else if(p===Ft){const l={x:o[1],y:o[2]};yn(e,t,{d:1,ts:n}),u(),s._last_mouse_down=l;const a=((e,t)=>{const o=ln[e].puzzle.info,n=ln[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const o=En(e)+1;fn(e,{maxZ:o}),r();const n=Rn(e,a);Mn(e,n,En(e)),Un(e,n,t),c(n)}s._last_mouse=l}else if(p===Wt){const l=o[1],a=o[2],i={x:l,y:a};if(null===s._last_mouse_down)yn(e,t,{x:l,y:a,ts:n}),u();else{const o=Tn(e,t);if(o>=0){yn(e,t,{x:l,y:a,ts:n}),u();const r=Rn(e,o);let d=en.pointInBounds(i,An(e))&&en.pointInBounds(s._last_mouse_down,An(e));for(const t of r){const o=Sn(e,t);if(en.pointInBounds(i,o)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,o=a-s._last_mouse_down.y;Nn(e,r,{x:t,y:o}),c(r)}}else yn(e,t,{ts:n}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===jt){const i={x:o[1],y:o[2]},p=0;s._last_mouse_down=null;const g=Tn(e,t);if(g>=0){const o=Rn(e,g);Un(e,o,0),c(o);const s=kn(e,g),i=Cn(e,g);let h=!1;if(gn(e)===ee.REAL){for(const t of o)if(xn(e,t)){h=!0;break}}else h=!0;if(h&&en.pointDistance(i,s){const l=ln[e].puzzle.info;if(o<0)return!1;if(((e,t,o)=>{const n=bn(e,t),l=bn(e,o);return!(!n||n!==l)})(e,t,o))return!1;const a=kn(e,t),s=en.pointAdd(kn(e,o),{x:n[0]*l.tileSize,y:n[1]*l.tileSize});if(en.pointDistance(a,s){const n=ln[e].puzzle.tiles,l=bn(e,t),a=bn(e,o);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(fn(e,{maxGroup:Dn(e)+1}),r(),s=Dn(e));if(vn(e,t,{group:s}),d(t),vn(e,o,{group:s}),d(o),i.length>0)for(const r of n){const t=se.decodePiece(r);i.includes(t.group)&&(vn(e,t.idx,{group:s}),d(t.idx))}})(e,t,o),l=Rn(e,t),((e,t)=>-1===On(e,t))(e,o))Bn(e,l);else{const t=((e,t)=>{let o=0;for(const n of t){const t=Pn(e,n);t>o&&(o=t)}return o})(e,l);Mn(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Rn(e,g)){const n=_n(e,t);if(o(e,t,n[0],[0,1])||o(e,t,n[1],[-1,0])||o(e,t,n[2],[0,-1])||o(e,t,n[3],[1,0])){a=!0;break}}if(a&&pn(e)===Q.ANY){const o=Gn(e,t)+1;yn(e,t,{d:p,ts:n,points:o}),u()}else yn(e,t,{d:p,ts:n}),u();a&&gn(e)===ee.REAL&&mn(e)===un(e)&&(fn(e,{finished:n}),r()),a&&l&&l(t)}}else yn(e,t,{d:p,ts:n}),u();s._last_mouse=i}else if(p===Ht){const l=o[1],a=o[2];yn(e,t,{x:l,y:a,ts:n}),u(),s._last_mouse={x:l,y:a}}else if(p===Kt){const l=o[1],a=o[2];yn(e,t,{x:l,y:a,ts:n}),u(),s._last_mouse={x:l,y:a}}else yn(e,t,{ts:n}),u();return function(e,t,o){ln[e].evtInfos[t]=o}(e,t,s),i}};let jn=-10,Wn=20,Hn=2,Kn=15;class Yn{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=jn+Math.random()*Wn,this.vy=-1*(Hn+Math.random()*Kn),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let o=0;o{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Hn=t/2,Kn=t-Hn;const o=1/4*this.canvas.width/(t/2);jn=-o,Wn=2*o}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Yn(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Yn(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 o=[];for(;this.particles.length>0;){const e=this.particles.shift();if(!e)break;e.update(),e.alive&&o.push(e)}this.particles=o}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(!y[t]){const o=e.d?r:d;if(e.color){const n=e.d?c:u;y[t]=await createImageBitmap(Ho.colorizedCanvas(o,n,e.color))}else y[t]=o}return y[t]},v=function(e,t){return t.width=window.innerWidth,t.height=window.innerHeight,e.appendChild(t),window.addEventListener("resize",(()=>{t.width=window.innerWidth,t.height=window.innerHeight,ol=!0})),t}(l,Ho.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};fo.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const o=await fo.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...o.log),0===o.log.length&&(w.final=!0),o};let x=()=>0;const C=async()=>{if("play"===n){const n=await fo.connect(o,e,t),l=se.decodeGame(n);Fn.setGame(l.id,l),x=()=>N()}else{if("replay"!==n)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const o=se.decodeGame(t.game);Fn.setGame(o.id,o),w.lastRealTs=N(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,x=()=>w.lastGameTs}}ol=!0};await C();const k=Fn.getPieceDrawOffset(e),A=Fn.getPieceDrawSize(e),S=Fn.getPuzzleWidth(e),P=Fn.getPuzzleHeight(e),T=Fn.getTableWidth(e),I=Fn.getTableHeight(e),z={x:(T-S)/2,y:(I-P)/2},D={w:S,h:P},E={w:A,h:A},_=await nn.loadPuzzleBitmaps(Fn.getPuzzle(e)),V=new Qn(v,Fn.getRng(e));V.init();const O=v.getContext("2d");v.classList.add("loaded");const B=jo();B.move(-(T-v.width)/2,-(I-v.height)/2);const U=function(e,t,o,n){let l=[],a=!0,s=!1,i=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const n=o.viewportToWorld({x:e,y:t});return[n.x,n.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Ft,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([jt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([Wt,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),o.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Ht:Kt;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([Zt]),"replay"===n&&("KeyI"===e.code&&v([eo]),"KeyO"===e.code&&v([to]),"KeyP"===e.code&&v([Jt])),"KeyF"===e.code&&(el=!el,ol=!0),"KeyG"===e.code&&(tl=!tl,ol=!0),"KeyM"===e.code&&v([Xt]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const n=(p?24:12)*Math.sqrt(o.getCurrentZoom()),l=o.viewportDimToWorld({w:e*n,h:t*n});v([Lt,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(o.canZoom("in")){const e=f||m();v([Ht,...e])}}else if(u&&o.canZoom("out")){const e=f||m();v([Kt,...e])}},setHotkeys:e=>{a=e}}}(v,window,B,n),R=Fn.getImageUrl(e),G=()=>{const t=Fn.getStartTs(e),o=Fn.getFinishTs(e),n=x();a.setFinished(!!o),a.setDuration((o||n)-t)};G(),a.setPiecesDone(Fn.getFinishedPiecesCount(e)),a.setPiecesTotal(Fn.getPieceCount(e));const $=x();a.setActivePlayers(Fn.getActivePlayers(e,$)),a.setIdlePlayers(Fn.getIdlePlayers(e,$));const L=!!Fn.getFinishTs(e);let F=L;const j=()=>F&&!L,W=()=>{const e=localStorage.getItem("sound_volume");if(null===e)return 100;const t=parseInt(e,10);return isNaN(t)?100:t},H=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},K=()=>{const e=W();i.volume=e/100,i.play()},Y=()=>"replay"===n?localStorage.getItem("bg_color")||"#222222":Fn.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",q=()=>"replay"===n?localStorage.getItem("player_color")||"#ffffff":Fn.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let Q="",Z="",X=!1;const J=e=>{X=e;const[t,o]=e?[Q,"grab"]:[Z,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${o}`},ee=e=>{Q=Ho.colorizedCanvas(r,c,e).toDataURL(),Z=Ho.colorizedCanvas(d,u,e).toDataURL(),J(X)};ee(q());const te=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},oe=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,te())},le=()=>{w.paused=!w.paused,te()},ae=[];let ie;let re;if("play"===n?ae.push(setInterval((()=>{G()}),1e3)):"replay"===n&&te(),"play"===n)fo.onServerChange((o=>{o[0],o[1],o[2];const n=o[3];for(const[l,a]of n)switch(l){case lo:{const o=se.decodePlayer(a);o.id!==t&&(Fn.setPlayer(e,o.id,o),ol=!0)}break;case no:{const t=se.decodePiece(a);Fn.setPiece(e,t.idx,t),ol=!0}break;case oo:Fn.setPuzzleData(e,a),ol=!0}F=!!Fn.getFinishTs(e)}));else if("replay"===n){const t=(t,o)=>{const n=t;if(n[0]===Rt){const t=n[1];return Fn.addPlayer(e,t,o),!0}if(n[0]===Gt){const t=Fn.getPlayerIdByIndex(e,n[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Fn.addPlayer(e,t,o),!0}if(n[0]===$t){const t=Fn.getPlayerIdByIndex(e,n[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=n[2];return Fn.handleInput(e,t,l,o),!0}return!1};let o=w.lastGameTs;const n=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=N();if(w.paused)return w.lastRealTs=l,void(ie=setTimeout(n,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const n=w.log[w.logPointer],l=o+n[n.length-1],a=w.log[e],i=a[a.length-1],r=l+i;if(r>s){s+500*M{let t=!1;const o=e.fps||60,n=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/o,r=n*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/n),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{U.createKeyEvents();for(const o of U.consumeAll())if("play"===n){const n=o[0];if(n===Lt){const e=o[1],t=o[2],n=B.worldDimToViewport({w:e,h:t});ol=!0,B.move(n.w,n.h)}else if(n===Wt){if(de&&!Fn.getFirstOwnedPiece(e,t)){const e={x:o[1],y:o[2]},t=B.worldToViewport(e),n=Math.round(t.x-de.x),l=Math.round(t.y-de.y);ol=!0,B.move(n,l),de=t}}else if(n===qt)ee(o[1]);else if(n===Ft){const e={x:o[1],y:o[2]};de=B.worldToViewport(e),J(!0)}else if(n===jt)de=null,J(!1);else if(n===Ht){const e={x:o[1],y:o[2]};ol=!0,B.zoom("in",B.worldToViewport(e))}else if(n===Kt){const e={x:o[1],y:o[2]};ol=!0,B.zoom("out",B.worldToViewport(e))}else n===Zt?a.togglePreview():n===Xt&&a.toggleSoundsEnabled();const l=x();Fn.handleInput(e,t,o,l,(e=>{H()&&K()})).length>0&&(ol=!0),fo.sendClientEvent(o)}else if("replay"===n){const e=o[0];if(e===Jt)le();else if(e===to)ne();else if(e===eo)oe();else if(e===Lt){const e=o[1],t=o[2];ol=!0,B.move(e,t)}else if(e===Wt){if(de){const e={x:o[1],y:o[2]},t=B.worldToViewport(e),n=Math.round(t.x-de.x),l=Math.round(t.y-de.y);ol=!0,B.move(n,l),de=t}}else if(e===qt)ee(o[1]);else if(e===Ft){const e={x:o[1],y:o[2]};de=B.worldToViewport(e),J(!0)}else if(e===jt)de=null,J(!1);else if(e===Ht){const e={x:o[1],y:o[2]};ol=!0,B.zoom("in",B.worldToViewport(e))}else if(e===Kt){const e={x:o[1],y:o[2]};ol=!0,B.zoom("out",B.worldToViewport(e))}else e===Zt&&a.togglePreview()}F=!!Fn.getFinishTs(e),j()&&(V.update(),ol=!0)},render:async()=>{if(!ol)return;const o=x();let l,s,i;window.DEBUG&&Qo(0),O.fillStyle=Y(),O.fillRect(0,0,v.width,v.height),window.DEBUG&&Zo("clear done"),l=B.worldToViewportRaw(z),s=B.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&Zo("board done");const r=Fn.getPiecesSortedByZIndex(e);window.DEBUG&&Zo("get tiles done"),s=B.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?el:tl)&&(i=_[e.idx],l=B.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&Zo("tiles done");const d=[];for(const a of Fn.getActivePlayers(e,o))c=a,("replay"===n||c.id!==t)&&(i=await f(a),l=B.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,n]of d)O.fillText(e,t,n);window.DEBUG&&Zo("players done"),a.setActivePlayers(Fn.getActivePlayers(e,o)),a.setIdlePlayers(Fn.getIdlePlayers(e,o)),a.setPiecesDone(Fn.getFinishedPiecesCount(e)),window.DEBUG&&Zo("HUD done"),j()&&V.render(),ol=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([Yt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([qt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Qt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},onSoundsVolumeChange:e=>{Zn.info("vol changed",e),localStorage.setItem("sound_volume",`${e}`),K()},replayOnSpeedUp:oe,replayOnSpeedDown:ne,replayOnPauseToggle:le,previewImageUrl:R,player:{background:Y(),color:q(),name:"replay"===n?localStorage.getItem("player_name")||"#ffffff":Fn.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:H(),soundsVolume:W()},disconnect:fo.disconnect,connect:C,unload:()=>{ae.forEach((e=>{clearInterval(e)})),ie&&clearTimeout(ie),re&&re.stop()}}}var ll=e({name:"game",components:{PuzzleStatus:Ct,Scores:ft,SettingsOverlay:At,PreviewOverlay:Mt,ConnectionOverlay:vo,HelpOverlay:Ao},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await nl(`${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},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 al={id:"game"},sl={class:"menu"},il={class:"tabs"},rl=i("🧩 Puzzles");ll.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",al,[p(o(u,{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"]),[[C,"settings"===e.overlay]]),p(o(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(o(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),o(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),o(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),o("div",sl,[o("div",il,[o(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:n((()=>[rl])),_:1}),o("div",{class:"opener",onClick:l[5]||(l[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),o("div",{class:"opener",onClick:l[6]||(l[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),o("div",{class:"opener",onClick:l[7]||(l[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),o(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var dl=e({name:"replay",components:{PuzzleStatus:Ct,Scores:ft,SettingsOverlay:At,PreviewOverlay:Mt,HelpOverlay:Ao},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await nl(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 cl={id:"replay"},ul={class:"menu"},pl={class:"tabs"},gl=i("🧩 Puzzles");dl.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",cl,[p(o(g,{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"]),[[C,"settings"===e.overlay]]),p(o(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(o(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),o(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:n((()=>[o("div",null,[o("div",null,r(e.replayText),1),o("button",{class:"btn",onClick:l[5]||(l[5]=t=>e.g.replayOnSpeedUp())},"⏫"),o("button",{class:"btn",onClick:l[6]||(l[6]=t=>e.g.replayOnSpeedDown())},"⏬"),o("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),o("div",ul,[o("div",pl,[o(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:n((()=>[gl])),_:1}),o("div",{class:"opener",onClick:l[8]||(l[8]=t=>e.toggle("preview",!1))},"🖼️ Preview"),o("div",{class:"opener",onClick:l[9]||(l[9]=t=>e.toggle("settings",!0))},"🛠️ Settings"),o("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),o(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const o=k({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:it},{name:"game",path:"/g/:id",component:ll},{name:"replay",path:"/replay/:id",component:dl}]});o.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const n=S(P);n.config.globalProperties.$config=t,n.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=se.uniqId(),localStorage.setItem("ID",e)),e}(),n.use(o),n.mount("#app")})(); diff --git a/build/public/assets/index.e0d48db2.js b/build/public/assets/index.e0d48db2.js new file mode 100644 index 0000000..7a470e4 --- /dev/null +++ b/build/public/assets/index.e0d48db2.js @@ -0,0 +1 @@ +import{d as e,c as t,a as o,w as n,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as x,s as C,u as k,x as A,y as S}from"./vendor.684f7bc8.js";var P=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const T={id:"app"},I={key:0,class:"nav"},z=i("Games overview"),D=i("New game");P.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",T,[e.showNav?(s(),t("ul",I,[o("li",null,[o(p,{class:"btn",to:{name:"index"}},{default:n((()=>[z])),_:1})]),o("li",null,[o(p,{class:"btn",to:{name:"new-game"}},{default:n((()=>[D])),_:1})])])):l("",!0),o(g)])};const E=864e5,_=e=>{const t=Math.floor(e/E);e%=E;const o=Math.floor(e/36e5);e%=36e5;const n=Math.floor(e/6e4);e%=6e4;return`${t}d ${o}h ${n}m ${Math.floor(e/1e3)}s`};var M=1,V=1e3,N=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>_(t-e),B=_,U=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 o=t?"🏁":"⏳",n=e,l=t||N();return`${o} ${O(n,l)}`}}});const R={class:"game-info-text"},G=o("br",null,null,-1),$=o("br",null,null,-1),L=o("br",null,null,-1),F=i(" ↪️ Watch replay ");U.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[o(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:n((()=>[o("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),G,i(" 👥 "+r(e.game.players),1),$,i(" "+r(e.time(e.game.started,e.game.finished)),1),L])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:n((()=>[F])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:U},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 W=o("h1",null,"Running games",-1),H=o("h1",null,"Finished games",-1);j.render=function(e,n,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(s(!0),t(d,null,c(e.gamesRunning,((e,n)=>(s(),t("div",{class:"game-teaser-wrap",key:n},[o(p,{game:e},null,8,["game"])])))),128)),H,(s(!0),t(d,null,c(e.gamesFinished,((e,n)=>(s(),t("div",{class:"game-teaser-wrap",key:n},[o(p,{game:e},null,8,["game"])])))),128))])};var K=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});K.render=function(e,n,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:n[2]||(n[2]=(...t)=>e.onClick&&e.onClick(...t))},[o("div",{class:"btn edit",onClick:n[1]||(n[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y,q,Q,Z,X,J,ee,te,oe=e({name:"image-library",components:{ImageTeaser:K},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});oe.render=function(e,o,n,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((o,n)=>(s(),t(u,{image:o,onClick:t=>e.imageClicked(o),onEditClick:t=>e.imageEditClicked(o),key:n},null,8,["image","onClick","onEditClick"])))),128))])},(q=Y||(Y={}))[q.Flat=0]="Flat",q[q.Out=1]="Out",q[q.In=-1]="In",(Z=Q||(Q={}))[Z.FINAL=0]="FINAL",Z[Z.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class ne{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let o=0;o<=t.length-2;o++){const e=this.random(o,t.length-1),n=t[o];t[o]=t[e],t[e]=n}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new ne(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const o=`${e}`;return o.length>=t.length?o:t.substr(0,t.length-o.length)+o},ae=(...e)=>{const t=t=>(...o)=>{const n=new Date,l=le(n.getHours(),"00"),a=le(n.getMinutes(),"00"),s=le(n.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...o)};return{log:t("log"),error:t("error"),info:t("info")}};var se={hash:e=>{let t=0;for(let o=0;o{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",ne.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Q.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:ne.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const o=e.width/e.tileSize;return{x:t%o,y:Math.floor(t/o)}},asQueryArgs:function(e){const t=[];for(const o in e){const n=[o,e[o]].map(encodeURIComponent);t.push(n.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const ie={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};ie.render=function(e,o,n,l,a,i){return s(),t("div",{style:i.style,title:n.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-a4fa5e7e");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,n,a,i,u,m)=>(s(),t("div",null,[p(o("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":n[1]||(n[1]=t=>e.input=t),placeholder:"Plants, People",onChange:n[2]||(n[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:n[3]||(n[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:n[4]||(n[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ce,[o("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((o,n)=>(s(),t("li",{key:n,class:{active:n===e.autocomplete.idx},onClick:t=>e.addVal(o)},r(o),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((o,n)=>(s(),t("span",{key:n,class:"bit",onClick:t=>e.rm(o)},r(o)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-a4fa5e7e";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{autocompleteTags:{type:Function},uploadProgress:{type:Number},uploading:{type:String}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{uploadProgressPercent(){return this.uploadProgress?Math.round(100*this.uploadProgress):0},canPostToGallery(){return!this.uploading&&!(!this.previewUrl||!this.file)},canSetupGameClick(){return!this.uploading&&!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const o=null==(t=e.dataTransfer)?void 0:t.items;if(!o||0===o.length)return null;const n=o[0];return n.type.startsWith("image/")?n:null},onFileSelect(e){const t=e.target;if(!t.files)return;const o=t.files[0];o&&this.preview(o)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const o=t.getAsFile();return!!o&&(this.file=o,this.preview(o),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=o("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},ve=o("span",{class:"btn"},"Upload File",-1),we={class:"area-settings"},be=o("td",null,[o("label",null,"Title")],-1),xe=o("tr",null,[o("td",{colspan:"2"},[o("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ce=o("td",null,[o("label",null,"Tags")],-1),ke={class:"area-buttons"},Ae=i("🖼️ Post to gallery"),Se=i("🧩 Post to gallery "),Pe=o("br",null,null,-1),Te=i(" + set up game");ge.render=function(e,n,l,c,h,m){const y=a("responsive-image"),f=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:n[11]||(n[11]=t=>e.$emit("bgclick"))},[o("div",{class:"overlay-content",onClick:n[10]||(n[10]=u((()=>{}),["stop"]))},[o("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:n[3]||(n[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:n[4]||(n[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:n[5]||(n[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(s(),t("div",me,[o("span",{class:"remove btn",onClick:n[1]||(n[1]=t=>e.previewUrl="")},"X"),o(y,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",ye,[o("label",fe,[o("input",{type:"file",style:{display:"none"},onChange:n[2]||(n[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ve])]))],34),o("div",we,[o("table",null,[o("tr",null,[be,o("td",null,[p(o("input",{type:"text","onUpdate:modelValue":n[6]||(n[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),xe,o("tr",null,[Ce,o("td",null,[o(f,{modelValue:e.tags,"onUpdate:modelValue":n[7]||(n[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),o("div",ke,[o("button",{class:"btn",disabled:!e.canPostToGallery,onClick:n[8]||(n[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},["postToGallery"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Ae],64))],8,["disabled"]),o("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:n[9]||(n[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},["setupGame"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Se,Pe,Te],64))],8,["disabled"])])])])};var Ie=e({name:"edit-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const ze={class:"area-image"},De={class:"has-image"},Ee={class:"area-settings"},_e=o("td",null,[o("label",null,"Title")],-1),Me=o("tr",null,[o("td",{colspan:"2"},[o("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ve=o("td",null,[o("label",null,"Tags")],-1),Ne={class:"area-buttons"};Ie.render=function(e,n,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:n[5]||(n[5]=t=>e.$emit("bgclick"))},[o("div",{class:"overlay-content",onClick:n[4]||(n[4]=u((()=>{}),["stop"]))},[o("div",ze,[o("div",De,[o(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),o("div",Ee,[o("table",null,[o("tr",null,[_e,o("td",null,[p(o("input",{type:"text","onUpdate:modelValue":n[1]||(n[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Me,o("tr",null,[Ve,o("td",null,[o(h,{modelValue:e.tags,"onUpdate:modelValue":n[2]||(n[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),o("div",Ne,[o("button",{class:"btn",onClick:n[3]||(n[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Oe=e({name:"new-game-dialog",components:{ResponsiveImage:ie},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Q.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Be={class:"area-image"},Ue={class:"has-image"},Re={key:0,class:"image-title"},Ge={key:0,class:"image-title-title"},$e={key:1,class:"image-title-dim"},Le={class:"area-settings"},Fe=o("td",null,[o("label",null,"Pieces")],-1),je=o("td",null,[o("label",null,"Scoring: ")],-1),We=i(" Any (Score when pieces are connected to each other or on final location)"),He=o("br",null,null,-1),Ke=i(" Final (Score when pieces are put to their final location)"),Ye=o("td",null,[o("label",null,"Shapes: ")],-1),qe=i(" Normal"),Qe=o("br",null,null,-1),Ze=i(" Any (flat pieces can occur anywhere)"),Xe=o("br",null,null,-1),Je=i(" Flat (all pieces flat on all sides)"),et=o("td",null,[o("label",null,"Snapping: ")],-1),tt=i(" Normal (pieces snap to final destination and to each other)"),ot=o("br",null,null,-1),nt=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),lt={class:"area-buttons"};Oe.render=function(e,n,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:n[11]||(n[11]=t=>e.$emit("bgclick"))},[o("div",{class:"overlay-content",onClick:n[10]||(n[10]=u((()=>{}),["stop"]))},[o("div",Be,[o("div",Ue,[o(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title||e.image.width||e.image.height?(s(),t("div",Re,[e.image.title?(s(),t("span",Ge,'"'+r(e.image.title)+'"',1)):l("",!0),e.image.width||e.image.height?(s(),t("span",$e,"("+r(e.image.width)+" ✕ "+r(e.image.height)+")",1)):l("",!0)])):l("",!0)]),o("div",Le,[o("table",null,[o("tr",null,[Fe,o("td",null,[p(o("input",{type:"text","onUpdate:modelValue":n[1]||(n[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),o("tr",null,[je,o("td",null,[o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[2]||(n[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),We]),He,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[3]||(n[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),Ke])])]),o("tr",null,[Ye,o("td",null,[o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[4]||(n[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),qe]),Qe,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[5]||(n[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ze]),Xe,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[6]||(n[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),Je])])]),o("tr",null,[et,o("td",null,[o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[7]||(n[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),tt]),ot,o("label",null,[p(o("input",{type:"radio","onUpdate:modelValue":n[8]||(n[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),nt])])])])]),o("div",lt,[o("button",{class:"btn",disabled:!e.canStartNewGame,onClick:n[9]||(n[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};const at=async(e,t,o)=>new Promise(((n,l)=>{const a=new window.XMLHttpRequest;a.open(e,t,!0),a.withCredentials=!0;for(const e in o.headers||{})a.setRequestHeader(e,o.headers[e]);a.addEventListener("load",(function(e){n({status:this.status,text:this.responseText,json:async()=>JSON.parse(this.responseText)})})),a.addEventListener("error",(function(e){l(new Error("xhr error"))})),a.upload&&o.onUploadProgress&&a.upload.addEventListener("progress",(function(e){o.onUploadProgress&&o.onUploadProgress(e)})),a.send(o.body)}));var st=(e,t)=>at("post",e,t),it=e({components:{ImageLibrary:oe,NewImageDialog:ge,EditImageDialog:Ie,NewGameDialog:Oe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:"",uploading:"",uploadProgress:0}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((o=>!t.includes(o.title)&&o.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${se.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){this.uploadProgress=0;const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const o=await st("/api/upload",{body:t,onUploadProgress:e=>{this.uploadProgress=e.loaded/e.total}});return this.uploadProgress=1,await o.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){this.uploading="postToGallery",await this.uploadImage(e),this.uploading="",this.dialog="",await this.loadImages()},async setupGameClick(e){this.uploading="setupGame";const t=await this.uploadImage(e);this.uploading="",this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const rt={class:"upload-image-teaser"},dt=o("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),ct={key:0},ut=i(" Tags: "),pt=i(" Sort by: "),gt=o("option",{value:"date_desc"},"Newest first",-1),ht=o("option",{value:"date_asc"},"Oldest first",-1),mt=o("option",{value:"alpha_asc"},"A-Z",-1),yt=o("option",{value:"alpha_desc"},"Z-A",-1);it.render=function(e,n,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[o("div",rt,[o("div",{class:"btn btn-big",onClick:n[1]||(n[1]=t=>e.dialog="new-image")},"Upload your image"),dt]),o("div",null,[e.tags.length>0?(s(),t("label",ct,[ut,(s(!0),t(d,null,c(e.relevantTags,((o,n)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(o.slug)}],key:n,onClick:t=>e.toggleTag(o)},r(o.title)+" ("+r(o.total)+")",11,["onClick"])))),128))])):l("",!0),o("label",null,[pt,p(o("select",{"onUpdate:modelValue":n[2]||(n[2]=t=>e.filters.sort=t),onChange:n[3]||(n[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[gt,ht,mt,yt],544),[[w,e.filters.sort]])])]),o(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:n[4]||(n[4]=t=>e.dialog=""),uploadProgress:e.uploadProgress,uploading:e.uploading,onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","uploadProgress","uploading","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:n[5]||(n[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:n[6]||(n[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ft=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"},wt=o("div",null,"Scores",-1),bt=o("td",null,"⚡",-1),xt=o("td",null,"💤",-1);ft.render=function(e,n,l,a,i,u){return s(),t("div",vt,[wt,o("table",null,[(s(!0),t(d,null,c(e.actives,((e,n)=>(s(),t("tr",{key:n,style:{color:e.color}},[bt,o("td",null,r(e.name),1),o("td",null,r(e.points),1)],4)))),128)),(s(!0),t(d,null,c(e.idles,((e,n)=>(s(),t("tr",{key:n,style:{color:e.color}},[xt,o("td",null,r(e.name),1),o("td",null,r(e.points),1)],4)))),128))])])};var Ct=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 B(this.duration)}}});const kt={class:"timer"};Ct.render=function(e,n,l,a,i,d){return s(),t("div",kt,[o("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),o("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var At=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const St=m();y("data-v-a1d1c822");const Pt=o("td",null,[o("label",null,"Background: ")],-1),Tt=o("td",null,[o("label",null,"Color: ")],-1),It=o("td",null,[o("label",null,"Name: ")],-1),zt=o("td",null,[o("label",null,"Sounds: ")],-1),Dt=o("td",null,[o("label",null,"Sounds Volume: ")],-1),Et={class:"sound-volume"};f();const _t=St(((e,n,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:n[9]||(n[9]=t=>e.$emit("bgclick"))},[o("table",{class:"overlay-content settings",onClick:n[8]||(n[8]=u((()=>{}),["stop"]))},[o("tr",null,[Pt,o("td",null,[p(o("input",{type:"color","onUpdate:modelValue":n[1]||(n[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),o("tr",null,[Tt,o("td",null,[p(o("input",{type:"color","onUpdate:modelValue":n[2]||(n[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),o("tr",null,[It,o("td",null,[p(o("input",{type:"text",maxLength:"16","onUpdate:modelValue":n[3]||(n[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),o("tr",null,[zt,o("td",null,[p(o("input",{type:"checkbox","onUpdate:modelValue":n[4]||(n[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[x,e.modelValue.soundsEnabled]])])]),o("tr",null,[Dt,o("td",Et,[o("span",{onClick:n[5]||(n[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),o("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:n[6]||(n[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),o("span",{onClick:n[7]||(n[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])])])]))));At.render=_t,At.__scopeId="data-v-a1d1c822";var Mt=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const Vt={class:"preview"};Mt.render=function(e,n,l,a,i,r){return s(),t("div",{class:"overlay",onClick:n[1]||(n[1]=t=>e.$emit("bgclick"))},[o("div",Vt,[o("div",{class:"img",style:e.previewStyle},null,4)])])};var Nt=1,Ot=4,Bt=2,Ut=3,Rt=2,Gt=4,$t=3,Lt=9,Ft=1,jt=2,Wt=3,Ht=4,Kt=5,Yt=6,qt=7,Qt=8,Zt=10,Xt=11,Jt=12,eo=13,to=14,oo=1,no=2,lo=3;const ao=ae("Communication.js");let so,io=[],ro=e=>{io.push(e)},co=[],uo=e=>{co.push(e)};let po=0;const go=e=>{po!==e&&(po=e,uo(e))};function ho(e){if(2===po)try{so.send(JSON.stringify(e))}catch(t){ao.info("unable to send message.. maybe because ws is invalid?")}}let mo,yo;var fo={connect:function(e,t,o){return mo=0,yo={},go(3),new Promise((n=>{so=new WebSocket(e,o+"|"+t),so.onopen=()=>{go(2),ho([Ut])},so.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Ot){const e=t[1];n(e)}else{if(l!==Nt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],n=t[2];if(e===o&&yo[n])return void delete yo[n];ro(t)}}},so.onerror=()=>{throw go(1),"[ 2021-05-15 onerror ]"},so.onclose=e=>{4e3===e.code||1001===e.code?go(4):go(1)}}))},requestReplayData:async function(e,t){const o={gameId:e,offset:t},n=await fetch(`/api/replay-data${se.asQueryArgs(o)}`);return await n.json()},disconnect:function(){so&&so.close(4e3),mo=0,yo={}},sendClientEvent:function(e){mo++,yo[mo]=e,ho([Bt,mo,yo[mo]])},onServerChange:function(e){ro=e;for(const t of io)ro(t);io=[]},onConnectionStateChange:function(e){uo=e;for(const t of co)uo(t);co=[]},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},vo=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===fo.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===fo.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const wo={key:0,class:"overlay connection-lost"},bo={key:0,class:"overlay-content"},xo=o("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Co={key:1,class:"overlay-content"},ko=o("div",null,"Connecting...",-1);vo.render=function(e,n,a,i,r,d){return e.show?(s(),t("div",wo,[e.lostConnection?(s(),t("div",bo,[xo,o("span",{class:"btn",onClick:n[1]||(n[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",Co,[ko])):l("",!0)])):l("",!0)};var Ao=e({name:"help-overlay",emits:{bgclick:null}});const So=o("tr",null,[o("td",null,"⬆️ Move up:"),o("td",null,[o("div",null,[o("kbd",null,"W"),i("/"),o("kbd",null,"↑"),i("/🖱️")])])],-1),Po=o("tr",null,[o("td",null,"⬇️ Move down:"),o("td",null,[o("div",null,[o("kbd",null,"S"),i("/"),o("kbd",null,"↓"),i("/🖱️")])])],-1),To=o("tr",null,[o("td",null,"⬅️ Move left:"),o("td",null,[o("div",null,[o("kbd",null,"A"),i("/"),o("kbd",null,"←"),i("/🖱️")])])],-1),Io=o("tr",null,[o("td",null,"➡️ Move right:"),o("td",null,[o("div",null,[o("kbd",null,"D"),i("/"),o("kbd",null,"→"),i("/🖱️")])])],-1),zo=o("tr",null,[o("td"),o("td",null,[o("div",null,[i("Move faster by holding "),o("kbd",null,"Shift")])])],-1),Do=o("tr",null,[o("td",null,"🔍+ Zoom in:"),o("td",null,[o("div",null,[o("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Eo=o("tr",null,[o("td",null,"🔍- Zoom out:"),o("td",null,[o("div",null,[o("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),_o=o("tr",null,[o("td",null,"🖼️ Toggle preview:"),o("td",null,[o("div",null,[o("kbd",null,"Space")])])],-1),Mo=o("tr",null,[o("td",null,"🧩✔️ Toggle fixed pieces:"),o("td",null,[o("div",null,[o("kbd",null,"F")])])],-1),Vo=o("tr",null,[o("td",null,"🧩❓ Toggle loose pieces:"),o("td",null,[o("div",null,[o("kbd",null,"G")])])],-1),No=o("tr",null,[o("td",null,"🔉 Toggle sounds:"),o("td",null,[o("div",null,[o("kbd",null,"M")])])],-1),Oo=o("tr",null,[o("td",null,"⏫ Speed up (replay):"),o("td",null,[o("div",null,[o("kbd",null,"I")])])],-1),Bo=o("tr",null,[o("td",null,"⏬ Speed down (replay):"),o("td",null,[o("div",null,[o("kbd",null,"O")])])],-1),Uo=o("tr",null,[o("td",null,"⏸️ Pause (replay):"),o("td",null,[o("div",null,[o("kbd",null,"P")])])],-1);Ao.render=function(e,n,l,a,i,r){return s(),t("div",{class:"overlay transparent",onClick:n[2]||(n[2]=t=>e.$emit("bgclick"))},[o("table",{class:"overlay-content help",onClick:n[1]||(n[1]=u((()=>{}),["stop"]))},[So,Po,To,Io,zo,Do,Eo,_o,Mo,Vo,No,Oo,Bo,Uo])])};var Ro=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Go=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),$o=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Lo=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Fo=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function jo(){let e=0,t=0,o=1;const n=(n,l)=>{e+=n/o,t+=l/o},l=e=>{const t=o+.05*o*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},a=n=>({x:n.x/o-e,y:n.y/o-t}),s=n=>({x:(n.x+e)*o,y:(n.y+t)*o}),i=e=>({w:e.w*o,h:e.h*o}),r=e=>({w:e.w/o,h:e.h/o});return{getCurrentZoom:()=>o,move:n,canZoom:e=>o!=l(e),zoom:(e,t)=>((e,t)=>{if(o==e)return!1;const l=1-o/e;return n(-t.x*l,-t.y*l),o=e,!0})(l(e),t),worldToViewport:e=>{const{x:t,y:o}=s(e);return{x:Math.round(t),y:Math.round(o)}},worldToViewportRaw:s,worldDimToViewport:e=>{const{w:t,h:o}=i(e);return{w:Math.round(t),h:Math.round(o)}},worldDimToViewportRaw:i,viewportToWorld:e=>{const{x:t,y:o}=a(e);return{x:Math.round(t),y:Math.round(o)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:o}=r(e);return{w:Math.round(t),h:Math.round(o)}},viewportDimToWorldRaw:r}}function Wo(e=0,t=0){const o=document.createElement("canvas");return o.width=e,o.height=t,o}var Ho={createCanvas:Wo,loadImageToBitmap:async function(e){return new Promise((t=>{const o=new Image;o.onload=()=>{createImageBitmap(o).then(t)},o.src=e}))},resizeBitmap:async function(e,t,o){const n=Wo(t,o);return n.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,o),await createImageBitmap(n)},colorizedCanvas:function(e,t,o){const n=Wo(e.width,e.height),l=n.getContext("2d");return l.save(),l.drawImage(t,0,0),l.fillStyle=o,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(),n}};const Ko=ae("Debug.js");let Yo=0,qo=0;var Qo=e=>{Yo=performance.now(),qo=e},Zo=e=>{const t=performance.now(),o=t-Yo;o>qo&&Ko.log(e+": "+o),Yo=t};function Xo(e,t){const o=e.x-t.x,n=e.y-t.y;return Math.sqrt(o*o+n*n)}function Jo(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var en={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:Xo,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:Jo,rectMoved:function(e,t,o){return{x:e.x+t,y:e.y+o,w:e.w,h:e.h}},rectCenterDistance:function(e,t){return Xo(Jo(e),Jo(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const tn=ae("PuzzleGraphics.js");function on(e,t){const o=se.coordByPieceIdx(e,t);return{x:o.x*e.tileSize,y:o.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var nn={loadPuzzleBitmaps:async function(e){const t=await Ho.loadImageToBitmap(e.info.imageUrl),o=await Ho.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,o){tn.log("start createPuzzleTileBitmaps");const n=o.tileSize,l=o.tileMarginWidth,a=o.tileDrawSize,s=n/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const o=new Path2D,a={x:l,y:l},r=en.pointAdd(a,{x:n,y:0}),c=en.pointAdd(r,{x:0,y:n}),u=en.pointSub(c,{x:n,y:0});if(o.moveTo(a.x,a.y),0!==e.top)for(let n=0;nse.decodePiece(ln[e].puzzle.tiles[t]),bn=(e,t)=>wn(e,t).group,xn=(e,t)=>{const o=ln[e].puzzle.info;return 0===t||t===o.tilesX-1||t===o.tiles-o.tilesX||t===o.tiles-1},Cn=(e,t)=>{const o=ln[e].puzzle.info,n={x:(o.table.width-o.width)/2,y:(o.table.height-o.height)/2},l=function(e,t){const o=ln[e].puzzle.info,n=se.coordByPieceIdx(o,t),l=n.x*o.tileSize,a=n.y*o.tileSize;return{x:l,y:a}}(e,t);return en.pointAdd(n,l)},kn=(e,t)=>wn(e,t).pos,An=e=>{const t=$n(e),o=Ln(e),n=Math.round(t/4),l=Math.round(o/4);return{x:0-n,y:0-l,w:t+2*n,h:o+2*l}},Sn=(e,t)=>{const o=zn(e),n=wn(e,t);return{x:n.pos.x,y:n.pos.y,w:o,h:o}},Pn=(e,t)=>wn(e,t).z,Tn=(e,t)=>{for(const o of ln[e].puzzle.tiles){const e=se.decodePiece(o);if(e.owner===t)return e.idx}return-1},In=e=>ln[e].puzzle.info.tileDrawSize,zn=e=>ln[e].puzzle.info.tileSize,Dn=e=>ln[e].puzzle.data.maxGroup,En=e=>ln[e].puzzle.data.maxZ;function _n(e,t){const o=ln[e].puzzle.info,n=se.coordByPieceIdx(o,t);return[n.y>0?t-o.tilesX:-1,n.x0?t-1:-1]}const Mn=(e,t,o)=>{for(const n of t)vn(e,n,{z:o})},Vn=(e,t,o)=>{const n=kn(e,t);vn(e,t,{pos:en.pointAdd(n,o)})},Nn=(e,t,o)=>{const n=In(e),l=An(e),a=o;for(const s of t){const t=wn(e,s);t.pos.x+o.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+n,a.x)),t.pos.y+o.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+n,a.y))}for(const s of t)Vn(e,s,a)},On=(e,t)=>wn(e,t).owner,Bn=(e,t)=>{for(const o of t)vn(e,o,{owner:-1,z:1})},Un=(e,t,o)=>{for(const n of t)vn(e,n,{owner:o})};function Rn(e,t){const o=ln[e].puzzle.tiles,n=se.decodePiece(o[t]),l=[];if(n.group)for(const a of o){const e=se.decodePiece(a);e.group===n.group&&l.push(e.idx)}else l.push(n.idx);return l}const Gn=(e,t)=>{const o=sn(e,t);return o?o.points:0},$n=e=>ln[e].puzzle.info.table.width,Ln=e=>ln[e].puzzle.info.table.height;var Fn={setGame:function(e,t){ln[e]=t},exists:function(e){return!!ln[e]||!1},playerExists:dn,getActivePlayers:function(e,t){const o=t-30*V;return cn(e).filter((e=>e.ts>=o))},getIdlePlayers:function(e,t){const o=t-30*V;return cn(e).filter((e=>e.ts0))},addPlayer:function(e,t,o){dn(e,t)?yn(e,t,{ts:o}):rn(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,o))},getFinishedPiecesCount:mn,getPieceCount:un,getImageUrl:function(e){return ln[e].puzzle.info.imageUrl},setImageUrl:function(e,t){ln[e].puzzle.info.imageUrl=t},get:function(e){return ln[e]||null},getAllGames:function(){return Object.values(ln).sort(((e,t)=>hn(e.id)===hn(t.id)?t.puzzle.data.started-e.puzzle.data.started:hn(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const o=sn(e,t);return o?o.bgcolor:null},getPlayerColor:(e,t)=>{const o=sn(e,t);return o?o.color:null},getPlayerName:(e,t)=>{const o=sn(e,t);return o?o.name:null},getPlayerIndexById:an,getPlayerIdByIndex:function(e,t){return ln[e].players.length>t?se.decodePlayer(ln[e].players[t]).id:null},changePlayer:yn,setPlayer:rn,setPiece:function(e,t,o){ln[e].puzzle.tiles[t]=se.encodePiece(o)},setPuzzleData:function(e,t){ln[e].puzzle.data=t},getTableWidth:$n,getTableHeight:Ln,getPuzzle:e=>ln[e].puzzle,getRng:e=>ln[e].rng.obj,getPuzzleWidth:e=>ln[e].puzzle.info.width,getPuzzleHeight:e=>ln[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return ln[e].puzzle.tiles.map(se.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const o=Tn(e,t);return o<0?null:ln[e].puzzle.tiles[o]},getPieceDrawOffset:e=>ln[e].puzzle.info.tileDrawOffset,getPieceDrawSize:In,getFinalPiecePos:Cn,getStartTs:e=>ln[e].puzzle.data.started,getFinishTs:e=>ln[e].puzzle.data.finished,handleInput:function(e,t,o,n,l){const a=ln[e].puzzle,s=function(e,t){return t in ln[e].evtInfos?ln[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([oo,a.data])},d=t=>{i.push([no,se.encodePiece(wn(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const o=sn(e,t);o&&i.push([lo,se.encodePlayer(o)])},p=o[0];if(p===Yt){const l=o[1];yn(e,t,{bgcolor:l,ts:n}),u()}else if(p===qt){const l=o[1];yn(e,t,{color:l,ts:n}),u()}else if(p===Qt){const l=`${o[1]}`.substr(0,16);yn(e,t,{name:l,ts:n}),u()}else if(p===Lt){const l=o[1],a=o[2],s=sn(e,t);if(s){const o=s.x-l,i=s.y-a;yn(e,t,{ts:n,x:o,y:i}),u()}}else if(p===Ft){const l={x:o[1],y:o[2]};yn(e,t,{d:1,ts:n}),u(),s._last_mouse_down=l;const a=((e,t)=>{const o=ln[e].puzzle.info,n=ln[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const o=En(e)+1;fn(e,{maxZ:o}),r();const n=Rn(e,a);Mn(e,n,En(e)),Un(e,n,t),c(n)}s._last_mouse=l}else if(p===Wt){const l=o[1],a=o[2],i={x:l,y:a};if(null===s._last_mouse_down)yn(e,t,{x:l,y:a,ts:n}),u();else{const o=Tn(e,t);if(o>=0){yn(e,t,{x:l,y:a,ts:n}),u();const r=Rn(e,o);let d=en.pointInBounds(i,An(e))&&en.pointInBounds(s._last_mouse_down,An(e));for(const t of r){const o=Sn(e,t);if(en.pointInBounds(i,o)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,o=a-s._last_mouse_down.y;Nn(e,r,{x:t,y:o}),c(r)}}else yn(e,t,{ts:n}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===jt){const i={x:o[1],y:o[2]},p=0;s._last_mouse_down=null;const g=Tn(e,t);if(g>=0){const o=Rn(e,g);Un(e,o,0),c(o);const s=kn(e,g),i=Cn(e,g);let h=!1;if(gn(e)===ee.REAL){for(const t of o)if(xn(e,t)){h=!0;break}}else h=!0;if(h&&en.pointDistance(i,s){const l=ln[e].puzzle.info;if(o<0)return!1;if(((e,t,o)=>{const n=bn(e,t),l=bn(e,o);return!(!n||n!==l)})(e,t,o))return!1;const a=kn(e,t),s=en.pointAdd(kn(e,o),{x:n[0]*l.tileSize,y:n[1]*l.tileSize});if(en.pointDistance(a,s){const n=ln[e].puzzle.tiles,l=bn(e,t),a=bn(e,o);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(fn(e,{maxGroup:Dn(e)+1}),r(),s=Dn(e));if(vn(e,t,{group:s}),d(t),vn(e,o,{group:s}),d(o),i.length>0)for(const r of n){const t=se.decodePiece(r);i.includes(t.group)&&(vn(e,t.idx,{group:s}),d(t.idx))}})(e,t,o),l=Rn(e,t),((e,t)=>-1===On(e,t))(e,o))Bn(e,l);else{const t=((e,t)=>{let o=0;for(const n of t){const t=Pn(e,n);t>o&&(o=t)}return o})(e,l);Mn(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Rn(e,g)){const n=_n(e,t);if(o(e,t,n[0],[0,1])||o(e,t,n[1],[-1,0])||o(e,t,n[2],[0,-1])||o(e,t,n[3],[1,0])){a=!0;break}}if(a&&pn(e)===Q.ANY){const o=Gn(e,t)+1;yn(e,t,{d:p,ts:n,points:o}),u()}else yn(e,t,{d:p,ts:n}),u();a&&gn(e)===ee.REAL&&mn(e)===un(e)&&(fn(e,{finished:n}),r()),a&&l&&l(t)}}else yn(e,t,{d:p,ts:n}),u();s._last_mouse=i}else if(p===Ht){const l=o[1],a=o[2];yn(e,t,{x:l,y:a,ts:n}),u(),s._last_mouse={x:l,y:a}}else if(p===Kt){const l=o[1],a=o[2];yn(e,t,{x:l,y:a,ts:n}),u(),s._last_mouse={x:l,y:a}}else yn(e,t,{ts:n}),u();return function(e,t,o){ln[e].evtInfos[t]=o}(e,t,s),i}};let jn=-10,Wn=20,Hn=2,Kn=15;class Yn{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=jn+Math.random()*Wn,this.vy=-1*(Hn+Math.random()*Kn),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let o=0;o{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Hn=t/2,Kn=t-Hn;const o=1/4*this.canvas.width/(t/2);jn=-o,Wn=2*o}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Yn(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Yn(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 o=[];for(;this.particles.length>0;){const e=this.particles.shift();if(!e)break;e.update(),e.alive&&o.push(e)}this.particles=o}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(!y[t]){const o=e.d?r:d;if(e.color){const n=e.d?c:u;y[t]=await createImageBitmap(Ho.colorizedCanvas(o,n,e.color))}else y[t]=o}return y[t]},v=function(e,t){return t.width=window.innerWidth,t.height=window.innerHeight,e.appendChild(t),window.addEventListener("resize",(()=>{t.width=window.innerWidth,t.height=window.innerHeight,ol=!0})),t}(l,Ho.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};fo.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const o=await fo.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...o.log),0===o.log.length&&(w.final=!0),o};let x=()=>0;const C=async()=>{if("play"===n){const n=await fo.connect(o,e,t),l=se.decodeGame(n);Fn.setGame(l.id,l),x=()=>N()}else{if("replay"!==n)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const o=se.decodeGame(t.game);Fn.setGame(o.id,o),w.lastRealTs=N(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,x=()=>w.lastGameTs}}ol=!0};await C();const k=Fn.getPieceDrawOffset(e),A=Fn.getPieceDrawSize(e),S=Fn.getPuzzleWidth(e),P=Fn.getPuzzleHeight(e),T=Fn.getTableWidth(e),I=Fn.getTableHeight(e),z={x:(T-S)/2,y:(I-P)/2},D={w:S,h:P},E={w:A,h:A},_=await nn.loadPuzzleBitmaps(Fn.getPuzzle(e)),V=new Qn(v,Fn.getRng(e));V.init();const O=v.getContext("2d");v.classList.add("loaded");const B=jo();B.move(-(T-v.width)/2,-(I-v.height)/2);const U=function(e,t,o,n){let l=[],a=!0,s=!1,i=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const n=o.viewportToWorld({x:e,y:t});return[n.x,n.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Ft,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([jt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([Wt,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),o.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Ht:Kt;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([Zt]),"replay"===n&&("KeyI"===e.code&&v([eo]),"KeyO"===e.code&&v([to]),"KeyP"===e.code&&v([Jt])),"KeyF"===e.code&&(el=!el,ol=!0),"KeyG"===e.code&&(tl=!tl,ol=!0),"KeyM"===e.code&&v([Xt]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const n=(p?24:12)*Math.sqrt(o.getCurrentZoom()),l=o.viewportDimToWorld({w:e*n,h:t*n});v([Lt,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(o.canZoom("in")){const e=f||m();v([Ht,...e])}}else if(u&&o.canZoom("out")){const e=f||m();v([Kt,...e])}},setHotkeys:e=>{a=e}}}(v,window,B,n),R=Fn.getImageUrl(e),G=()=>{const t=Fn.getStartTs(e),o=Fn.getFinishTs(e),n=x();a.setFinished(!!o),a.setDuration((o||n)-t)};G(),a.setPiecesDone(Fn.getFinishedPiecesCount(e)),a.setPiecesTotal(Fn.getPieceCount(e));const $=x();a.setActivePlayers(Fn.getActivePlayers(e,$)),a.setIdlePlayers(Fn.getIdlePlayers(e,$));const L=!!Fn.getFinishTs(e);let F=L;const j=()=>F&&!L,W=()=>{const e=localStorage.getItem("sound_volume");if(null===e)return 100;const t=parseInt(e,10);return isNaN(t)?100:t},H=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},K=()=>{const e=W();i.volume=e/100,i.play()},Y=()=>"replay"===n?localStorage.getItem("bg_color")||"#222222":Fn.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",q=()=>"replay"===n?localStorage.getItem("player_color")||"#ffffff":Fn.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let Q="",Z="",X=!1;const J=e=>{X=e;const[t,o]=e?[Q,"grab"]:[Z,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${o}`},ee=e=>{Q=Ho.colorizedCanvas(r,c,e).toDataURL(),Z=Ho.colorizedCanvas(d,u,e).toDataURL(),J(X)};ee(q());const te=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},oe=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,te())},le=()=>{w.paused=!w.paused,te()},ae=[];let ie;let re;if("play"===n?ae.push(setInterval((()=>{G()}),1e3)):"replay"===n&&te(),"play"===n)fo.onServerChange((o=>{o[0],o[1],o[2];const n=o[3];for(const[l,a]of n)switch(l){case lo:{const o=se.decodePlayer(a);o.id!==t&&(Fn.setPlayer(e,o.id,o),ol=!0)}break;case no:{const t=se.decodePiece(a);Fn.setPiece(e,t.idx,t),ol=!0}break;case oo:Fn.setPuzzleData(e,a),ol=!0}F=!!Fn.getFinishTs(e)}));else if("replay"===n){const t=(t,o)=>{const n=t;if(n[0]===Rt){const t=n[1];return Fn.addPlayer(e,t,o),!0}if(n[0]===Gt){const t=Fn.getPlayerIdByIndex(e,n[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Fn.addPlayer(e,t,o),!0}if(n[0]===$t){const t=Fn.getPlayerIdByIndex(e,n[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=n[2];return Fn.handleInput(e,t,l,o),!0}return!1};let o=w.lastGameTs;const n=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=N();if(w.paused)return w.lastRealTs=l,void(ie=setTimeout(n,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const n=w.log[w.logPointer],l=o+n[n.length-1],a=w.log[e],i=a[a.length-1],r=l+i;if(r>s){s+500*M{let t=!1;const o=e.fps||60,n=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/o,r=n*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/n),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{U.createKeyEvents();for(const o of U.consumeAll())if("play"===n){const n=o[0];if(n===Lt){const e=o[1],t=o[2],n=B.worldDimToViewport({w:e,h:t});ol=!0,B.move(n.w,n.h)}else if(n===Wt){if(de&&!Fn.getFirstOwnedPiece(e,t)){const e={x:o[1],y:o[2]},t=B.worldToViewport(e),n=Math.round(t.x-de.x),l=Math.round(t.y-de.y);ol=!0,B.move(n,l),de=t}}else if(n===qt)ee(o[1]);else if(n===Ft){const e={x:o[1],y:o[2]};de=B.worldToViewport(e),J(!0)}else if(n===jt)de=null,J(!1);else if(n===Ht){const e={x:o[1],y:o[2]};ol=!0,B.zoom("in",B.worldToViewport(e))}else if(n===Kt){const e={x:o[1],y:o[2]};ol=!0,B.zoom("out",B.worldToViewport(e))}else n===Zt?a.togglePreview():n===Xt&&a.toggleSoundsEnabled();const l=x();Fn.handleInput(e,t,o,l,(e=>{H()&&K()})).length>0&&(ol=!0),fo.sendClientEvent(o)}else if("replay"===n){const e=o[0];if(e===Jt)le();else if(e===to)ne();else if(e===eo)oe();else if(e===Lt){const e=o[1],t=o[2];ol=!0,B.move(e,t)}else if(e===Wt){if(de){const e={x:o[1],y:o[2]},t=B.worldToViewport(e),n=Math.round(t.x-de.x),l=Math.round(t.y-de.y);ol=!0,B.move(n,l),de=t}}else if(e===qt)ee(o[1]);else if(e===Ft){const e={x:o[1],y:o[2]};de=B.worldToViewport(e),J(!0)}else if(e===jt)de=null,J(!1);else if(e===Ht){const e={x:o[1],y:o[2]};ol=!0,B.zoom("in",B.worldToViewport(e))}else if(e===Kt){const e={x:o[1],y:o[2]};ol=!0,B.zoom("out",B.worldToViewport(e))}else e===Zt&&a.togglePreview()}F=!!Fn.getFinishTs(e),j()&&(V.update(),ol=!0)},render:async()=>{if(!ol)return;const o=x();let l,s,i;window.DEBUG&&Qo(0),O.fillStyle=Y(),O.fillRect(0,0,v.width,v.height),window.DEBUG&&Zo("clear done"),l=B.worldToViewportRaw(z),s=B.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&Zo("board done");const r=Fn.getPiecesSortedByZIndex(e);window.DEBUG&&Zo("get tiles done"),s=B.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?el:tl)&&(i=_[e.idx],l=B.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&Zo("tiles done");const d=[];for(const a of Fn.getActivePlayers(e,o))c=a,("replay"===n||c.id!==t)&&(i=await f(a),l=B.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,n]of d)O.fillText(e,t,n);window.DEBUG&&Zo("players done"),a.setActivePlayers(Fn.getActivePlayers(e,o)),a.setIdlePlayers(Fn.getIdlePlayers(e,o)),a.setPiecesDone(Fn.getFinishedPiecesCount(e)),window.DEBUG&&Zo("HUD done"),j()&&V.render(),ol=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([Yt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([qt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Qt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},onSoundsVolumeChange:e=>{Zn.info("vol changed",e),localStorage.setItem("sound_volume",`${e}`),K()},replayOnSpeedUp:oe,replayOnSpeedDown:ne,replayOnPauseToggle:le,previewImageUrl:R,player:{background:Y(),color:q(),name:"replay"===n?localStorage.getItem("player_name")||"#ffffff":Fn.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:H(),soundsVolume:W()},disconnect:fo.disconnect,connect:C,unload:()=>{ae.forEach((e=>{clearInterval(e)})),ie&&clearTimeout(ie),re&&re.stop()}}}var ll=e({name:"game",components:{PuzzleStatus:Ct,Scores:ft,SettingsOverlay:At,PreviewOverlay:Mt,ConnectionOverlay:vo,HelpOverlay:Ao},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await nl(`${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},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 al={id:"game"},sl={class:"menu"},il={class:"tabs"},rl=i("🧩 Puzzles");ll.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",al,[p(o(u,{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"]),[[C,"settings"===e.overlay]]),p(o(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(o(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),o(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),o(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),o("div",sl,[o("div",il,[o(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:n((()=>[rl])),_:1}),o("div",{class:"opener",onClick:l[5]||(l[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),o("div",{class:"opener",onClick:l[6]||(l[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),o("div",{class:"opener",onClick:l[7]||(l[7]=t=>e.toggle("help",!0))},"ℹ️ Hotkeys")])]),o(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var dl=e({name:"replay",components:{PuzzleStatus:Ct,Scores:ft,SettingsOverlay:At,PreviewOverlay:Mt,HelpOverlay:Ao},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.g=await nl(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled}}))},unmounted(){this.g.unload(),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 cl={id:"replay"},ul={class:"menu"},pl={class:"tabs"},gl=i("🧩 Puzzles");dl.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),v=a("scores");return s(),t("div",cl,[p(o(g,{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"]),[[C,"settings"===e.overlay]]),p(o(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(o(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),o(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:n((()=>[o("div",null,[o("div",null,r(e.replayText),1),o("button",{class:"btn",onClick:l[5]||(l[5]=t=>e.g.replayOnSpeedUp())},"⏫"),o("button",{class:"btn",onClick:l[6]||(l[6]=t=>e.g.replayOnSpeedDown())},"⏬"),o("button",{class:"btn",onClick:l[7]||(l[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),o("div",ul,[o("div",pl,[o(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:n((()=>[gl])),_:1}),o("div",{class:"opener",onClick:l[8]||(l[8]=t=>e.toggle("preview",!1))},"🖼️ Preview"),o("div",{class:"opener",onClick:l[9]||(l[9]=t=>e.toggle("settings",!0))},"🛠️ Settings"),o("div",{class:"opener",onClick:l[10]||(l[10]=t=>e.toggle("help",!0))},"ℹ️ Hotkeys")])]),o(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const o=k({history:A(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:it},{name:"game",path:"/g/:id",component:ll},{name:"replay",path:"/replay/:id",component:dl}]});o.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const n=S(P);n.config.globalProperties.$config=t,n.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=se.uniqId(),localStorage.setItem("ID",e)),e}(),n.use(o),n.mount("#app")})(); diff --git a/build/public/index.html b/build/public/index.html index b9375dd..e1a9d87 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,7 +4,7 @@ 🧩 jigsaw.hyottoko.club - + diff --git a/src/frontend/App.vue b/src/frontend/App.vue index 7e13439..8a9c113 100644 --- a/src/frontend/App.vue +++ b/src/frontend/App.vue @@ -1,7 +1,7 @@ diff --git a/src/frontend/game.ts b/src/frontend/game.ts index c5ea865..9002f3f 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -11,6 +11,8 @@ import Game from './../common/GameCommon' import fireworksController from './Fireworks' import Protocol from '../common/Protocol' import Time from '../common/Time' +import settings from './settings' +import { SETTINGS } from './settings' import { Dim, Point } from '../common/Geometry' import { FixedLengthArray, @@ -53,6 +55,7 @@ interface Hud { setConnectionState: (v: number) => void togglePreview: () => void toggleSoundsEnabled: () => void + togglePlayerNames: () => void setReplaySpeed?: (v: number) => void setReplayPaused?: (v: boolean) => void } @@ -205,6 +208,9 @@ function EventAdapter ( if (ev.code === 'KeyM') { addEvent([Protocol.INPUT_EV_TOGGLE_SOUNDS]) } + if (ev.code === 'KeyN') { + addEvent([Protocol.INPUT_EV_TOGGLE_PLAYER_NAMES]) + } }) const addEvent = (event: GameEvent) => { @@ -441,19 +447,13 @@ export async function main( const justFinished = () => finished && !longFinished const playerSoundVolume = (): number => { - const volume = localStorage.getItem('sound_volume') - if (volume === null) { - return 100 - } - const vol = parseInt(volume, 10) - return isNaN(vol) ? 100 : vol + return settings.getInt(SETTINGS.SOUND_VOLUME, 100) } const playerSoundEnabled = (): boolean => { - const enabled = localStorage.getItem('sound_enabled') - if (enabled === null) { - return false - } - return enabled === '1' + return settings.getBool(SETTINGS.SOUND_ENABLED, false) + } + const showPlayerNames = (): boolean => { + return settings.getBool(SETTINGS.SHOW_PLAYER_NAMES, true) } const playClick = () => { @@ -464,27 +464,24 @@ export async function main( const playerBgColor = () => { if (MODE === MODE_REPLAY) { - return localStorage.getItem('bg_color') || '#222222' + return settings.getStr(SETTINGS.COLOR_BACKGROUND, '#222222') } - return (Game.getPlayerBgColor(gameId, clientId) - || localStorage.getItem('bg_color') - || '#222222') + return Game.getPlayerBgColor(gameId, clientId) + || settings.getStr(SETTINGS.COLOR_BACKGROUND, '#222222') } const playerColor = () => { if (MODE === MODE_REPLAY) { - return localStorage.getItem('player_color') || '#ffffff' + return settings.getStr(SETTINGS.PLAYER_COLOR, '#ffffff') } - return (Game.getPlayerColor(gameId, clientId) - || localStorage.getItem('player_color') - || '#ffffff') + return Game.getPlayerColor(gameId, clientId) + || settings.getStr(SETTINGS.PLAYER_COLOR, '#ffffff') } const playerName = () => { if (MODE === MODE_REPLAY) { - return localStorage.getItem('player_name') || '#ffffff' + return settings.getStr(SETTINGS.PLAYER_NAME, 'anon') } - return (Game.getPlayerName(gameId, clientId) - || localStorage.getItem('player_name') - || 'anon') + return Game.getPlayerName(gameId, clientId) + || settings.getStr(SETTINGS.PLAYER_NAME, 'anon') } let cursorDown: string = '' @@ -718,6 +715,8 @@ export async function main( HUD.togglePreview() } else if (type === Protocol.INPUT_EV_TOGGLE_SOUNDS) { HUD.toggleSoundsEnabled() + } else if (type === Protocol.INPUT_EV_TOGGLE_PLAYER_NAMES) { + HUD.togglePlayerNames() } // LOCAL + SERVER CHANGES @@ -784,6 +783,10 @@ export async function main( viewport.zoom('out', viewport.worldToViewport(pos)) } else if (type === Protocol.INPUT_EV_TOGGLE_PREVIEW) { HUD.togglePreview() + } else if (type === Protocol.INPUT_EV_TOGGLE_SOUNDS) { + HUD.toggleSoundsEnabled() + } else if (type === Protocol.INPUT_EV_TOGGLE_PLAYER_NAMES) { + HUD.togglePlayerNames() } } } @@ -859,10 +862,12 @@ export async function main( bmp = await getPlayerCursor(p) pos = viewport.worldToViewport(p) ctx.drawImage(bmp, pos.x - CURSOR_W_2, pos.y - CURSOR_H_2) - // performance: - // not drawing text directly here, to have less ctx - // switches between drawImage and fillTxt - texts.push([`${p.name} (${p.points})`, pos.x, pos.y + CURSOR_H]) + if (showPlayerNames()) { + // performance: + // not drawing text directly here, to have less ctx + // switches between drawImage and fillTxt + texts.push([`${p.name} (${p.points})`, pos.x, pos.y + CURSOR_H]) + } } } @@ -900,25 +905,27 @@ export async function main( evts.setHotkeys(state) }, onBgChange: (value: string) => { - localStorage.setItem('bg_color', value) + settings.setStr(SETTINGS.COLOR_BACKGROUND, value) evts.addEvent([Protocol.INPUT_EV_BG_COLOR, value]) }, onColorChange: (value: string) => { - localStorage.setItem('player_color', value) + settings.setStr(SETTINGS.PLAYER_COLOR, value) evts.addEvent([Protocol.INPUT_EV_PLAYER_COLOR, value]) }, onNameChange: (value: string) => { - localStorage.setItem('player_name', value) + settings.setStr(SETTINGS.PLAYER_NAME, value) evts.addEvent([Protocol.INPUT_EV_PLAYER_NAME, value]) }, onSoundsEnabledChange: (value: boolean) => { - localStorage.setItem('sound_enabled', value ? '1' : '0') + settings.setBool(SETTINGS.SOUND_ENABLED, value) }, onSoundsVolumeChange: (value: number) => { - log.info('vol changed', value) - localStorage.setItem('sound_volume', `${value}`) + settings.setInt(SETTINGS.SOUND_VOLUME, value) playClick() }, + onShowPlayerNamesChange: (value: boolean) => { + settings.setBool(SETTINGS.SHOW_PLAYER_NAMES, value) + }, replayOnSpeedUp, replayOnSpeedDown, replayOnPauseToggle, @@ -929,6 +936,7 @@ export async function main( name: playerName(), soundsEnabled: playerSoundEnabled(), soundsVolume: playerSoundVolume(), + showPlayerNames: showPlayerNames(), }, disconnect: Communication.disconnect, connect: connect, diff --git a/src/frontend/settings.ts b/src/frontend/settings.ts new file mode 100644 index 0000000..bed0dab --- /dev/null +++ b/src/frontend/settings.ts @@ -0,0 +1,66 @@ +/** + * Player settings + */ + +export const SETTINGS = { + SOUND_VOLUME: 'sound_volume', + SOUND_ENABLED: 'sound_enabled', + COLOR_BACKGROUND: 'bg_color', + PLAYER_COLOR: 'player_color', + PLAYER_NAME: 'player_name', + SHOW_PLAYER_NAMES: 'show_player_names', +} + +const set = (setting: string, value: string): void => { + localStorage.setItem(setting, value) +} + +const get = (setting: string): any => { + return localStorage.getItem(setting) +} + +const setInt = (setting: string, val: number): void => { + set(setting, `${val}`) +} + +const getInt = (setting: string, def: number): number => { + const value = get(setting) + if (value === null) { + return def + } + const vol = parseInt(value, 10) + return isNaN(vol) ? def : vol +} + +const setBool = (setting: string, val: boolean): void => { + set(setting, val ? '1' : '0') +} + +const getBool = (setting: string, def: boolean): boolean => { + const value = get(setting) + if (value === null) { + return def + } + return value === '1' +} + +const setStr = (setting: string, val: string): void => { + set(setting, val) +} + +const getStr = (setting: string, def: string): string => { + const value = get(setting) + if (value === null) { + return def + } + return value +} + +export default { + setInt, + getInt, + setBool, + getBool, + setStr, + getStr, +} diff --git a/src/frontend/views/Game.vue b/src/frontend/views/Game.vue index ee15612..17fea3c 100644 --- a/src/frontend/views/Game.vue +++ b/src/frontend/views/Game.vue @@ -72,6 +72,7 @@ export default defineComponent({ name: '', soundsEnabled: false, soundsVolume: 100, + showPlayerNames: true, }, previewImageUrl: '', setHotkeys: (v: boolean) => {}, @@ -80,6 +81,7 @@ export default defineComponent({ onNameChange: (v: string) => {}, onSoundsEnabledChange: (v: boolean) => {}, onSoundsVolumeChange: (v: number) => {}, + onShowPlayerNamesChange: (v: boolean) => {}, connect: () => {}, disconnect: () => {}, unload: () => {}, @@ -105,6 +107,9 @@ export default defineComponent({ this.$watch(() => this.g.player.soundsVolume, (value: number) => { this.g.onSoundsVolumeChange(value) }) + this.$watch(() => this.g.player.showPlayerNames, (value: boolean) => { + this.g.onShowPlayerNamesChange(value) + }) this.g = await main( `${this.$route.params.id}`, // @ts-ignore @@ -123,6 +128,7 @@ export default defineComponent({ togglePreview: () => { this.toggle('preview', false) }, setConnectionState: (v: number) => { this.connectionState = v }, toggleSoundsEnabled: () => { this.g.player.soundsEnabled = !this.g.player.soundsEnabled }, + togglePlayerNames: () => { this.g.player.showPlayerNames = !this.g.player.showPlayerNames }, } ) }, diff --git a/src/frontend/views/Replay.vue b/src/frontend/views/Replay.vue index 2049701..b86874c 100644 --- a/src/frontend/views/Replay.vue +++ b/src/frontend/views/Replay.vue @@ -72,6 +72,7 @@ export default defineComponent({ name: '', soundsEnabled: false, soundsVolume: 100, + showPlayerNames: true, }, previewImageUrl: '', setHotkeys: (v: boolean) => {}, @@ -80,6 +81,7 @@ export default defineComponent({ onNameChange: (v: string) => {}, onSoundsEnabledChange: (v: boolean) => {}, onSoundsVolumeChange: (v: number) => {}, + onShowPlayerNamesChange: (v: boolean) => {}, replayOnSpeedUp: () => {}, replayOnSpeedDown: () => {}, replayOnPauseToggle: () => {}, @@ -113,6 +115,9 @@ export default defineComponent({ this.$watch(() => this.g.player.soundsVolume, (value: number) => { this.g.onSoundsVolumeChange(value) }) + this.$watch(() => this.g.player.showPlayerNames, (value: boolean) => { + this.g.onShowPlayerNamesChange(value) + }) this.g = await main( `${this.$route.params.id}`, // @ts-ignore @@ -133,6 +138,7 @@ export default defineComponent({ setReplaySpeed: (v: number) => { this.replay.speed = v }, setReplayPaused: (v: boolean) => { this.replay.paused = v }, toggleSoundsEnabled: () => { this.g.player.soundsEnabled = !this.g.player.soundsEnabled }, + togglePlayerNames: () => { this.g.player.showPlayerNames = !this.g.player.showPlayerNames }, } ) }, From d009f84156fb891be4035b6ac4c4493770b86d4a Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Wed, 7 Jul 2021 22:39:32 +0200 Subject: [PATCH 26/45] add puzzle center/fit --- build/public/assets/index.855f4dd3.js | 1 + build/public/assets/index.c8fb176c.js | 1 - build/public/index.html | 2 +- build/server/main.js | 2 ++ src/common/Protocol.ts | 2 ++ src/frontend/Camera.ts | 8 +++++ src/frontend/components/HelpOverlay.vue | 2 ++ src/frontend/game.ts | 39 +++++++++++++++++++++---- 8 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 build/public/assets/index.855f4dd3.js delete mode 100644 build/public/assets/index.c8fb176c.js diff --git a/build/public/assets/index.855f4dd3.js b/build/public/assets/index.855f4dd3.js new file mode 100644 index 0000000..5e9d456 --- /dev/null +++ b/build/public/assets/index.855f4dd3.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as w,m as v,n as b,q as C,s as x,u as k,x as P,y as A}from"./vendor.684f7bc8.js";var S=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const T={id:"app"},z={key:0,class:"nav"},I=i("Games overview"),D=i("New game");S.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",T,[e.showNav?(s(),t("ul",z,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[D])),_:1})])])):l("",!0),n(g)])};const E=864e5,N=e=>{const t=Math.floor(e/E);e%=E;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 M=1,_=1e3,V=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>N(t-e),U=N,B=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||V();return`${n} ${O(o,l)}`}}});const R={class:"game-info-text"},G=n("br",null,null,-1),$=n("br",null,null,-1),L=n("br",null,null,-1),F=i(" ↪️ Watch replay ");B.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),G,i(" 👥 "+r(e.game.players),1),$,i(" "+r(e.time(e.game.started,e.game.finished)),1),L])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[F])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:B},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 W=n("h1",null,"Running games",-1),K=n("h1",null,"Finished games",-1);j.render=function(e,o,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(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)),K,(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 H=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});H.render=function(e,o,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y,q,Q,Z,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:H},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(q=Y||(Y={}))[q.Flat=0]="Flat",q[q.Out=1]="Out",q[q.In=-1]="In",(Z=Q||(Q={}))[Z.FINAL=0]="FINAL",Z[Z.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),s=le(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var se={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Q.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const ie={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};ie.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-a4fa5e7e");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ce,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-a4fa5e7e";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{autocompleteTags:{type:Function},uploadProgress:{type:Number},uploading:{type:String}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{uploadProgressPercent(){return this.uploadProgress?Math.round(100*this.uploadProgress):0},canPostToGallery(){return!this.uploading&&!(!this.previewUrl||!this.file)},canSetupGameClick(){return!this.uploading&&!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=n("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},we=n("span",{class:"btn"},"Upload File",-1),ve={class:"area-settings"},be=n("td",null,[n("label",null,"Title")],-1),Ce=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),xe=n("td",null,[n("label",null,"Tags")],-1),ke={class:"area-buttons"},Pe=i("🖼️ Post to gallery"),Ae=i("🧩 Post to gallery "),Se=n("br",null,null,-1),Te=i(" + set up game");ge.render=function(e,o,l,c,h,m){const y=a("responsive-image"),f=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(s(),t("div",me,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(y,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",ye,[n("label",fe,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),we])]))],34),n("div",ve,[n("table",null,[n("tr",null,[be,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ce,n("tr",null,[xe,n("td",null,[n(f,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",ke,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},["postToGallery"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Pe],64))],8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},["setupGame"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Ae,Se,Te],64))],8,["disabled"])])])])};var ze=e({name:"edit-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Ie={class:"area-image"},De={class:"has-image"},Ee={class:"area-settings"},Ne=n("td",null,[n("label",null,"Title")],-1),Me=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),_e=n("td",null,[n("label",null,"Tags")],-1),Ve={class:"area-buttons"};ze.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Ie,[n("div",De,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ee,[n("table",null,[n("tr",null,[Ne,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Me,n("tr",null,[_e,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Ve,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Oe=e({name:"new-game-dialog",components:{ResponsiveImage:ie},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Q.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Ue={class:"area-image"},Be={class:"has-image"},Re={key:0,class:"image-title"},Ge={key:0,class:"image-title-title"},$e={key:1,class:"image-title-dim"},Le={class:"area-settings"},Fe=n("td",null,[n("label",null,"Pieces")],-1),je=n("td",null,[n("label",null,"Scoring: ")],-1),We=i(" Any (Score when pieces are connected to each other or on final location)"),Ke=n("br",null,null,-1),He=i(" Final (Score when pieces are put to their final location)"),Ye=n("td",null,[n("label",null,"Shapes: ")],-1),qe=i(" Normal"),Qe=n("br",null,null,-1),Ze=i(" Any (flat pieces can occur anywhere)"),Xe=n("br",null,null,-1),Je=i(" Flat (all pieces flat on all sides)"),et=n("td",null,[n("label",null,"Snapping: ")],-1),tt=i(" Normal (pieces snap to final destination and to each other)"),nt=n("br",null,null,-1),ot=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),lt={class:"area-buttons"};Oe.render=function(e,o,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Ue,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title||e.image.width||e.image.height?(s(),t("div",Re,[e.image.title?(s(),t("span",Ge,'"'+r(e.image.title)+'"',1)):l("",!0),e.image.width||e.image.height?(s(),t("span",$e,"("+r(e.image.width)+" ✕ "+r(e.image.height)+")",1)):l("",!0)])):l("",!0)]),n("div",Le,[n("table",null,[n("tr",null,[Fe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[je,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[w,e.scoreMode]]),We]),Ke,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[w,e.scoreMode]]),He])])]),n("tr",null,[Ye,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[w,e.shapeMode]]),qe]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[w,e.shapeMode]]),Ze]),Xe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[w,e.shapeMode]]),Je])])]),n("tr",null,[et,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[w,e.snapMode]]),tt]),nt,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[w,e.snapMode]]),ot])])])])]),n("div",lt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};const at=async(e,t,n)=>new Promise(((o,l)=>{const a=new window.XMLHttpRequest;a.open(e,t,!0),a.withCredentials=!0;for(const e in n.headers||{})a.setRequestHeader(e,n.headers[e]);a.addEventListener("load",(function(e){o({status:this.status,text:this.responseText,json:async()=>JSON.parse(this.responseText)})})),a.addEventListener("error",(function(e){l(new Error("xhr error"))})),a.upload&&n.onUploadProgress&&a.upload.addEventListener("progress",(function(e){n.onUploadProgress&&n.onUploadProgress(e)})),a.send(n.body)}));var st=(e,t)=>at("post",e,t),it=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:ze,NewGameDialog:Oe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:"",uploading:"",uploadProgress:0}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${se.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){this.uploadProgress=0;const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await st("/api/upload",{body:t,onUploadProgress:e=>{this.uploadProgress=e.loaded/e.total}});return this.uploadProgress=1,await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){this.uploading="postToGallery",await this.uploadImage(e),this.uploading="",this.dialog="",await this.loadImages()},async setupGameClick(e){this.uploading="setupGame";const t=await this.uploadImage(e);this.uploading="",this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const rt={class:"upload-image-teaser"},dt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),ct={key:0},ut=i(" Tags: "),pt=i(" Sort by: "),gt=n("option",{value:"date_desc"},"Newest first",-1),ht=n("option",{value:"date_asc"},"Oldest first",-1),mt=n("option",{value:"alpha_asc"},"A-Z",-1),yt=n("option",{value:"alpha_desc"},"Z-A",-1);it.render=function(e,o,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),w=a("new-game-dialog");return s(),t("div",null,[n("div",rt,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),dt]),n("div",null,[e.tags.length>0?(s(),t("label",ct,[ut,(s(!0),t(d,null,c(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[pt,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[gt,ht,mt,yt],544),[[v,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),uploadProgress:e.uploadProgress,uploading:e.uploading,onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","uploadProgress","uploading","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(w,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ft=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 wt={class:"scores"},vt=n("div",null,"Scores",-1),bt=n("td",null,"⚡",-1),Ct=n("td",null,"💤",-1);ft.render=function(e,o,l,a,i,u){return s(),t("div",wt,[vt,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}},[Ct,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var xt=e({name:"puzzle-status",props:{finished:{type:Boolean,required:!0},duration:{type:Number,required:!0},piecesDone:{type:Number,required:!0},piecesTotal:{type:Number,required:!0}},computed:{icon(){return this.finished?"🏁":"⏳"},durationStr(){return U(this.duration)}}});const kt={class:"timer"};xt.render=function(e,o,l,a,i,d){return s(),t("div",kt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var Pt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const At=m();y("data-v-4d56fc17");const St=n("td",null,[n("label",null,"Background: ")],-1),Tt=n("td",null,[n("label",null,"Color: ")],-1),zt=n("td",null,[n("label",null,"Name: ")],-1),It=n("td",null,[n("label",null,"Sounds: ")],-1),Dt=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Et={class:"sound-volume"},Nt=n("td",null,[n("label",null,"Show player names: ")],-1);f();const Mt=At(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[10]||(o[10]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[9]||(o[9]=u((()=>{}),["stop"]))},[n("tr",null,[St,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[Tt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[zt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[It,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[C,e.modelValue.soundsEnabled]])])]),n("tr",null,[Dt,n("td",Et,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])]),n("tr",null,[Nt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[8]||(o[8]=t=>e.modelValue.showPlayerNames=t)},null,512),[[C,e.modelValue.showPlayerNames]])])])])]))));Pt.render=Mt,Pt.__scopeId="data-v-4d56fc17";var _t=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const Vt={class:"preview"};_t.render=function(e,o,l,a,i,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",Vt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Ot=1,Ut=4,Bt=2,Rt=3,Gt=2,$t=4,Lt=3,Ft=9,jt=1,Wt=2,Kt=3,Ht=4,Yt=5,qt=6,Qt=7,Zt=8,Xt=10,Jt=11,en=12,tn=13,nn=14,on=15,ln=16,an=1,sn=2,rn=3;const dn=ae("Communication.js");let cn,un=[],pn=e=>{un.push(e)},gn=[],hn=e=>{gn.push(e)};let mn=0;const yn=e=>{mn!==e&&(mn=e,hn(e))};function fn(e){if(2===mn)try{cn.send(JSON.stringify(e))}catch(t){dn.info("unable to send message.. maybe because ws is invalid?")}}let wn,vn;var bn={connect:function(e,t,n){return wn=0,vn={},yn(3),new Promise((o=>{cn=new WebSocket(e,n+"|"+t),cn.onopen=()=>{yn(2),fn([Rt])},cn.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Ut){const e=t[1];o(e)}else{if(l!==Ot)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&vn[o])return void delete vn[o];pn(t)}}},cn.onerror=()=>{throw yn(1),"[ 2021-05-15 onerror ]"},cn.onclose=e=>{4e3===e.code||1001===e.code?yn(4):yn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${se.asQueryArgs(n)}`);return await o.json()},disconnect:function(){cn&&cn.close(4e3),wn=0,vn={}},sendClientEvent:function(e){wn++,vn[wn]=e,fn([Bt,wn,vn[wn]])},onServerChange:function(e){pn=e;for(const t of un)pn(t);un=[]},onConnectionStateChange:function(e){hn=e;for(const t of gn)hn(t);gn=[]},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},Cn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===bn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===bn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const xn={key:0,class:"overlay connection-lost"},kn={key:0,class:"overlay-content"},Pn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),An={key:1,class:"overlay-content"},Sn=n("div",null,"Connecting...",-1);Cn.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",xn,[e.lostConnection?(s(),t("div",kn,[Pn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",An,[Sn])):l("",!0)])):l("",!0)};var Tn=e({name:"help-overlay",emits:{bgclick:null}});const zn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),In=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),Dn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),En=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),Nn=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Mn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),_n=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),Vn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),On=n("tr",null,[n("td",null,"🎯 Center puzzle in screen:"),n("td",null,[n("div",null,[n("kbd",null,"C")])])],-1),Un=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Bn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Rn=n("tr",null,[n("td",null,"👤 Toggle player names:"),n("td",null,[n("div",null,[n("kbd",null,"N")])])],-1),Gn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),$n=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),Ln=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),Fn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);Tn.render=function(e,o,l,a,i,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]=u((()=>{}),["stop"]))},[zn,In,Dn,En,Nn,Mn,_n,Vn,On,Un,Bn,Rn,Gn,$n,Ln,Fn])])};var jn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Wn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Kn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Hn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Yn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function qn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Qn={createCanvas:qn,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=qn(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=qn(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(),o}};const Zn=ae("Debug.js");let Xn=0,Jn=0;var eo=e=>{Xn=performance.now(),Jn=e},to=e=>{const t=performance.now(),n=t-Xn;n>Jn&&Zn.log(e+": "+n),Xn=t};function no(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function oo(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var lo={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:no,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:oo,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 no(oo(e),oo(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const ao=ae("PuzzleGraphics.js");function so(e,t){const n=se.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var io={loadPuzzleBitmaps:async function(e){const t=await Qn.loadImageToBitmap(e.info.imageUrl),n=await Qn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){ao.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=lo.pointAdd(a,{x:o,y:0}),c=lo.pointAdd(r,{x:0,y:o}),u=lo.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;ose.decodePiece(ro[e].puzzle.tiles[t]),Po=(e,t)=>ko(e,t).group,Ao=(e,t)=>{const n=ro[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},So=(e,t)=>{const n=ro[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=ro[e].puzzle.info,o=se.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return lo.pointAdd(o,l)},To=(e,t)=>ko(e,t).pos,zo=e=>{const t=Wo(e),n=Ko(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}},Io=(e,t)=>{const n=Mo(e),o=ko(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Do=(e,t)=>ko(e,t).z,Eo=(e,t)=>{for(const n of ro[e].puzzle.tiles){const e=se.decodePiece(n);if(e.owner===t)return e.idx}return-1},No=e=>ro[e].puzzle.info.tileDrawSize,Mo=e=>ro[e].puzzle.info.tileSize,_o=e=>ro[e].puzzle.data.maxGroup,Vo=e=>ro[e].puzzle.data.maxZ;function Oo(e,t){const n=ro[e].puzzle.info,o=se.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Uo=(e,t,n)=>{for(const o of t)xo(e,o,{z:n})},Bo=(e,t,n)=>{const o=To(e,t);xo(e,t,{pos:lo.pointAdd(o,n)})},Ro=(e,t,n)=>{const o=No(e),l=zo(e),a=n;for(const s of t){const t=ko(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)Bo(e,s,a)},Go=(e,t)=>ko(e,t).owner,$o=(e,t)=>{for(const n of t)xo(e,n,{owner:-1,z:1})},Lo=(e,t,n)=>{for(const o of t)xo(e,o,{owner:n})};function Fo(e,t){const n=ro[e].puzzle.tiles,o=se.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=se.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const jo=(e,t)=>{const n=uo(e,t);return n?n.points:0},Wo=e=>ro[e].puzzle.info.table.width,Ko=e=>ro[e].puzzle.info.table.height;var Ho={setGame:function(e,t){ro[e]=t},exists:function(e){return!!ro[e]||!1},playerExists:go,getActivePlayers:function(e,t){const n=t-30*_;return ho(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return ho(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){go(e,t)?bo(e,t,{ts:n}):po(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:vo,getPieceCount:mo,getImageUrl:function(e){return ro[e].puzzle.info.imageUrl},setImageUrl:function(e,t){ro[e].puzzle.info.imageUrl=t},get:function(e){return ro[e]||null},getAllGames:function(){return Object.values(ro).sort(((e,t)=>wo(e.id)===wo(t.id)?t.puzzle.data.started-e.puzzle.data.started:wo(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=uo(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=uo(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=uo(e,t);return n?n.name:null},getPlayerIndexById:co,getPlayerIdByIndex:function(e,t){return ro[e].players.length>t?se.decodePlayer(ro[e].players[t]).id:null},changePlayer:bo,setPlayer:po,setPiece:function(e,t,n){ro[e].puzzle.tiles[t]=se.encodePiece(n)},setPuzzleData:function(e,t){ro[e].puzzle.data=t},getTableWidth:Wo,getTableHeight:Ko,getPuzzle:e=>ro[e].puzzle,getRng:e=>ro[e].rng.obj,getPuzzleWidth:e=>ro[e].puzzle.info.width,getPuzzleHeight:e=>ro[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return ro[e].puzzle.tiles.map(se.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Eo(e,t);return n<0?null:ro[e].puzzle.tiles[n]},getPieceDrawOffset:e=>ro[e].puzzle.info.tileDrawOffset,getPieceDrawSize:No,getFinalPiecePos:So,getStartTs:e=>ro[e].puzzle.data.started,getFinishTs:e=>ro[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=ro[e].puzzle,s=function(e,t){return t in ro[e].evtInfos?ro[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([an,a.data])},d=t=>{i.push([sn,se.encodePiece(ko(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=uo(e,t);n&&i.push([rn,se.encodePlayer(n)])},p=n[0];if(p===qt){const l=n[1];bo(e,t,{bgcolor:l,ts:o}),u()}else if(p===Qt){const l=n[1];bo(e,t,{color:l,ts:o}),u()}else if(p===Zt){const l=`${n[1]}`.substr(0,16);bo(e,t,{name:l,ts:o}),u()}else if(p===Ft){const l=n[1],a=n[2],s=uo(e,t);if(s){const n=s.x-l,i=s.y-a;bo(e,t,{ts:o,x:n,y:i}),u()}}else if(p===jt){const l={x:n[1],y:n[2]};bo(e,t,{d:1,ts:o}),u(),s._last_mouse_down=l;const a=((e,t)=>{const n=ro[e].puzzle.info,o=ro[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=Vo(e)+1;Co(e,{maxZ:n}),r();const o=Fo(e,a);Uo(e,o,Vo(e)),Lo(e,o,t),c(o)}s._last_mouse=l}else if(p===Kt){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)bo(e,t,{x:l,y:a,ts:o}),u();else{const n=Eo(e,t);if(n>=0){bo(e,t,{x:l,y:a,ts:o}),u();const r=Fo(e,n);let d=lo.pointInBounds(i,zo(e))&&lo.pointInBounds(s._last_mouse_down,zo(e));for(const t of r){const n=Io(e,t);if(lo.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Ro(e,r,{x:t,y:n}),c(r)}}else bo(e,t,{ts:o}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===Wt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Eo(e,t);if(g>=0){const n=Fo(e,g);Lo(e,n,0),c(n);const s=To(e,g),i=So(e,g);let h=!1;if(fo(e)===ee.REAL){for(const t of n)if(Ao(e,t)){h=!0;break}}else h=!0;if(h&&lo.pointDistance(i,s){const l=ro[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=Po(e,t),l=Po(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=To(e,t),s=lo.pointAdd(To(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(lo.pointDistance(a,s){const o=ro[e].puzzle.tiles,l=Po(e,t),a=Po(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(Co(e,{maxGroup:_o(e)+1}),r(),s=_o(e));if(xo(e,t,{group:s}),d(t),xo(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=se.decodePiece(r);i.includes(t.group)&&(xo(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=Fo(e,t),((e,t)=>-1===Go(e,t))(e,n))$o(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=Do(e,o);t>n&&(n=t)}return n})(e,l);Uo(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Fo(e,g)){const o=Oo(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])){a=!0;break}}if(a&&yo(e)===Q.ANY){const n=jo(e,t)+1;bo(e,t,{d:p,ts:o,points:n}),u()}else bo(e,t,{d:p,ts:o}),u();a&&fo(e)===ee.REAL&&vo(e)===mo(e)&&(Co(e,{finished:o}),r()),a&&l&&l(t)}}else bo(e,t,{d:p,ts:o}),u();s._last_mouse=i}else if(p===Ht){const l=n[1],a=n[2];bo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else if(p===Yt){const l=n[1],a=n[2];bo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else bo(e,t,{ts:o}),u();return function(e,t,n){ro[e].evtInfos[t]=n}(e,t,s),i}};let Yo=-10,qo=20,Qo=2,Zo=15;class Xo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Yo+Math.random()*qo,this.vy=-1*(Qo+Math.random()*Zo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Qo=t/2,Zo=t-Qo;const n=1/4*this.canvas.width/(t/2);Yo=-n,qo=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Xo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Xo(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{localStorage.setItem(e,t)},rl=e=>localStorage.getItem(e);var dl=(e,t)=>{il(e,`${t}`)},cl=(e,t)=>{const n=rl(e);if(null===n)return t;const o=parseInt(n,10);return isNaN(o)?t:o},ul=(e,t)=>{il(e,t?"1":"0")},pl=(e,t)=>{const n=rl(e);return null===n?t:"1"===n},gl=(e,t)=>{il(e,t)},hl=(e,t)=>{const n=rl(e);return null===n?t:n};const ml={"./grab.png":Wn,"./grab_mask.png":Kn,"./hand.png":Hn,"./hand_mask.png":Yn},yl={"./click.mp3":jn};let fl=!0,wl=!0;let vl=!0;async function bl(e,t,n,o,l,a){void 0===window.DEBUG&&(window.DEBUG=!1);const s=yl["./click.mp3"].default,i=new Audio(s),r=await Qn.loadImageToBitmap(ml["./grab.png"].default),d=await Qn.loadImageToBitmap(ml["./hand.png"].default),c=await Qn.loadImageToBitmap(ml["./grab_mask.png"].default),u=await Qn.loadImageToBitmap(ml["./hand_mask.png"].default),p=r.width,g=Math.round(p/2),h=r.height,m=Math.round(h/2),y={},f=async e=>{const t=e.color+" "+e.d;if(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Qn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},w=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,vl=!0})),t}(l,Qn.createCanvas()),v={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};bn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=v.dataOffset;v.dataOffset+=1e4;const n=await bn.requestReplayData(e,t);return v.log=v.log.slice(v.logPointer),v.logPointer=0,v.log.push(...n.log),0===n.log.length&&(v.final=!0),n};let C=()=>0;const x=async()=>{if("play"===o){const o=await bn.connect(n,e,t),l=se.decodeGame(o);Ho.setGame(l.id,l),C=()=>V()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=se.decodeGame(t.game);Ho.setGame(n.id,n),v.lastRealTs=V(),v.gameStartTs=parseInt(t.log[0][4],10),v.lastGameTs=v.gameStartTs,C=()=>v.lastGameTs}}vl=!0};await x();const k=Ho.getPieceDrawOffset(e),P=Ho.getPieceDrawSize(e),A=Ho.getPuzzleWidth(e),S=Ho.getPuzzleHeight(e),T=Ho.getTableWidth(e),z=Ho.getTableHeight(e),I={x:(T-A)/2,y:(z-S)/2},D={w:A,h:S},E={w:P,h:P},N=await io.loadPuzzleBitmaps(Ho.getPuzzle(e)),_=new el(w,Ho.getRng(e));_.init();const O=w.getContext("2d");w.classList.add("loaded");const U=function(){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)},a=(e,t)=>{if(n==e)return!1;const l=1-n/e;return o(-t.x*l,-t.y*l),n=e,!0},s=o=>({x:o.x/n-e,y:o.y/n-t}),i=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),r=e=>({w:e.w*n,h:e.h*n}),d=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,reset:()=>{e=0,t=0,n=1},move:o,canZoom:e=>n!=l(e),zoom:(e,t)=>a(l(e),t),setZoom:a,worldToViewport:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:r,viewportToWorld:e=>{const{x:t,y:n}=s(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:s,viewportDimToWorld:e=>{const{w:t,h:n}=d(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:d}}(),B=()=>{U.reset(),U.move(-(T-w.width)/2,-(z-w.height)/2);const e=U.worldDimToViewport(D),t=w.width-40,n=w.height-40;if(e.w>t||e.h>n||e.w{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&w([jt,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&w([Wt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),w([Kt,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Ht:Yt;w([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&w([Xt]),"replay"===o&&("KeyI"===e.code&&w([tn]),"KeyO"===e.code&&w([nn]),"KeyP"===e.code&&w([en])),"KeyF"===e.code&&(fl=!fl,vl=!0),"KeyG"===e.code&&(wl=!wl,vl=!0),"KeyM"===e.code&&w([Jt]),"KeyN"===e.code&&w([on]),"KeyC"===e.code&&w([ln]))}));const w=e=>{l.push(e)};return{addEvent:w,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});w([Ft,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();w([Ht,...e])}}else if(u&&n.canZoom("out")){const e=f||m();w([Yt,...e])}},setHotkeys:e=>{a=e}}}(w,window,U,o),G=Ho.getImageUrl(e),$=()=>{const t=Ho.getStartTs(e),n=Ho.getFinishTs(e),o=C();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(Ho.getFinishedPiecesCount(e)),a.setPiecesTotal(Ho.getPieceCount(e));const L=C();a.setActivePlayers(Ho.getActivePlayers(e,L)),a.setIdlePlayers(Ho.getIdlePlayers(e,L));const F=!!Ho.getFinishTs(e);let j=F;const W=()=>j&&!F,K=()=>cl(tl,100),H=()=>pl(nl,!1),Y=()=>pl(sl,!0),q=()=>{const e=K();i.volume=e/100,i.play()},Q=()=>"replay"===o?hl(ol,"#222222"):Ho.getPlayerBgColor(e,t)||hl(ol,"#222222"),Z=()=>"replay"===o?hl(ll,"#ffffff"):Ho.getPlayerColor(e,t)||hl(ll,"#ffffff");let X="",J="",ee=!1;const te=e=>{ee=e;const[t,n]=e?[X,"grab"]:[J,"default"];w.style.cursor=`url('${t}') ${g} ${m}, ${n}`},ne=e=>{X=Qn.colorizedCanvas(r,c,e).toDataURL(),J=Qn.colorizedCanvas(d,u,e).toDataURL(),te(ee)};ne(Z());const oe=()=>{a.setReplaySpeed&&a.setReplaySpeed(v.speeds[v.speedIdx]),a.setReplayPaused&&a.setReplayPaused(v.paused)},le=()=>{v.speedIdx+1{v.speedIdx>=1&&(v.speedIdx--,oe())},ie=()=>{v.paused=!v.paused,oe()},re=[];let de;let ce;if("play"===o?re.push(setInterval((()=>{$()}),1e3)):"replay"===o&&oe(),"play"===o)bn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case rn:{const n=se.decodePlayer(a);n.id!==t&&(Ho.setPlayer(e,n.id,n),vl=!0)}break;case sn:{const t=se.decodePiece(a);Ho.setPiece(e,t.idx,t),vl=!0}break;case an:Ho.setPuzzleData(e,a),vl=!0}j=!!Ho.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===Gt){const t=o[1];return Ho.addPlayer(e,t,n),!0}if(o[0]===$t){const t=Ho.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Ho.addPlayer(e,t,n),!0}if(o[0]===Lt){const t=Ho.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Ho.handleInput(e,t,l,n),!0}return!1};let n=v.lastGameTs;const o=async()=>{v.logPointer+1>=v.log.length&&await b(e);const l=V();if(v.paused)return v.lastRealTs=l,void(de=setTimeout(o,50));const a=(l-v.lastRealTs)*v.speeds[v.speedIdx];let s=v.lastGameTs+a;for(;;){if(v.paused)break;const e=v.logPointer+1;if(e>=v.log.length)break;const o=v.log[v.logPointer],l=n+o[o.length-1],a=v.log[e],i=a[a.length-1],r=l+i;if(r>s){s+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/o),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{R.createKeyEvents();for(const n of R.consumeAll())if("play"===o){const o=n[0];if(o===Ft){const e=n[1],t=n[2],o=U.worldDimToViewport({w:e,h:t});vl=!0,U.move(o.w,o.h)}else if(o===Kt){if(ue&&!Ho.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-ue.x),l=Math.round(t.y-ue.y);vl=!0,U.move(o,l),ue=t}}else if(o===Qt)ne(n[1]);else if(o===jt){const e={x:n[1],y:n[2]};ue=U.worldToViewport(e),te(!0)}else if(o===Wt)ue=null,te(!1);else if(o===Ht){const e={x:n[1],y:n[2]};vl=!0,U.zoom("in",U.worldToViewport(e))}else if(o===Yt){const e={x:n[1],y:n[2]};vl=!0,U.zoom("out",U.worldToViewport(e))}else o===Xt?a.togglePreview():o===Jt?a.toggleSoundsEnabled():o===on?a.togglePlayerNames():o===ln&&B();const l=C();Ho.handleInput(e,t,n,l,(e=>{H()&&q()})).length>0&&(vl=!0),bn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===en)ie();else if(e===nn)ae();else if(e===tn)le();else if(e===Ft){const e=n[1],t=n[2];vl=!0,U.move(e,t)}else if(e===Kt){if(ue){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-ue.x),l=Math.round(t.y-ue.y);vl=!0,U.move(o,l),ue=t}}else if(e===Qt)ne(n[1]);else if(e===jt){const e={x:n[1],y:n[2]};ue=U.worldToViewport(e),te(!0)}else if(e===Wt)ue=null,te(!1);else if(e===Ht){const e={x:n[1],y:n[2]};vl=!0,U.zoom("in",U.worldToViewport(e))}else if(e===Yt){const e={x:n[1],y:n[2]};vl=!0,U.zoom("out",U.worldToViewport(e))}else e===Xt?a.togglePreview():e===Jt?a.toggleSoundsEnabled():e===on?a.togglePlayerNames():e===ln&&B()}j=!!Ho.getFinishTs(e),W()&&(_.update(),vl=!0)},render:async()=>{if(!vl)return;const n=C();let l,s,i;window.DEBUG&&eo(0),O.fillStyle=Q(),O.fillRect(0,0,w.width,w.height),window.DEBUG&&to("clear done"),l=U.worldToViewportRaw(I),s=U.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&to("board done");const r=Ho.getPiecesSortedByZIndex(e);window.DEBUG&&to("get tiles done"),s=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?fl:wl)&&(i=N[e.idx],l=U.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&to("tiles done");const d=[];for(const a of Ho.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(i=await f(a),l=U.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),Y()&&d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,o]of d)O.fillText(e,t,o);window.DEBUG&&to("players done"),a.setActivePlayers(Ho.getActivePlayers(e,n)),a.setIdlePlayers(Ho.getIdlePlayers(e,n)),a.setPiecesDone(Ho.getFinishedPiecesCount(e)),window.DEBUG&&to("HUD done"),W()&&_.render(),vl=!1}}),{setHotkeys:e=>{R.setHotkeys(e)},onBgChange:e=>{gl(ol,e),R.addEvent([qt,e])},onColorChange:e=>{gl(ll,e),R.addEvent([Qt,e])},onNameChange:e=>{gl(al,e),R.addEvent([Zt,e])},onSoundsEnabledChange:e=>{ul(nl,e)},onSoundsVolumeChange:e=>{dl(tl,e),q()},onShowPlayerNamesChange:e=>{ul(sl,e)},replayOnSpeedUp:le,replayOnSpeedDown:ae,replayOnPauseToggle:ie,previewImageUrl:G,player:{background:Q(),color:Z(),name:"replay"===o?hl(al,"anon"):Ho.getPlayerName(e,t)||hl(al,"anon"),soundsEnabled:H(),soundsVolume:K(),showPlayerNames:Y()},disconnect:bn.disconnect,connect:x,unload:()=>{re.forEach((e=>{clearInterval(e)})),de&&clearTimeout(de),ce&&ce.stop()}}}var Cl=e({name:"game",components:{PuzzleStatus:xt,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,ConnectionOverlay:Cn,HelpOverlay:Tn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await bl(`${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},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 xl={id:"game"},kl={class:"menu"},Pl={class:"tabs"},Al=i("🧩 Puzzles");Cl.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),w=a("scores");return s(),t("div",xl,[p(n(u,{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"]),[[x,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",kl,[n("div",Pl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Al])),_: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))},"ℹ️ Hotkeys")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var Sl=e({name:"replay",components:{PuzzleStatus:xt,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,HelpOverlay:Tn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await bl(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 Tl={id:"replay"},zl={class:"menu"},Il={class:"tabs"},Dl=i("🧩 Puzzles");Sl.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),w=a("scores");return s(),t("div",Tl,[p(n(g,{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"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick: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",zl,[n("div",Il,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Dl])),_: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))},"ℹ️ Hotkeys")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=k({history:P(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:it},{name:"game",path:"/g/:id",component:Cl},{name:"replay",path:"/replay/:id",component:Sl}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=A(S);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=se.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); diff --git a/build/public/assets/index.c8fb176c.js b/build/public/assets/index.c8fb176c.js deleted file mode 100644 index 120d472..0000000 --- a/build/public/assets/index.c8fb176c.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as w,m as v,n as b,q as x,s as C,u as k,x as P,y as A}from"./vendor.684f7bc8.js";var S=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const T={id:"app"},z={key:0,class:"nav"},I=i("Games overview"),E=i("New game");S.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",T,[e.showNav?(s(),t("ul",z,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[E])),_:1})])])):l("",!0),n(g)])};const D=864e5,N=e=>{const t=Math.floor(e/D);e%=D;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 M=1,_=1e3,V=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>N(t-e),U=N,B=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||V();return`${n} ${O(o,l)}`}}});const R={class:"game-info-text"},G=n("br",null,null,-1),$=n("br",null,null,-1),L=n("br",null,null,-1),F=i(" ↪️ Watch replay ");B.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),G,i(" 👥 "+r(e.game.players),1),$,i(" "+r(e.time(e.game.started,e.game.finished)),1),L])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[F])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:B},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 W=n("h1",null,"Running games",-1),K=n("h1",null,"Finished games",-1);j.render=function(e,o,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(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)),K,(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 H=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});H.render=function(e,o,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y,q,Q,Z,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:H},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(q=Y||(Y={}))[q.Flat=0]="Flat",q[q.Out=1]="Out",q[q.In=-1]="In",(Z=Q||(Q={}))[Z.FINAL=0]="FINAL",Z[Z.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),s=le(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var se={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Q.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const ie={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};ie.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-a4fa5e7e");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ce,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-a4fa5e7e";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{autocompleteTags:{type:Function},uploadProgress:{type:Number},uploading:{type:String}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{uploadProgressPercent(){return this.uploadProgress?Math.round(100*this.uploadProgress):0},canPostToGallery(){return!this.uploading&&!(!this.previewUrl||!this.file)},canSetupGameClick(){return!this.uploading&&!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=n("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},we=n("span",{class:"btn"},"Upload File",-1),ve={class:"area-settings"},be=n("td",null,[n("label",null,"Title")],-1),xe=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ce=n("td",null,[n("label",null,"Tags")],-1),ke={class:"area-buttons"},Pe=i("🖼️ Post to gallery"),Ae=i("🧩 Post to gallery "),Se=n("br",null,null,-1),Te=i(" + set up game");ge.render=function(e,o,l,c,h,m){const y=a("responsive-image"),f=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(s(),t("div",me,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(y,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",ye,[n("label",fe,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),we])]))],34),n("div",ve,[n("table",null,[n("tr",null,[be,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),xe,n("tr",null,[Ce,n("td",null,[n(f,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",ke,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},["postToGallery"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Pe],64))],8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},["setupGame"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Ae,Se,Te],64))],8,["disabled"])])])])};var ze=e({name:"edit-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Ie={class:"area-image"},Ee={class:"has-image"},De={class:"area-settings"},Ne=n("td",null,[n("label",null,"Title")],-1),Me=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),_e=n("td",null,[n("label",null,"Tags")],-1),Ve={class:"area-buttons"};ze.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Ie,[n("div",Ee,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",De,[n("table",null,[n("tr",null,[Ne,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Me,n("tr",null,[_e,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Ve,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Oe=e({name:"new-game-dialog",components:{ResponsiveImage:ie},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Q.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Ue={class:"area-image"},Be={class:"has-image"},Re={key:0,class:"image-title"},Ge={key:0,class:"image-title-title"},$e={key:1,class:"image-title-dim"},Le={class:"area-settings"},Fe=n("td",null,[n("label",null,"Pieces")],-1),je=n("td",null,[n("label",null,"Scoring: ")],-1),We=i(" Any (Score when pieces are connected to each other or on final location)"),Ke=n("br",null,null,-1),He=i(" Final (Score when pieces are put to their final location)"),Ye=n("td",null,[n("label",null,"Shapes: ")],-1),qe=i(" Normal"),Qe=n("br",null,null,-1),Ze=i(" Any (flat pieces can occur anywhere)"),Xe=n("br",null,null,-1),Je=i(" Flat (all pieces flat on all sides)"),et=n("td",null,[n("label",null,"Snapping: ")],-1),tt=i(" Normal (pieces snap to final destination and to each other)"),nt=n("br",null,null,-1),ot=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),lt={class:"area-buttons"};Oe.render=function(e,o,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Ue,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title||e.image.width||e.image.height?(s(),t("div",Re,[e.image.title?(s(),t("span",Ge,'"'+r(e.image.title)+'"',1)):l("",!0),e.image.width||e.image.height?(s(),t("span",$e,"("+r(e.image.width)+" ✕ "+r(e.image.height)+")",1)):l("",!0)])):l("",!0)]),n("div",Le,[n("table",null,[n("tr",null,[Fe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[je,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[w,e.scoreMode]]),We]),Ke,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[w,e.scoreMode]]),He])])]),n("tr",null,[Ye,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[w,e.shapeMode]]),qe]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[w,e.shapeMode]]),Ze]),Xe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[w,e.shapeMode]]),Je])])]),n("tr",null,[et,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[w,e.snapMode]]),tt]),nt,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[w,e.snapMode]]),ot])])])])]),n("div",lt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};const at=async(e,t,n)=>new Promise(((o,l)=>{const a=new window.XMLHttpRequest;a.open(e,t,!0),a.withCredentials=!0;for(const e in n.headers||{})a.setRequestHeader(e,n.headers[e]);a.addEventListener("load",(function(e){o({status:this.status,text:this.responseText,json:async()=>JSON.parse(this.responseText)})})),a.addEventListener("error",(function(e){l(new Error("xhr error"))})),a.upload&&n.onUploadProgress&&a.upload.addEventListener("progress",(function(e){n.onUploadProgress&&n.onUploadProgress(e)})),a.send(n.body)}));var st=(e,t)=>at("post",e,t),it=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:ze,NewGameDialog:Oe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:"",uploading:"",uploadProgress:0}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${se.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){this.uploadProgress=0;const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await st("/api/upload",{body:t,onUploadProgress:e=>{this.uploadProgress=e.loaded/e.total}});return this.uploadProgress=1,await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){this.uploading="postToGallery",await this.uploadImage(e),this.uploading="",this.dialog="",await this.loadImages()},async setupGameClick(e){this.uploading="setupGame";const t=await this.uploadImage(e);this.uploading="",this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const rt={class:"upload-image-teaser"},dt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),ct={key:0},ut=i(" Tags: "),pt=i(" Sort by: "),gt=n("option",{value:"date_desc"},"Newest first",-1),ht=n("option",{value:"date_asc"},"Oldest first",-1),mt=n("option",{value:"alpha_asc"},"A-Z",-1),yt=n("option",{value:"alpha_desc"},"Z-A",-1);it.render=function(e,o,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),w=a("new-game-dialog");return s(),t("div",null,[n("div",rt,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),dt]),n("div",null,[e.tags.length>0?(s(),t("label",ct,[ut,(s(!0),t(d,null,c(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[pt,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[gt,ht,mt,yt],544),[[v,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),uploadProgress:e.uploadProgress,uploading:e.uploading,onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","uploadProgress","uploading","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(w,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ft=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 wt={class:"scores"},vt=n("div",null,"Scores",-1),bt=n("td",null,"⚡",-1),xt=n("td",null,"💤",-1);ft.render=function(e,o,l,a,i,u){return s(),t("div",wt,[vt,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}},[xt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var Ct=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 U(this.duration)}}});const kt={class:"timer"};Ct.render=function(e,o,l,a,i,d){return s(),t("div",kt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var Pt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const At=m();y("data-v-4d56fc17");const St=n("td",null,[n("label",null,"Background: ")],-1),Tt=n("td",null,[n("label",null,"Color: ")],-1),zt=n("td",null,[n("label",null,"Name: ")],-1),It=n("td",null,[n("label",null,"Sounds: ")],-1),Et=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Dt={class:"sound-volume"},Nt=n("td",null,[n("label",null,"Show player names: ")],-1);f();const Mt=At(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[10]||(o[10]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[9]||(o[9]=u((()=>{}),["stop"]))},[n("tr",null,[St,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[Tt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[zt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[It,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[x,e.modelValue.soundsEnabled]])])]),n("tr",null,[Et,n("td",Dt,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])]),n("tr",null,[Nt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[8]||(o[8]=t=>e.modelValue.showPlayerNames=t)},null,512),[[x,e.modelValue.showPlayerNames]])])])])]))));Pt.render=Mt,Pt.__scopeId="data-v-4d56fc17";var _t=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const Vt={class:"preview"};_t.render=function(e,o,l,a,i,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",Vt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Ot=1,Ut=4,Bt=2,Rt=3,Gt=2,$t=4,Lt=3,Ft=9,jt=1,Wt=2,Kt=3,Ht=4,Yt=5,qt=6,Qt=7,Zt=8,Xt=10,Jt=11,en=12,tn=13,nn=14,on=15,ln=1,an=2,sn=3;const rn=ae("Communication.js");let dn,cn=[],un=e=>{cn.push(e)},pn=[],gn=e=>{pn.push(e)};let hn=0;const mn=e=>{hn!==e&&(hn=e,gn(e))};function yn(e){if(2===hn)try{dn.send(JSON.stringify(e))}catch(t){rn.info("unable to send message.. maybe because ws is invalid?")}}let fn,wn;var vn={connect:function(e,t,n){return fn=0,wn={},mn(3),new Promise((o=>{dn=new WebSocket(e,n+"|"+t),dn.onopen=()=>{mn(2),yn([Rt])},dn.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Ut){const e=t[1];o(e)}else{if(l!==Ot)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&wn[o])return void delete wn[o];un(t)}}},dn.onerror=()=>{throw mn(1),"[ 2021-05-15 onerror ]"},dn.onclose=e=>{4e3===e.code||1001===e.code?mn(4):mn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${se.asQueryArgs(n)}`);return await o.json()},disconnect:function(){dn&&dn.close(4e3),fn=0,wn={}},sendClientEvent:function(e){fn++,wn[fn]=e,yn([Bt,fn,wn[fn]])},onServerChange:function(e){un=e;for(const t of cn)un(t);cn=[]},onConnectionStateChange:function(e){gn=e;for(const t of pn)gn(t);pn=[]},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},bn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===vn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===vn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const xn={key:0,class:"overlay connection-lost"},Cn={key:0,class:"overlay-content"},kn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Pn={key:1,class:"overlay-content"},An=n("div",null,"Connecting...",-1);bn.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",xn,[e.lostConnection?(s(),t("div",Cn,[kn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",Pn,[An])):l("",!0)])):l("",!0)};var Sn=e({name:"help-overlay",emits:{bgclick:null}});const Tn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),zn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),In=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),En=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),Dn=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Nn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Mn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),_n=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Vn=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),Un=n("tr",null,[n("td",null,"👤 Toggle player names:"),n("td",null,[n("div",null,[n("kbd",null,"N")])])],-1),Bn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),Rn=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),Gn=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),$n=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);Sn.render=function(e,o,l,a,i,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]=u((()=>{}),["stop"]))},[Tn,zn,In,En,Dn,Nn,Mn,_n,Vn,On,Un,Bn,Rn,Gn,$n])])};var Ln=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Fn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),jn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Wn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Kn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Hn(){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)},a=o=>({x:o.x/n-e,y:o.y/n-t}),s=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),i=e=>({w:e.w*n,h:e.h*n}),r=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,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}=i(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:i,viewportToWorld:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:a,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function Yn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var qn={createCanvas:Yn,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=Yn(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=Yn(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(),o}};const Qn=ae("Debug.js");let Zn=0,Xn=0;var Jn=e=>{Zn=performance.now(),Xn=e},eo=e=>{const t=performance.now(),n=t-Zn;n>Xn&&Qn.log(e+": "+n),Zn=t};function to(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function no(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var oo={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:to,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:no,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 to(no(e),no(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const lo=ae("PuzzleGraphics.js");function ao(e,t){const n=se.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var so={loadPuzzleBitmaps:async function(e){const t=await qn.loadImageToBitmap(e.info.imageUrl),n=await qn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){lo.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=oo.pointAdd(a,{x:o,y:0}),c=oo.pointAdd(r,{x:0,y:o}),u=oo.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;ose.decodePiece(io[e].puzzle.tiles[t]),ko=(e,t)=>Co(e,t).group,Po=(e,t)=>{const n=io[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},Ao=(e,t)=>{const n=io[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=io[e].puzzle.info,o=se.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return oo.pointAdd(o,l)},So=(e,t)=>Co(e,t).pos,To=e=>{const t=jo(e),n=Wo(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}},zo=(e,t)=>{const n=No(e),o=Co(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Io=(e,t)=>Co(e,t).z,Eo=(e,t)=>{for(const n of io[e].puzzle.tiles){const e=se.decodePiece(n);if(e.owner===t)return e.idx}return-1},Do=e=>io[e].puzzle.info.tileDrawSize,No=e=>io[e].puzzle.info.tileSize,Mo=e=>io[e].puzzle.data.maxGroup,_o=e=>io[e].puzzle.data.maxZ;function Vo(e,t){const n=io[e].puzzle.info,o=se.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Oo=(e,t,n)=>{for(const o of t)xo(e,o,{z:n})},Uo=(e,t,n)=>{const o=So(e,t);xo(e,t,{pos:oo.pointAdd(o,n)})},Bo=(e,t,n)=>{const o=Do(e),l=To(e),a=n;for(const s of t){const t=Co(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)Uo(e,s,a)},Ro=(e,t)=>Co(e,t).owner,Go=(e,t)=>{for(const n of t)xo(e,n,{owner:-1,z:1})},$o=(e,t,n)=>{for(const o of t)xo(e,o,{owner:n})};function Lo(e,t){const n=io[e].puzzle.tiles,o=se.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=se.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const Fo=(e,t)=>{const n=co(e,t);return n?n.points:0},jo=e=>io[e].puzzle.info.table.width,Wo=e=>io[e].puzzle.info.table.height;var Ko={setGame:function(e,t){io[e]=t},exists:function(e){return!!io[e]||!1},playerExists:po,getActivePlayers:function(e,t){const n=t-30*_;return go(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return go(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){po(e,t)?vo(e,t,{ts:n}):uo(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:wo,getPieceCount:ho,getImageUrl:function(e){return io[e].puzzle.info.imageUrl},setImageUrl:function(e,t){io[e].puzzle.info.imageUrl=t},get:function(e){return io[e]||null},getAllGames:function(){return Object.values(io).sort(((e,t)=>fo(e.id)===fo(t.id)?t.puzzle.data.started-e.puzzle.data.started:fo(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=co(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=co(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=co(e,t);return n?n.name:null},getPlayerIndexById:ro,getPlayerIdByIndex:function(e,t){return io[e].players.length>t?se.decodePlayer(io[e].players[t]).id:null},changePlayer:vo,setPlayer:uo,setPiece:function(e,t,n){io[e].puzzle.tiles[t]=se.encodePiece(n)},setPuzzleData:function(e,t){io[e].puzzle.data=t},getTableWidth:jo,getTableHeight:Wo,getPuzzle:e=>io[e].puzzle,getRng:e=>io[e].rng.obj,getPuzzleWidth:e=>io[e].puzzle.info.width,getPuzzleHeight:e=>io[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return io[e].puzzle.tiles.map(se.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Eo(e,t);return n<0?null:io[e].puzzle.tiles[n]},getPieceDrawOffset:e=>io[e].puzzle.info.tileDrawOffset,getPieceDrawSize:Do,getFinalPiecePos:Ao,getStartTs:e=>io[e].puzzle.data.started,getFinishTs:e=>io[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=io[e].puzzle,s=function(e,t){return t in io[e].evtInfos?io[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([ln,a.data])},d=t=>{i.push([an,se.encodePiece(Co(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=co(e,t);n&&i.push([sn,se.encodePlayer(n)])},p=n[0];if(p===qt){const l=n[1];vo(e,t,{bgcolor:l,ts:o}),u()}else if(p===Qt){const l=n[1];vo(e,t,{color:l,ts:o}),u()}else if(p===Zt){const l=`${n[1]}`.substr(0,16);vo(e,t,{name:l,ts:o}),u()}else if(p===Ft){const l=n[1],a=n[2],s=co(e,t);if(s){const n=s.x-l,i=s.y-a;vo(e,t,{ts:o,x:n,y:i}),u()}}else if(p===jt){const l={x:n[1],y:n[2]};vo(e,t,{d:1,ts:o}),u(),s._last_mouse_down=l;const a=((e,t)=>{const n=io[e].puzzle.info,o=io[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=_o(e)+1;bo(e,{maxZ:n}),r();const o=Lo(e,a);Oo(e,o,_o(e)),$o(e,o,t),c(o)}s._last_mouse=l}else if(p===Kt){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)vo(e,t,{x:l,y:a,ts:o}),u();else{const n=Eo(e,t);if(n>=0){vo(e,t,{x:l,y:a,ts:o}),u();const r=Lo(e,n);let d=oo.pointInBounds(i,To(e))&&oo.pointInBounds(s._last_mouse_down,To(e));for(const t of r){const n=zo(e,t);if(oo.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Bo(e,r,{x:t,y:n}),c(r)}}else vo(e,t,{ts:o}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===Wt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Eo(e,t);if(g>=0){const n=Lo(e,g);$o(e,n,0),c(n);const s=So(e,g),i=Ao(e,g);let h=!1;if(yo(e)===ee.REAL){for(const t of n)if(Po(e,t)){h=!0;break}}else h=!0;if(h&&oo.pointDistance(i,s){const l=io[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=ko(e,t),l=ko(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=So(e,t),s=oo.pointAdd(So(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(oo.pointDistance(a,s){const o=io[e].puzzle.tiles,l=ko(e,t),a=ko(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(bo(e,{maxGroup:Mo(e)+1}),r(),s=Mo(e));if(xo(e,t,{group:s}),d(t),xo(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=se.decodePiece(r);i.includes(t.group)&&(xo(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=Lo(e,t),((e,t)=>-1===Ro(e,t))(e,n))Go(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=Io(e,o);t>n&&(n=t)}return n})(e,l);Oo(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Lo(e,g)){const o=Vo(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])){a=!0;break}}if(a&&mo(e)===Q.ANY){const n=Fo(e,t)+1;vo(e,t,{d:p,ts:o,points:n}),u()}else vo(e,t,{d:p,ts:o}),u();a&&yo(e)===ee.REAL&&wo(e)===ho(e)&&(bo(e,{finished:o}),r()),a&&l&&l(t)}}else vo(e,t,{d:p,ts:o}),u();s._last_mouse=i}else if(p===Ht){const l=n[1],a=n[2];vo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else if(p===Yt){const l=n[1],a=n[2];vo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else vo(e,t,{ts:o}),u();return function(e,t,n){io[e].evtInfos[t]=n}(e,t,s),i}};let Ho=-10,Yo=20,qo=2,Qo=15;class Zo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Ho+Math.random()*Yo,this.vy=-1*(qo+Math.random()*Qo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;qo=t/2,Qo=t-qo;const n=1/4*this.canvas.width/(t/2);Ho=-n,Yo=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Zo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Zo(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{localStorage.setItem(e,t)},il=e=>localStorage.getItem(e);var rl=(e,t)=>{sl(e,`${t}`)},dl=(e,t)=>{const n=il(e);if(null===n)return t;const o=parseInt(n,10);return isNaN(o)?t:o},cl=(e,t)=>{sl(e,t?"1":"0")},ul=(e,t)=>{const n=il(e);return null===n?t:"1"===n},pl=(e,t)=>{sl(e,t)},gl=(e,t)=>{const n=il(e);return null===n?t:n};const hl={"./grab.png":Fn,"./grab_mask.png":jn,"./hand.png":Wn,"./hand_mask.png":Kn},ml={"./click.mp3":Ln};let yl=!0,fl=!0;let wl=!0;async function vl(e,t,n,o,l,a){void 0===window.DEBUG&&(window.DEBUG=!1);const s=ml["./click.mp3"].default,i=new Audio(s),r=await qn.loadImageToBitmap(hl["./grab.png"].default),d=await qn.loadImageToBitmap(hl["./hand.png"].default),c=await qn.loadImageToBitmap(hl["./grab_mask.png"].default),u=await qn.loadImageToBitmap(hl["./hand_mask.png"].default),p=r.width,g=Math.round(p/2),h=r.height,m=Math.round(h/2),y={},f=async e=>{const t=e.color+" "+e.d;if(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(qn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},w=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,wl=!0})),t}(l,qn.createCanvas()),v={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};vn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=v.dataOffset;v.dataOffset+=1e4;const n=await vn.requestReplayData(e,t);return v.log=v.log.slice(v.logPointer),v.logPointer=0,v.log.push(...n.log),0===n.log.length&&(v.final=!0),n};let x=()=>0;const C=async()=>{if("play"===o){const o=await vn.connect(n,e,t),l=se.decodeGame(o);Ko.setGame(l.id,l),x=()=>V()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=se.decodeGame(t.game);Ko.setGame(n.id,n),v.lastRealTs=V(),v.gameStartTs=parseInt(t.log[0][4],10),v.lastGameTs=v.gameStartTs,x=()=>v.lastGameTs}}wl=!0};await C();const k=Ko.getPieceDrawOffset(e),P=Ko.getPieceDrawSize(e),A=Ko.getPuzzleWidth(e),S=Ko.getPuzzleHeight(e),T=Ko.getTableWidth(e),z=Ko.getTableHeight(e),I={x:(T-A)/2,y:(z-S)/2},E={w:A,h:S},D={w:P,h:P},N=await so.loadPuzzleBitmaps(Ko.getPuzzle(e)),_=new Jo(w,Ko.getRng(e));_.init();const O=w.getContext("2d");w.classList.add("loaded");const U=Hn();U.move(-(T-w.width)/2,-(z-w.height)/2);const B=function(e,t,n,o){let l=[],a=!0,s=!1,i=!1,r=!1,d=!1,c=!1,u=!1,p=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&w([jt,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&w([Wt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),w([Kt,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Ht:Yt;w([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&w([Xt]),"replay"===o&&("KeyI"===e.code&&w([tn]),"KeyO"===e.code&&w([nn]),"KeyP"===e.code&&w([en])),"KeyF"===e.code&&(yl=!yl,wl=!0),"KeyG"===e.code&&(fl=!fl,wl=!0),"KeyM"===e.code&&w([Jt]),"KeyN"===e.code&&w([on]))}));const w=e=>{l.push(e)};return{addEvent:w,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});w([Ft,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();w([Ht,...e])}}else if(u&&n.canZoom("out")){const e=f||m();w([Yt,...e])}},setHotkeys:e=>{a=e}}}(w,window,U,o),R=Ko.getImageUrl(e),G=()=>{const t=Ko.getStartTs(e),n=Ko.getFinishTs(e),o=x();a.setFinished(!!n),a.setDuration((n||o)-t)};G(),a.setPiecesDone(Ko.getFinishedPiecesCount(e)),a.setPiecesTotal(Ko.getPieceCount(e));const $=x();a.setActivePlayers(Ko.getActivePlayers(e,$)),a.setIdlePlayers(Ko.getIdlePlayers(e,$));const L=!!Ko.getFinishTs(e);let F=L;const j=()=>F&&!L,W=()=>dl(el,100),K=()=>ul(tl,!1),H=()=>ul(al,!0),Y=()=>{const e=W();i.volume=e/100,i.play()},q=()=>"replay"===o?gl(nl,"#222222"):Ko.getPlayerBgColor(e,t)||gl(nl,"#222222"),Q=()=>"replay"===o?gl(ol,"#ffffff"):Ko.getPlayerColor(e,t)||gl(ol,"#ffffff");let Z="",X="",J=!1;const ee=e=>{J=e;const[t,n]=e?[Z,"grab"]:[X,"default"];w.style.cursor=`url('${t}') ${g} ${m}, ${n}`},te=e=>{Z=qn.colorizedCanvas(r,c,e).toDataURL(),X=qn.colorizedCanvas(d,u,e).toDataURL(),ee(J)};te(Q());const ne=()=>{a.setReplaySpeed&&a.setReplaySpeed(v.speeds[v.speedIdx]),a.setReplayPaused&&a.setReplayPaused(v.paused)},oe=()=>{v.speedIdx+1{v.speedIdx>=1&&(v.speedIdx--,ne())},ae=()=>{v.paused=!v.paused,ne()},ie=[];let re;let de;if("play"===o?ie.push(setInterval((()=>{G()}),1e3)):"replay"===o&&ne(),"play"===o)vn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case sn:{const n=se.decodePlayer(a);n.id!==t&&(Ko.setPlayer(e,n.id,n),wl=!0)}break;case an:{const t=se.decodePiece(a);Ko.setPiece(e,t.idx,t),wl=!0}break;case ln:Ko.setPuzzleData(e,a),wl=!0}F=!!Ko.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===Gt){const t=o[1];return Ko.addPlayer(e,t,n),!0}if(o[0]===$t){const t=Ko.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Ko.addPlayer(e,t,n),!0}if(o[0]===Lt){const t=Ko.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Ko.handleInput(e,t,l,n),!0}return!1};let n=v.lastGameTs;const o=async()=>{v.logPointer+1>=v.log.length&&await b(e);const l=V();if(v.paused)return v.lastRealTs=l,void(re=setTimeout(o,50));const a=(l-v.lastRealTs)*v.speeds[v.speedIdx];let s=v.lastGameTs+a;for(;;){if(v.paused)break;const e=v.logPointer+1;if(e>=v.log.length)break;const o=v.log[v.logPointer],l=n+o[o.length-1],a=v.log[e],i=a[a.length-1],r=l+i;if(r>s){s+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/o),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{B.createKeyEvents();for(const n of B.consumeAll())if("play"===o){const o=n[0];if(o===Ft){const e=n[1],t=n[2],o=U.worldDimToViewport({w:e,h:t});wl=!0,U.move(o.w,o.h)}else if(o===Kt){if(ce&&!Ko.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-ce.x),l=Math.round(t.y-ce.y);wl=!0,U.move(o,l),ce=t}}else if(o===Qt)te(n[1]);else if(o===jt){const e={x:n[1],y:n[2]};ce=U.worldToViewport(e),ee(!0)}else if(o===Wt)ce=null,ee(!1);else if(o===Ht){const e={x:n[1],y:n[2]};wl=!0,U.zoom("in",U.worldToViewport(e))}else if(o===Yt){const e={x:n[1],y:n[2]};wl=!0,U.zoom("out",U.worldToViewport(e))}else o===Xt?a.togglePreview():o===Jt?a.toggleSoundsEnabled():o===on&&a.togglePlayerNames();const l=x();Ko.handleInput(e,t,n,l,(e=>{K()&&Y()})).length>0&&(wl=!0),vn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===en)ae();else if(e===nn)le();else if(e===tn)oe();else if(e===Ft){const e=n[1],t=n[2];wl=!0,U.move(e,t)}else if(e===Kt){if(ce){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-ce.x),l=Math.round(t.y-ce.y);wl=!0,U.move(o,l),ce=t}}else if(e===Qt)te(n[1]);else if(e===jt){const e={x:n[1],y:n[2]};ce=U.worldToViewport(e),ee(!0)}else if(e===Wt)ce=null,ee(!1);else if(e===Ht){const e={x:n[1],y:n[2]};wl=!0,U.zoom("in",U.worldToViewport(e))}else if(e===Yt){const e={x:n[1],y:n[2]};wl=!0,U.zoom("out",U.worldToViewport(e))}else e===Xt?a.togglePreview():e===Jt?a.toggleSoundsEnabled():e===on&&a.togglePlayerNames()}F=!!Ko.getFinishTs(e),j()&&(_.update(),wl=!0)},render:async()=>{if(!wl)return;const n=x();let l,s,i;window.DEBUG&&Jn(0),O.fillStyle=q(),O.fillRect(0,0,w.width,w.height),window.DEBUG&&eo("clear done"),l=U.worldToViewportRaw(I),s=U.worldDimToViewportRaw(E),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&eo("board done");const r=Ko.getPiecesSortedByZIndex(e);window.DEBUG&&eo("get tiles done"),s=U.worldDimToViewportRaw(D);for(const e of r)(-1===e.owner?yl:fl)&&(i=N[e.idx],l=U.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&eo("tiles done");const d=[];for(const a of Ko.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(i=await f(a),l=U.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),H()&&d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,o]of d)O.fillText(e,t,o);window.DEBUG&&eo("players done"),a.setActivePlayers(Ko.getActivePlayers(e,n)),a.setIdlePlayers(Ko.getIdlePlayers(e,n)),a.setPiecesDone(Ko.getFinishedPiecesCount(e)),window.DEBUG&&eo("HUD done"),j()&&_.render(),wl=!1}}),{setHotkeys:e=>{B.setHotkeys(e)},onBgChange:e=>{pl(nl,e),B.addEvent([qt,e])},onColorChange:e=>{pl(ol,e),B.addEvent([Qt,e])},onNameChange:e=>{pl(ll,e),B.addEvent([Zt,e])},onSoundsEnabledChange:e=>{cl(tl,e)},onSoundsVolumeChange:e=>{rl(el,e),Y()},onShowPlayerNamesChange:e=>{cl(al,e)},replayOnSpeedUp:oe,replayOnSpeedDown:le,replayOnPauseToggle:ae,previewImageUrl:R,player:{background:q(),color:Q(),name:"replay"===o?gl(ll,"anon"):Ko.getPlayerName(e,t)||gl(ll,"anon"),soundsEnabled:K(),soundsVolume:W(),showPlayerNames:H()},disconnect:vn.disconnect,connect:C,unload:()=>{ie.forEach((e=>{clearInterval(e)})),re&&clearTimeout(re),de&&de.stop()}}}var bl=e({name:"game",components:{PuzzleStatus:Ct,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,ConnectionOverlay:bn,HelpOverlay:Sn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await vl(`${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},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 xl={id:"game"},Cl={class:"menu"},kl={class:"tabs"},Pl=i("🧩 Puzzles");bl.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),w=a("scores");return s(),t("div",xl,[p(n(u,{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"]),[[C,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",Cl,[n("div",kl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Pl])),_: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))},"ℹ️ Hotkeys")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var Al=e({name:"replay",components:{PuzzleStatus:Ct,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,HelpOverlay:Sn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await vl(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 Sl={id:"replay"},Tl={class:"menu"},zl={class:"tabs"},Il=i("🧩 Puzzles");Al.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),w=a("scores");return s(),t("div",Sl,[p(n(g,{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"]),[[C,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[C,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick: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",Tl,[n("div",zl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Il])),_: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))},"ℹ️ Hotkeys")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=k({history:P(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:it},{name:"game",path:"/g/:id",component:bl},{name:"replay",path:"/replay/:id",component:Al}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=A(S);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=se.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 55441fe..d0cec69 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,7 +4,7 @@ 🧩 jigsaw.hyottoko.club - + diff --git a/build/server/main.js b/build/server/main.js index c2283d3..4a39918 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -362,6 +362,7 @@ const INPUT_EV_REPLAY_TOGGLE_PAUSE = 12; const INPUT_EV_REPLAY_SPEED_UP = 13; const INPUT_EV_REPLAY_SPEED_DOWN = 14; const INPUT_EV_TOGGLE_PLAYER_NAMES = 15; +const INPUT_EV_CENTER_FIT_PUZZLE = 16; const CHANGE_DATA = 1; const CHANGE_TILE = 2; const CHANGE_PLAYER = 3; @@ -389,6 +390,7 @@ var Protocol = { INPUT_EV_REPLAY_SPEED_UP, INPUT_EV_REPLAY_SPEED_DOWN, INPUT_EV_TOGGLE_PLAYER_NAMES, + INPUT_EV_CENTER_FIT_PUZZLE, CHANGE_DATA, CHANGE_TILE, CHANGE_PLAYER, diff --git a/src/common/Protocol.ts b/src/common/Protocol.ts index 24b1845..0370c1d 100644 --- a/src/common/Protocol.ts +++ b/src/common/Protocol.ts @@ -65,6 +65,7 @@ const INPUT_EV_REPLAY_SPEED_UP = 13 const INPUT_EV_REPLAY_SPEED_DOWN = 14 const INPUT_EV_TOGGLE_PLAYER_NAMES = 15 +const INPUT_EV_CENTER_FIT_PUZZLE = 16 const CHANGE_DATA = 1 const CHANGE_TILE = 2 @@ -101,6 +102,7 @@ export default { INPUT_EV_REPLAY_SPEED_DOWN, INPUT_EV_TOGGLE_PLAYER_NAMES, + INPUT_EV_CENTER_FIT_PUZZLE, CHANGE_DATA, CHANGE_TILE, diff --git a/src/frontend/Camera.ts b/src/frontend/Camera.ts index a336205..1ef347f 100644 --- a/src/frontend/Camera.ts +++ b/src/frontend/Camera.ts @@ -11,6 +11,12 @@ export default function Camera () { let y = 0 let curZoom = 1 + const reset = () => { + x = 0 + y = 0 + curZoom = 1 + } + const move = (byX: number, byY: number) => { x += byX / curZoom y += byY / curZoom @@ -130,9 +136,11 @@ export default function Camera () { return { getCurrentZoom: () => curZoom, + reset, move, canZoom, zoom, + setZoom, worldToViewport, worldToViewportRaw, worldDimToViewport, // not used outside diff --git a/src/frontend/components/HelpOverlay.vue b/src/frontend/components/HelpOverlay.vue index e49efd8..3492d40 100644 --- a/src/frontend/components/HelpOverlay.vue +++ b/src/frontend/components/HelpOverlay.vue @@ -10,6 +10,8 @@ 🔍+ Zoom in:
E/🖱️-Wheel
🔍- Zoom out:
Q/🖱️-Wheel
🖼️ Toggle preview:
Space
+ 🎯 Center puzzle in screen:
C
+ 🧩✔️ Toggle fixed pieces:
F
🧩❓ Toggle loose pieces:
G
diff --git a/src/frontend/game.ts b/src/frontend/game.ts index 9002f3f..94c4ad0 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -211,6 +211,9 @@ function EventAdapter ( if (ev.code === 'KeyN') { addEvent([Protocol.INPUT_EV_TOGGLE_PLAYER_NAMES]) } + if (ev.code === 'KeyC') { + addEvent([Protocol.INPUT_EV_CENTER_FIT_PUZZLE]) + } }) const addEvent = (event: GameEvent) => { @@ -416,11 +419,33 @@ export async function main( // initialize some view data // this global data will change according to input events const viewport = Camera() - // center viewport - viewport.move( - -(TABLE_WIDTH - canvas.width) /2, - -(TABLE_HEIGHT - canvas.height) /2 - ) + + const centerPuzzle = () => { + // center on the puzzle + viewport.reset() + viewport.move( + -(TABLE_WIDTH - canvas.width) /2, + -(TABLE_HEIGHT - canvas.height) /2 + ) + + // zoom viewport to fit whole puzzle in + const x = viewport.worldDimToViewport(BOARD_DIM) + const border = 20 + const targetW = canvas.width - (border * 2) + const targetH = canvas.height - (border * 2) + if ( + (x.w > targetW || x.h > targetH) + || (x.w < targetW && x.h < targetH) + ) { + const zoom = Math.min(targetW / x.w, targetH / x.h) + viewport.setZoom(zoom, { + x: canvas.width / 2, + y: canvas.height / 2, + }) + } + } + + centerPuzzle() const evts = EventAdapter(canvas, window, viewport, MODE) @@ -717,6 +742,8 @@ export async function main( HUD.toggleSoundsEnabled() } else if (type === Protocol.INPUT_EV_TOGGLE_PLAYER_NAMES) { HUD.togglePlayerNames() + } else if (type === Protocol.INPUT_EV_CENTER_FIT_PUZZLE) { + centerPuzzle() } // LOCAL + SERVER CHANGES @@ -787,6 +814,8 @@ export async function main( HUD.toggleSoundsEnabled() } else if (type === Protocol.INPUT_EV_TOGGLE_PLAYER_NAMES) { HUD.togglePlayerNames() + } else if (type === Protocol.INPUT_EV_CENTER_FIT_PUZZLE) { + centerPuzzle() } } } From 2b0dc392da77ef6338e1cdee794f3ca2afadda4e Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Thu, 8 Jul 2021 00:00:17 +0200 Subject: [PATCH 27/45] fix older replays --- build/public/assets/index.855f4dd3.js | 1 - build/public/assets/index.cc6b4801.js | 1 + build/public/index.html | 2 +- build/server/main.js | 92 +++++++++++++++++---------- src/common/GameCommon.ts | 4 +- src/common/Types.ts | 27 +++++++- src/common/Util.ts | 6 +- src/server/GameLog.ts | 12 +++- src/server/GameStorage.ts | 8 +-- src/server/main.ts | 6 +- 10 files changed, 105 insertions(+), 54 deletions(-) delete mode 100644 build/public/assets/index.855f4dd3.js create mode 100644 build/public/assets/index.cc6b4801.js diff --git a/build/public/assets/index.855f4dd3.js b/build/public/assets/index.855f4dd3.js deleted file mode 100644 index 5e9d456..0000000 --- a/build/public/assets/index.855f4dd3.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as w,m as v,n as b,q as C,s as x,u as k,x as P,y as A}from"./vendor.684f7bc8.js";var S=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const T={id:"app"},z={key:0,class:"nav"},I=i("Games overview"),D=i("New game");S.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",T,[e.showNav?(s(),t("ul",z,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[D])),_:1})])])):l("",!0),n(g)])};const E=864e5,N=e=>{const t=Math.floor(e/E);e%=E;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 M=1,_=1e3,V=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>N(t-e),U=N,B=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||V();return`${n} ${O(o,l)}`}}});const R={class:"game-info-text"},G=n("br",null,null,-1),$=n("br",null,null,-1),L=n("br",null,null,-1),F=i(" ↪️ Watch replay ");B.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),G,i(" 👥 "+r(e.game.players),1),$,i(" "+r(e.time(e.game.started,e.game.finished)),1),L])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[F])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:B},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 W=n("h1",null,"Running games",-1),K=n("h1",null,"Finished games",-1);j.render=function(e,o,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(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)),K,(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 H=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});H.render=function(e,o,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y,q,Q,Z,X,J,ee,te,ne=e({name:"image-library",components:{ImageTeaser:H},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});ne.render=function(e,n,o,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])},(q=Y||(Y={}))[q.Flat=0]="Flat",q[q.Out=1]="Out",q[q.In=-1]="In",(Z=Q||(Q={}))[Z.FINAL=0]="FINAL",Z[Z.ANY=1]="ANY",(J=X||(X={}))[J.NORMAL=0]="NORMAL",J[J.ANY=1]="ANY",J[J.FLAT=2]="FLAT",(te=ee||(ee={}))[te.NORMAL=0]="NORMAL",te[te.REAL=1]="REAL";class oe{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new oe(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},ae=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=le(o.getHours(),"00"),a=le(o.getMinutes(),"00"),s=le(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var se={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",oe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Q.FINAL,e.shapeMode||X.ANY,e.snapMode||ee.NORMAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:oe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const ie={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};ie.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var re=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const de=m();y("data-v-a4fa5e7e");const ce={key:0,class:"autocomplete"};f();const ue=de(((e,o,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ce,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));re.render=ue,re.__scopeId="data-v-a4fa5e7e";const pe=ae("NewImageDialog.vue");var ge=e({name:"new-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{autocompleteTags:{type:Function},uploadProgress:{type:Number},uploading:{type:String}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{uploadProgressPercent(){return this.uploadProgress?Math.round(100*this.uploadProgress):0},canPostToGallery(){return!this.uploading&&!(!this.previewUrl||!this.file)},canSetupGameClick(){return!this.uploading&&!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){pe.info("onDragleave"),this.droppable=!1}}});const he=n("div",{class:"drop-target"},null,-1),me={key:0,class:"has-image"},ye={key:1},fe={class:"upload"},we=n("span",{class:"btn"},"Upload File",-1),ve={class:"area-settings"},be=n("td",null,[n("label",null,"Title")],-1),Ce=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),xe=n("td",null,[n("label",null,"Tags")],-1),ke={class:"area-buttons"},Pe=i("🖼️ Post to gallery"),Ae=i("🧩 Post to gallery "),Se=n("br",null,null,-1),Te=i(" + set up game");ge.render=function(e,o,l,c,h,m){const y=a("responsive-image"),f=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[he,e.previewUrl?(s(),t("div",me,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(y,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",ye,[n("label",fe,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),we])]))],34),n("div",ve,[n("table",null,[n("tr",null,[be,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ce,n("tr",null,[xe,n("td",null,[n(f,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",ke,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},["postToGallery"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Pe],64))],8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},["setupGame"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Ae,Se,Te],64))],8,["disabled"])])])])};var ze=e({name:"edit-image-dialog",components:{ResponsiveImage:ie,TagsInput:re},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Ie={class:"area-image"},De={class:"has-image"},Ee={class:"area-settings"},Ne=n("td",null,[n("label",null,"Title")],-1),Me=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),_e=n("td",null,[n("label",null,"Tags")],-1),Ve={class:"area-buttons"};ze.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Ie,[n("div",De,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ee,[n("table",null,[n("tr",null,[Ne,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Me,n("tr",null,[_e,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Ve,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var Oe=e({name:"new-game-dialog",components:{ResponsiveImage:ie},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Q.ANY,shapeMode:X.NORMAL,snapMode:ee.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Ue={class:"area-image"},Be={class:"has-image"},Re={key:0,class:"image-title"},Ge={key:0,class:"image-title-title"},$e={key:1,class:"image-title-dim"},Le={class:"area-settings"},Fe=n("td",null,[n("label",null,"Pieces")],-1),je=n("td",null,[n("label",null,"Scoring: ")],-1),We=i(" Any (Score when pieces are connected to each other or on final location)"),Ke=n("br",null,null,-1),He=i(" Final (Score when pieces are put to their final location)"),Ye=n("td",null,[n("label",null,"Shapes: ")],-1),qe=i(" Normal"),Qe=n("br",null,null,-1),Ze=i(" Any (flat pieces can occur anywhere)"),Xe=n("br",null,null,-1),Je=i(" Flat (all pieces flat on all sides)"),et=n("td",null,[n("label",null,"Snapping: ")],-1),tt=i(" Normal (pieces snap to final destination and to each other)"),nt=n("br",null,null,-1),ot=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),lt={class:"area-buttons"};Oe.render=function(e,o,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Ue,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title||e.image.width||e.image.height?(s(),t("div",Re,[e.image.title?(s(),t("span",Ge,'"'+r(e.image.title)+'"',1)):l("",!0),e.image.width||e.image.height?(s(),t("span",$e,"("+r(e.image.width)+" ✕ "+r(e.image.height)+")",1)):l("",!0)])):l("",!0)]),n("div",Le,[n("table",null,[n("tr",null,[Fe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[je,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[w,e.scoreMode]]),We]),Ke,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[w,e.scoreMode]]),He])])]),n("tr",null,[Ye,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[w,e.shapeMode]]),qe]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[w,e.shapeMode]]),Ze]),Xe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[w,e.shapeMode]]),Je])])]),n("tr",null,[et,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[w,e.snapMode]]),tt]),nt,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[w,e.snapMode]]),ot])])])])]),n("div",lt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};const at=async(e,t,n)=>new Promise(((o,l)=>{const a=new window.XMLHttpRequest;a.open(e,t,!0),a.withCredentials=!0;for(const e in n.headers||{})a.setRequestHeader(e,n.headers[e]);a.addEventListener("load",(function(e){o({status:this.status,text:this.responseText,json:async()=>JSON.parse(this.responseText)})})),a.addEventListener("error",(function(e){l(new Error("xhr error"))})),a.upload&&n.onUploadProgress&&a.upload.addEventListener("progress",(function(e){n.onUploadProgress&&n.onUploadProgress(e)})),a.send(n.body)}));var st=(e,t)=>at("post",e,t),it=e({components:{ImageLibrary:ne,NewImageDialog:ge,EditImageDialog:ze,NewGameDialog:Oe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:"",uploading:"",uploadProgress:0}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${se.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){this.uploadProgress=0;const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await st("/api/upload",{body:t,onUploadProgress:e=>{this.uploadProgress=e.loaded/e.total}});return this.uploadProgress=1,await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){this.uploading="postToGallery",await this.uploadImage(e),this.uploading="",this.dialog="",await this.loadImages()},async setupGameClick(e){this.uploading="setupGame";const t=await this.uploadImage(e);this.uploading="",this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const rt={class:"upload-image-teaser"},dt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),ct={key:0},ut=i(" Tags: "),pt=i(" Sort by: "),gt=n("option",{value:"date_desc"},"Newest first",-1),ht=n("option",{value:"date_asc"},"Oldest first",-1),mt=n("option",{value:"alpha_asc"},"A-Z",-1),yt=n("option",{value:"alpha_desc"},"Z-A",-1);it.render=function(e,o,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),w=a("new-game-dialog");return s(),t("div",null,[n("div",rt,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),dt]),n("div",null,[e.tags.length>0?(s(),t("label",ct,[ut,(s(!0),t(d,null,c(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[pt,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[gt,ht,mt,yt],544),[[v,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),uploadProgress:e.uploadProgress,uploading:e.uploading,onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","uploadProgress","uploading","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(w,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ft=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 wt={class:"scores"},vt=n("div",null,"Scores",-1),bt=n("td",null,"⚡",-1),Ct=n("td",null,"💤",-1);ft.render=function(e,o,l,a,i,u){return s(),t("div",wt,[vt,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}},[Ct,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var xt=e({name:"puzzle-status",props:{finished:{type:Boolean,required:!0},duration:{type:Number,required:!0},piecesDone:{type:Number,required:!0},piecesTotal:{type:Number,required:!0}},computed:{icon(){return this.finished?"🏁":"⏳"},durationStr(){return U(this.duration)}}});const kt={class:"timer"};xt.render=function(e,o,l,a,i,d){return s(),t("div",kt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var Pt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const At=m();y("data-v-4d56fc17");const St=n("td",null,[n("label",null,"Background: ")],-1),Tt=n("td",null,[n("label",null,"Color: ")],-1),zt=n("td",null,[n("label",null,"Name: ")],-1),It=n("td",null,[n("label",null,"Sounds: ")],-1),Dt=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Et={class:"sound-volume"},Nt=n("td",null,[n("label",null,"Show player names: ")],-1);f();const Mt=At(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[10]||(o[10]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[9]||(o[9]=u((()=>{}),["stop"]))},[n("tr",null,[St,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[Tt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[zt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[It,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[C,e.modelValue.soundsEnabled]])])]),n("tr",null,[Dt,n("td",Et,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])]),n("tr",null,[Nt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[8]||(o[8]=t=>e.modelValue.showPlayerNames=t)},null,512),[[C,e.modelValue.showPlayerNames]])])])])]))));Pt.render=Mt,Pt.__scopeId="data-v-4d56fc17";var _t=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const Vt={class:"preview"};_t.render=function(e,o,l,a,i,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",Vt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Ot=1,Ut=4,Bt=2,Rt=3,Gt=2,$t=4,Lt=3,Ft=9,jt=1,Wt=2,Kt=3,Ht=4,Yt=5,qt=6,Qt=7,Zt=8,Xt=10,Jt=11,en=12,tn=13,nn=14,on=15,ln=16,an=1,sn=2,rn=3;const dn=ae("Communication.js");let cn,un=[],pn=e=>{un.push(e)},gn=[],hn=e=>{gn.push(e)};let mn=0;const yn=e=>{mn!==e&&(mn=e,hn(e))};function fn(e){if(2===mn)try{cn.send(JSON.stringify(e))}catch(t){dn.info("unable to send message.. maybe because ws is invalid?")}}let wn,vn;var bn={connect:function(e,t,n){return wn=0,vn={},yn(3),new Promise((o=>{cn=new WebSocket(e,n+"|"+t),cn.onopen=()=>{yn(2),fn([Rt])},cn.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Ut){const e=t[1];o(e)}else{if(l!==Ot)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&vn[o])return void delete vn[o];pn(t)}}},cn.onerror=()=>{throw yn(1),"[ 2021-05-15 onerror ]"},cn.onclose=e=>{4e3===e.code||1001===e.code?yn(4):yn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${se.asQueryArgs(n)}`);return await o.json()},disconnect:function(){cn&&cn.close(4e3),wn=0,vn={}},sendClientEvent:function(e){wn++,vn[wn]=e,fn([Bt,wn,vn[wn]])},onServerChange:function(e){pn=e;for(const t of un)pn(t);un=[]},onConnectionStateChange:function(e){hn=e;for(const t of gn)hn(t);gn=[]},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},Cn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===bn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===bn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const xn={key:0,class:"overlay connection-lost"},kn={key:0,class:"overlay-content"},Pn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),An={key:1,class:"overlay-content"},Sn=n("div",null,"Connecting...",-1);Cn.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",xn,[e.lostConnection?(s(),t("div",kn,[Pn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",An,[Sn])):l("",!0)])):l("",!0)};var Tn=e({name:"help-overlay",emits:{bgclick:null}});const zn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),In=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),Dn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),En=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),Nn=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Mn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),_n=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),Vn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),On=n("tr",null,[n("td",null,"🎯 Center puzzle in screen:"),n("td",null,[n("div",null,[n("kbd",null,"C")])])],-1),Un=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Bn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Rn=n("tr",null,[n("td",null,"👤 Toggle player names:"),n("td",null,[n("div",null,[n("kbd",null,"N")])])],-1),Gn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),$n=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),Ln=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),Fn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);Tn.render=function(e,o,l,a,i,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]=u((()=>{}),["stop"]))},[zn,In,Dn,En,Nn,Mn,_n,Vn,On,Un,Bn,Rn,Gn,$n,Ln,Fn])])};var jn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Wn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Kn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Hn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Yn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function qn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Qn={createCanvas:qn,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=qn(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=qn(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(),o}};const Zn=ae("Debug.js");let Xn=0,Jn=0;var eo=e=>{Xn=performance.now(),Jn=e},to=e=>{const t=performance.now(),n=t-Xn;n>Jn&&Zn.log(e+": "+n),Xn=t};function no(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function oo(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var lo={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:no,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:oo,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 no(oo(e),oo(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const ao=ae("PuzzleGraphics.js");function so(e,t){const n=se.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var io={loadPuzzleBitmaps:async function(e){const t=await Qn.loadImageToBitmap(e.info.imageUrl),n=await Qn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){ao.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=lo.pointAdd(a,{x:o,y:0}),c=lo.pointAdd(r,{x:0,y:o}),u=lo.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;ose.decodePiece(ro[e].puzzle.tiles[t]),Po=(e,t)=>ko(e,t).group,Ao=(e,t)=>{const n=ro[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},So=(e,t)=>{const n=ro[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=ro[e].puzzle.info,o=se.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return lo.pointAdd(o,l)},To=(e,t)=>ko(e,t).pos,zo=e=>{const t=Wo(e),n=Ko(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}},Io=(e,t)=>{const n=Mo(e),o=ko(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Do=(e,t)=>ko(e,t).z,Eo=(e,t)=>{for(const n of ro[e].puzzle.tiles){const e=se.decodePiece(n);if(e.owner===t)return e.idx}return-1},No=e=>ro[e].puzzle.info.tileDrawSize,Mo=e=>ro[e].puzzle.info.tileSize,_o=e=>ro[e].puzzle.data.maxGroup,Vo=e=>ro[e].puzzle.data.maxZ;function Oo(e,t){const n=ro[e].puzzle.info,o=se.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Uo=(e,t,n)=>{for(const o of t)xo(e,o,{z:n})},Bo=(e,t,n)=>{const o=To(e,t);xo(e,t,{pos:lo.pointAdd(o,n)})},Ro=(e,t,n)=>{const o=No(e),l=zo(e),a=n;for(const s of t){const t=ko(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)Bo(e,s,a)},Go=(e,t)=>ko(e,t).owner,$o=(e,t)=>{for(const n of t)xo(e,n,{owner:-1,z:1})},Lo=(e,t,n)=>{for(const o of t)xo(e,o,{owner:n})};function Fo(e,t){const n=ro[e].puzzle.tiles,o=se.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=se.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const jo=(e,t)=>{const n=uo(e,t);return n?n.points:0},Wo=e=>ro[e].puzzle.info.table.width,Ko=e=>ro[e].puzzle.info.table.height;var Ho={setGame:function(e,t){ro[e]=t},exists:function(e){return!!ro[e]||!1},playerExists:go,getActivePlayers:function(e,t){const n=t-30*_;return ho(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return ho(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){go(e,t)?bo(e,t,{ts:n}):po(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:vo,getPieceCount:mo,getImageUrl:function(e){return ro[e].puzzle.info.imageUrl},setImageUrl:function(e,t){ro[e].puzzle.info.imageUrl=t},get:function(e){return ro[e]||null},getAllGames:function(){return Object.values(ro).sort(((e,t)=>wo(e.id)===wo(t.id)?t.puzzle.data.started-e.puzzle.data.started:wo(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=uo(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=uo(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=uo(e,t);return n?n.name:null},getPlayerIndexById:co,getPlayerIdByIndex:function(e,t){return ro[e].players.length>t?se.decodePlayer(ro[e].players[t]).id:null},changePlayer:bo,setPlayer:po,setPiece:function(e,t,n){ro[e].puzzle.tiles[t]=se.encodePiece(n)},setPuzzleData:function(e,t){ro[e].puzzle.data=t},getTableWidth:Wo,getTableHeight:Ko,getPuzzle:e=>ro[e].puzzle,getRng:e=>ro[e].rng.obj,getPuzzleWidth:e=>ro[e].puzzle.info.width,getPuzzleHeight:e=>ro[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return ro[e].puzzle.tiles.map(se.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Eo(e,t);return n<0?null:ro[e].puzzle.tiles[n]},getPieceDrawOffset:e=>ro[e].puzzle.info.tileDrawOffset,getPieceDrawSize:No,getFinalPiecePos:So,getStartTs:e=>ro[e].puzzle.data.started,getFinishTs:e=>ro[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=ro[e].puzzle,s=function(e,t){return t in ro[e].evtInfos?ro[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([an,a.data])},d=t=>{i.push([sn,se.encodePiece(ko(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=uo(e,t);n&&i.push([rn,se.encodePlayer(n)])},p=n[0];if(p===qt){const l=n[1];bo(e,t,{bgcolor:l,ts:o}),u()}else if(p===Qt){const l=n[1];bo(e,t,{color:l,ts:o}),u()}else if(p===Zt){const l=`${n[1]}`.substr(0,16);bo(e,t,{name:l,ts:o}),u()}else if(p===Ft){const l=n[1],a=n[2],s=uo(e,t);if(s){const n=s.x-l,i=s.y-a;bo(e,t,{ts:o,x:n,y:i}),u()}}else if(p===jt){const l={x:n[1],y:n[2]};bo(e,t,{d:1,ts:o}),u(),s._last_mouse_down=l;const a=((e,t)=>{const n=ro[e].puzzle.info,o=ro[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=Vo(e)+1;Co(e,{maxZ:n}),r();const o=Fo(e,a);Uo(e,o,Vo(e)),Lo(e,o,t),c(o)}s._last_mouse=l}else if(p===Kt){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)bo(e,t,{x:l,y:a,ts:o}),u();else{const n=Eo(e,t);if(n>=0){bo(e,t,{x:l,y:a,ts:o}),u();const r=Fo(e,n);let d=lo.pointInBounds(i,zo(e))&&lo.pointInBounds(s._last_mouse_down,zo(e));for(const t of r){const n=Io(e,t);if(lo.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Ro(e,r,{x:t,y:n}),c(r)}}else bo(e,t,{ts:o}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===Wt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Eo(e,t);if(g>=0){const n=Fo(e,g);Lo(e,n,0),c(n);const s=To(e,g),i=So(e,g);let h=!1;if(fo(e)===ee.REAL){for(const t of n)if(Ao(e,t)){h=!0;break}}else h=!0;if(h&&lo.pointDistance(i,s){const l=ro[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=Po(e,t),l=Po(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=To(e,t),s=lo.pointAdd(To(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(lo.pointDistance(a,s){const o=ro[e].puzzle.tiles,l=Po(e,t),a=Po(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(Co(e,{maxGroup:_o(e)+1}),r(),s=_o(e));if(xo(e,t,{group:s}),d(t),xo(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=se.decodePiece(r);i.includes(t.group)&&(xo(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=Fo(e,t),((e,t)=>-1===Go(e,t))(e,n))$o(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=Do(e,o);t>n&&(n=t)}return n})(e,l);Uo(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Fo(e,g)){const o=Oo(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])){a=!0;break}}if(a&&yo(e)===Q.ANY){const n=jo(e,t)+1;bo(e,t,{d:p,ts:o,points:n}),u()}else bo(e,t,{d:p,ts:o}),u();a&&fo(e)===ee.REAL&&vo(e)===mo(e)&&(Co(e,{finished:o}),r()),a&&l&&l(t)}}else bo(e,t,{d:p,ts:o}),u();s._last_mouse=i}else if(p===Ht){const l=n[1],a=n[2];bo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else if(p===Yt){const l=n[1],a=n[2];bo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else bo(e,t,{ts:o}),u();return function(e,t,n){ro[e].evtInfos[t]=n}(e,t,s),i}};let Yo=-10,qo=20,Qo=2,Zo=15;class Xo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Yo+Math.random()*qo,this.vy=-1*(Qo+Math.random()*Zo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Qo=t/2,Zo=t-Qo;const n=1/4*this.canvas.width/(t/2);Yo=-n,qo=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Xo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Xo(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{localStorage.setItem(e,t)},rl=e=>localStorage.getItem(e);var dl=(e,t)=>{il(e,`${t}`)},cl=(e,t)=>{const n=rl(e);if(null===n)return t;const o=parseInt(n,10);return isNaN(o)?t:o},ul=(e,t)=>{il(e,t?"1":"0")},pl=(e,t)=>{const n=rl(e);return null===n?t:"1"===n},gl=(e,t)=>{il(e,t)},hl=(e,t)=>{const n=rl(e);return null===n?t:n};const ml={"./grab.png":Wn,"./grab_mask.png":Kn,"./hand.png":Hn,"./hand_mask.png":Yn},yl={"./click.mp3":jn};let fl=!0,wl=!0;let vl=!0;async function bl(e,t,n,o,l,a){void 0===window.DEBUG&&(window.DEBUG=!1);const s=yl["./click.mp3"].default,i=new Audio(s),r=await Qn.loadImageToBitmap(ml["./grab.png"].default),d=await Qn.loadImageToBitmap(ml["./hand.png"].default),c=await Qn.loadImageToBitmap(ml["./grab_mask.png"].default),u=await Qn.loadImageToBitmap(ml["./hand_mask.png"].default),p=r.width,g=Math.round(p/2),h=r.height,m=Math.round(h/2),y={},f=async e=>{const t=e.color+" "+e.d;if(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Qn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},w=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,vl=!0})),t}(l,Qn.createCanvas()),v={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};bn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=v.dataOffset;v.dataOffset+=1e4;const n=await bn.requestReplayData(e,t);return v.log=v.log.slice(v.logPointer),v.logPointer=0,v.log.push(...n.log),0===n.log.length&&(v.final=!0),n};let C=()=>0;const x=async()=>{if("play"===o){const o=await bn.connect(n,e,t),l=se.decodeGame(o);Ho.setGame(l.id,l),C=()=>V()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=se.decodeGame(t.game);Ho.setGame(n.id,n),v.lastRealTs=V(),v.gameStartTs=parseInt(t.log[0][4],10),v.lastGameTs=v.gameStartTs,C=()=>v.lastGameTs}}vl=!0};await x();const k=Ho.getPieceDrawOffset(e),P=Ho.getPieceDrawSize(e),A=Ho.getPuzzleWidth(e),S=Ho.getPuzzleHeight(e),T=Ho.getTableWidth(e),z=Ho.getTableHeight(e),I={x:(T-A)/2,y:(z-S)/2},D={w:A,h:S},E={w:P,h:P},N=await io.loadPuzzleBitmaps(Ho.getPuzzle(e)),_=new el(w,Ho.getRng(e));_.init();const O=w.getContext("2d");w.classList.add("loaded");const U=function(){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)},a=(e,t)=>{if(n==e)return!1;const l=1-n/e;return o(-t.x*l,-t.y*l),n=e,!0},s=o=>({x:o.x/n-e,y:o.y/n-t}),i=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),r=e=>({w:e.w*n,h:e.h*n}),d=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,reset:()=>{e=0,t=0,n=1},move:o,canZoom:e=>n!=l(e),zoom:(e,t)=>a(l(e),t),setZoom:a,worldToViewport:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:r,viewportToWorld:e=>{const{x:t,y:n}=s(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:s,viewportDimToWorld:e=>{const{w:t,h:n}=d(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:d}}(),B=()=>{U.reset(),U.move(-(T-w.width)/2,-(z-w.height)/2);const e=U.worldDimToViewport(D),t=w.width-40,n=w.height-40;if(e.w>t||e.h>n||e.w{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&w([jt,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&w([Wt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),w([Kt,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Ht:Yt;w([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&w([Xt]),"replay"===o&&("KeyI"===e.code&&w([tn]),"KeyO"===e.code&&w([nn]),"KeyP"===e.code&&w([en])),"KeyF"===e.code&&(fl=!fl,vl=!0),"KeyG"===e.code&&(wl=!wl,vl=!0),"KeyM"===e.code&&w([Jt]),"KeyN"===e.code&&w([on]),"KeyC"===e.code&&w([ln]))}));const w=e=>{l.push(e)};return{addEvent:w,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});w([Ft,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();w([Ht,...e])}}else if(u&&n.canZoom("out")){const e=f||m();w([Yt,...e])}},setHotkeys:e=>{a=e}}}(w,window,U,o),G=Ho.getImageUrl(e),$=()=>{const t=Ho.getStartTs(e),n=Ho.getFinishTs(e),o=C();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(Ho.getFinishedPiecesCount(e)),a.setPiecesTotal(Ho.getPieceCount(e));const L=C();a.setActivePlayers(Ho.getActivePlayers(e,L)),a.setIdlePlayers(Ho.getIdlePlayers(e,L));const F=!!Ho.getFinishTs(e);let j=F;const W=()=>j&&!F,K=()=>cl(tl,100),H=()=>pl(nl,!1),Y=()=>pl(sl,!0),q=()=>{const e=K();i.volume=e/100,i.play()},Q=()=>"replay"===o?hl(ol,"#222222"):Ho.getPlayerBgColor(e,t)||hl(ol,"#222222"),Z=()=>"replay"===o?hl(ll,"#ffffff"):Ho.getPlayerColor(e,t)||hl(ll,"#ffffff");let X="",J="",ee=!1;const te=e=>{ee=e;const[t,n]=e?[X,"grab"]:[J,"default"];w.style.cursor=`url('${t}') ${g} ${m}, ${n}`},ne=e=>{X=Qn.colorizedCanvas(r,c,e).toDataURL(),J=Qn.colorizedCanvas(d,u,e).toDataURL(),te(ee)};ne(Z());const oe=()=>{a.setReplaySpeed&&a.setReplaySpeed(v.speeds[v.speedIdx]),a.setReplayPaused&&a.setReplayPaused(v.paused)},le=()=>{v.speedIdx+1{v.speedIdx>=1&&(v.speedIdx--,oe())},ie=()=>{v.paused=!v.paused,oe()},re=[];let de;let ce;if("play"===o?re.push(setInterval((()=>{$()}),1e3)):"replay"===o&&oe(),"play"===o)bn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case rn:{const n=se.decodePlayer(a);n.id!==t&&(Ho.setPlayer(e,n.id,n),vl=!0)}break;case sn:{const t=se.decodePiece(a);Ho.setPiece(e,t.idx,t),vl=!0}break;case an:Ho.setPuzzleData(e,a),vl=!0}j=!!Ho.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===Gt){const t=o[1];return Ho.addPlayer(e,t,n),!0}if(o[0]===$t){const t=Ho.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Ho.addPlayer(e,t,n),!0}if(o[0]===Lt){const t=Ho.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Ho.handleInput(e,t,l,n),!0}return!1};let n=v.lastGameTs;const o=async()=>{v.logPointer+1>=v.log.length&&await b(e);const l=V();if(v.paused)return v.lastRealTs=l,void(de=setTimeout(o,50));const a=(l-v.lastRealTs)*v.speeds[v.speedIdx];let s=v.lastGameTs+a;for(;;){if(v.paused)break;const e=v.logPointer+1;if(e>=v.log.length)break;const o=v.log[v.logPointer],l=n+o[o.length-1],a=v.log[e],i=a[a.length-1],r=l+i;if(r>s){s+500*M{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/o),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{R.createKeyEvents();for(const n of R.consumeAll())if("play"===o){const o=n[0];if(o===Ft){const e=n[1],t=n[2],o=U.worldDimToViewport({w:e,h:t});vl=!0,U.move(o.w,o.h)}else if(o===Kt){if(ue&&!Ho.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-ue.x),l=Math.round(t.y-ue.y);vl=!0,U.move(o,l),ue=t}}else if(o===Qt)ne(n[1]);else if(o===jt){const e={x:n[1],y:n[2]};ue=U.worldToViewport(e),te(!0)}else if(o===Wt)ue=null,te(!1);else if(o===Ht){const e={x:n[1],y:n[2]};vl=!0,U.zoom("in",U.worldToViewport(e))}else if(o===Yt){const e={x:n[1],y:n[2]};vl=!0,U.zoom("out",U.worldToViewport(e))}else o===Xt?a.togglePreview():o===Jt?a.toggleSoundsEnabled():o===on?a.togglePlayerNames():o===ln&&B();const l=C();Ho.handleInput(e,t,n,l,(e=>{H()&&q()})).length>0&&(vl=!0),bn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===en)ie();else if(e===nn)ae();else if(e===tn)le();else if(e===Ft){const e=n[1],t=n[2];vl=!0,U.move(e,t)}else if(e===Kt){if(ue){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-ue.x),l=Math.round(t.y-ue.y);vl=!0,U.move(o,l),ue=t}}else if(e===Qt)ne(n[1]);else if(e===jt){const e={x:n[1],y:n[2]};ue=U.worldToViewport(e),te(!0)}else if(e===Wt)ue=null,te(!1);else if(e===Ht){const e={x:n[1],y:n[2]};vl=!0,U.zoom("in",U.worldToViewport(e))}else if(e===Yt){const e={x:n[1],y:n[2]};vl=!0,U.zoom("out",U.worldToViewport(e))}else e===Xt?a.togglePreview():e===Jt?a.toggleSoundsEnabled():e===on?a.togglePlayerNames():e===ln&&B()}j=!!Ho.getFinishTs(e),W()&&(_.update(),vl=!0)},render:async()=>{if(!vl)return;const n=C();let l,s,i;window.DEBUG&&eo(0),O.fillStyle=Q(),O.fillRect(0,0,w.width,w.height),window.DEBUG&&to("clear done"),l=U.worldToViewportRaw(I),s=U.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&to("board done");const r=Ho.getPiecesSortedByZIndex(e);window.DEBUG&&to("get tiles done"),s=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?fl:wl)&&(i=N[e.idx],l=U.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&to("tiles done");const d=[];for(const a of Ho.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(i=await f(a),l=U.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),Y()&&d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,o]of d)O.fillText(e,t,o);window.DEBUG&&to("players done"),a.setActivePlayers(Ho.getActivePlayers(e,n)),a.setIdlePlayers(Ho.getIdlePlayers(e,n)),a.setPiecesDone(Ho.getFinishedPiecesCount(e)),window.DEBUG&&to("HUD done"),W()&&_.render(),vl=!1}}),{setHotkeys:e=>{R.setHotkeys(e)},onBgChange:e=>{gl(ol,e),R.addEvent([qt,e])},onColorChange:e=>{gl(ll,e),R.addEvent([Qt,e])},onNameChange:e=>{gl(al,e),R.addEvent([Zt,e])},onSoundsEnabledChange:e=>{ul(nl,e)},onSoundsVolumeChange:e=>{dl(tl,e),q()},onShowPlayerNamesChange:e=>{ul(sl,e)},replayOnSpeedUp:le,replayOnSpeedDown:ae,replayOnPauseToggle:ie,previewImageUrl:G,player:{background:Q(),color:Z(),name:"replay"===o?hl(al,"anon"):Ho.getPlayerName(e,t)||hl(al,"anon"),soundsEnabled:H(),soundsVolume:K(),showPlayerNames:Y()},disconnect:bn.disconnect,connect:x,unload:()=>{re.forEach((e=>{clearInterval(e)})),de&&clearTimeout(de),ce&&ce.stop()}}}var Cl=e({name:"game",components:{PuzzleStatus:xt,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,ConnectionOverlay:Cn,HelpOverlay:Tn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await bl(`${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},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 xl={id:"game"},kl={class:"menu"},Pl={class:"tabs"},Al=i("🧩 Puzzles");Cl.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),w=a("scores");return s(),t("div",xl,[p(n(u,{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"]),[[x,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",kl,[n("div",Pl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Al])),_: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))},"ℹ️ Hotkeys")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var Sl=e({name:"replay",components:{PuzzleStatus:xt,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,HelpOverlay:Tn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await bl(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 Tl={id:"replay"},zl={class:"menu"},Il={class:"tabs"},Dl=i("🧩 Puzzles");Sl.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),w=a("scores");return s(),t("div",Tl,[p(n(g,{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"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick: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",zl,[n("div",Il,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Dl])),_: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))},"ℹ️ Hotkeys")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=k({history:P(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:it},{name:"game",path:"/g/:id",component:Cl},{name:"replay",path:"/replay/:id",component:Sl}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=A(S);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=se.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); diff --git a/build/public/assets/index.cc6b4801.js b/build/public/assets/index.cc6b4801.js new file mode 100644 index 0000000..7e3c4be --- /dev/null +++ b/build/public/assets/index.cc6b4801.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as w,m as v,n as b,q as C,s as x,u as k,x as P,y as A}from"./vendor.684f7bc8.js";var S=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const T={id:"app"},z={key:0,class:"nav"},I=i("Games overview"),D=i("New game");S.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",T,[e.showNav?(s(),t("ul",z,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[D])),_:1})])])):l("",!0),n(g)])};const E=864e5,M=e=>{const t=Math.floor(e/E);e%=E;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 N=1,_=1e3,V=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>M(t-e),U=M,B=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||V();return`${n} ${O(o,l)}`}}});const R={class:"game-info-text"},G=n("br",null,null,-1),$=n("br",null,null,-1),L=n("br",null,null,-1),F=i(" ↪️ Watch replay ");B.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),G,i(" 👥 "+r(e.game.players),1),$,i(" "+r(e.time(e.game.started,e.game.finished)),1),L])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[F])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:B},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 W=n("h1",null,"Running games",-1),K=n("h1",null,"Finished games",-1);j.render=function(e,o,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(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)),K,(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 H=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});H.render=function(e,o,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y=e({name:"image-library",components:{ImageTeaser:H},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});Y.render=function(e,n,o,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])};class q{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new q(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const Q=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},Z=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=Q(o.getHours(),"00"),a=Q(o.getMinutes(),"00"),s=Q(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var X={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",q.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode,e.shapeMode,e.snapMode]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:q.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const J={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};J.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var ee=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const te=m();y("data-v-a4fa5e7e");const ne={key:0,class:"autocomplete"};f();const oe=te(((e,o,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ne,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));ee.render=oe,ee.__scopeId="data-v-a4fa5e7e";const le=Z("NewImageDialog.vue");var ae=e({name:"new-image-dialog",components:{ResponsiveImage:J,TagsInput:ee},props:{autocompleteTags:{type:Function},uploadProgress:{type:Number},uploading:{type:String}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{uploadProgressPercent(){return this.uploadProgress?Math.round(100*this.uploadProgress):0},canPostToGallery(){return!this.uploading&&!(!this.previewUrl||!this.file)},canSetupGameClick(){return!this.uploading&&!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){le.info("onDragleave"),this.droppable=!1}}});const se=n("div",{class:"drop-target"},null,-1),ie={key:0,class:"has-image"},re={key:1},de={class:"upload"},ce=n("span",{class:"btn"},"Upload File",-1),ue={class:"area-settings"},pe=n("td",null,[n("label",null,"Title")],-1),ge=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),he=n("td",null,[n("label",null,"Tags")],-1),me={class:"area-buttons"},ye=i("🖼️ Post to gallery"),fe=i("🧩 Post to gallery "),we=n("br",null,null,-1),ve=i(" + set up game");ae.render=function(e,o,l,c,h,m){const y=a("responsive-image"),f=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[se,e.previewUrl?(s(),t("div",ie,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(y,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",re,[n("label",de,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ce])]))],34),n("div",ue,[n("table",null,[n("tr",null,[pe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),ge,n("tr",null,[he,n("td",null,[n(f,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",me,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},["postToGallery"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[ye],64))],8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},["setupGame"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[fe,we,ve],64))],8,["disabled"])])])])};var be=e({name:"edit-image-dialog",components:{ResponsiveImage:J,TagsInput:ee},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Ce={class:"area-image"},xe={class:"has-image"},ke={class:"area-settings"},Pe=n("td",null,[n("label",null,"Title")],-1),Ae=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Se=n("td",null,[n("label",null,"Tags")],-1),Te={class:"area-buttons"};var ze,Ie,De,Ee,Me,Ne,_e,Ve;be.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Ce,[n("div",xe,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ke,[n("table",null,[n("tr",null,[Pe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ae,n("tr",null,[Se,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Te,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])},(Ie=ze||(ze={}))[Ie.Flat=0]="Flat",Ie[Ie.Out=1]="Out",Ie[Ie.In=-1]="In",(Ee=De||(De={}))[Ee.FINAL=0]="FINAL",Ee[Ee.ANY=1]="ANY",(Ne=Me||(Me={}))[Ne.NORMAL=0]="NORMAL",Ne[Ne.ANY=1]="ANY",Ne[Ne.FLAT=2]="FLAT",(Ve=_e||(_e={}))[Ve.NORMAL=0]="NORMAL",Ve[Ve.REAL=1]="REAL";var Oe=e({name:"new-game-dialog",components:{ResponsiveImage:J},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:De.ANY,shapeMode:Me.NORMAL,snapMode:_e.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Ue={class:"area-image"},Be={class:"has-image"},Re={key:0,class:"image-title"},Ge={key:0,class:"image-title-title"},$e={key:1,class:"image-title-dim"},Le={class:"area-settings"},Fe=n("td",null,[n("label",null,"Pieces")],-1),je=n("td",null,[n("label",null,"Scoring: ")],-1),We=i(" Any (Score when pieces are connected to each other or on final location)"),Ke=n("br",null,null,-1),He=i(" Final (Score when pieces are put to their final location)"),Ye=n("td",null,[n("label",null,"Shapes: ")],-1),qe=i(" Normal"),Qe=n("br",null,null,-1),Ze=i(" Any (flat pieces can occur anywhere)"),Xe=n("br",null,null,-1),Je=i(" Flat (all pieces flat on all sides)"),et=n("td",null,[n("label",null,"Snapping: ")],-1),tt=i(" Normal (pieces snap to final destination and to each other)"),nt=n("br",null,null,-1),ot=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),lt={class:"area-buttons"};Oe.render=function(e,o,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Ue,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title||e.image.width||e.image.height?(s(),t("div",Re,[e.image.title?(s(),t("span",Ge,'"'+r(e.image.title)+'"',1)):l("",!0),e.image.width||e.image.height?(s(),t("span",$e,"("+r(e.image.width)+" ✕ "+r(e.image.height)+")",1)):l("",!0)])):l("",!0)]),n("div",Le,[n("table",null,[n("tr",null,[Fe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[je,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[w,e.scoreMode]]),We]),Ke,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[w,e.scoreMode]]),He])])]),n("tr",null,[Ye,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[w,e.shapeMode]]),qe]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[w,e.shapeMode]]),Ze]),Xe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[w,e.shapeMode]]),Je])])]),n("tr",null,[et,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[w,e.snapMode]]),tt]),nt,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[w,e.snapMode]]),ot])])])])]),n("div",lt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};const at=async(e,t,n)=>new Promise(((o,l)=>{const a=new window.XMLHttpRequest;a.open(e,t,!0),a.withCredentials=!0;for(const e in n.headers||{})a.setRequestHeader(e,n.headers[e]);a.addEventListener("load",(function(e){o({status:this.status,text:this.responseText,json:async()=>JSON.parse(this.responseText)})})),a.addEventListener("error",(function(e){l(new Error("xhr error"))})),a.upload&&n.onUploadProgress&&a.upload.addEventListener("progress",(function(e){n.onUploadProgress&&n.onUploadProgress(e)})),a.send(n.body)}));var st=(e,t)=>at("post",e,t),it=e({components:{ImageLibrary:Y,NewImageDialog:ae,EditImageDialog:be,NewGameDialog:Oe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:"",uploading:"",uploadProgress:0}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${X.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){this.uploadProgress=0;const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await st("/api/upload",{body:t,onUploadProgress:e=>{this.uploadProgress=e.loaded/e.total}});return this.uploadProgress=1,await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){this.uploading="postToGallery",await this.uploadImage(e),this.uploading="",this.dialog="",await this.loadImages()},async setupGameClick(e){this.uploading="setupGame";const t=await this.uploadImage(e);this.uploading="",this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const rt={class:"upload-image-teaser"},dt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),ct={key:0},ut=i(" Tags: "),pt=i(" Sort by: "),gt=n("option",{value:"date_desc"},"Newest first",-1),ht=n("option",{value:"date_asc"},"Oldest first",-1),mt=n("option",{value:"alpha_asc"},"A-Z",-1),yt=n("option",{value:"alpha_desc"},"Z-A",-1);it.render=function(e,o,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),w=a("new-game-dialog");return s(),t("div",null,[n("div",rt,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),dt]),n("div",null,[e.tags.length>0?(s(),t("label",ct,[ut,(s(!0),t(d,null,c(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[pt,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[gt,ht,mt,yt],544),[[v,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),uploadProgress:e.uploadProgress,uploading:e.uploading,onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","uploadProgress","uploading","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(w,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ft=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 wt={class:"scores"},vt=n("div",null,"Scores",-1),bt=n("td",null,"⚡",-1),Ct=n("td",null,"💤",-1);ft.render=function(e,o,l,a,i,u){return s(),t("div",wt,[vt,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}},[Ct,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var xt=e({name:"puzzle-status",props:{finished:{type:Boolean,required:!0},duration:{type:Number,required:!0},piecesDone:{type:Number,required:!0},piecesTotal:{type:Number,required:!0}},computed:{icon(){return this.finished?"🏁":"⏳"},durationStr(){return U(this.duration)}}});const kt={class:"timer"};xt.render=function(e,o,l,a,i,d){return s(),t("div",kt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var Pt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const At=m();y("data-v-4d56fc17");const St=n("td",null,[n("label",null,"Background: ")],-1),Tt=n("td",null,[n("label",null,"Color: ")],-1),zt=n("td",null,[n("label",null,"Name: ")],-1),It=n("td",null,[n("label",null,"Sounds: ")],-1),Dt=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Et={class:"sound-volume"},Mt=n("td",null,[n("label",null,"Show player names: ")],-1);f();const Nt=At(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[10]||(o[10]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[9]||(o[9]=u((()=>{}),["stop"]))},[n("tr",null,[St,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[Tt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[zt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[It,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[C,e.modelValue.soundsEnabled]])])]),n("tr",null,[Dt,n("td",Et,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])]),n("tr",null,[Mt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[8]||(o[8]=t=>e.modelValue.showPlayerNames=t)},null,512),[[C,e.modelValue.showPlayerNames]])])])])]))));Pt.render=Nt,Pt.__scopeId="data-v-4d56fc17";var _t=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const Vt={class:"preview"};_t.render=function(e,o,l,a,i,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",Vt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Ot=1,Ut=4,Bt=2,Rt=3,Gt=2,$t=4,Lt=3,Ft=9,jt=1,Wt=2,Kt=3,Ht=4,Yt=5,qt=6,Qt=7,Zt=8,Xt=10,Jt=11,en=12,tn=13,nn=14,on=15,ln=16,an=1,sn=2,rn=3;const dn=Z("Communication.js");let cn,un=[],pn=e=>{un.push(e)},gn=[],hn=e=>{gn.push(e)};let mn=0;const yn=e=>{mn!==e&&(mn=e,hn(e))};function fn(e){if(2===mn)try{cn.send(JSON.stringify(e))}catch(t){dn.info("unable to send message.. maybe because ws is invalid?")}}let wn,vn;var bn={connect:function(e,t,n){return wn=0,vn={},yn(3),new Promise((o=>{cn=new WebSocket(e,n+"|"+t),cn.onopen=()=>{yn(2),fn([Rt])},cn.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Ut){const e=t[1];o(e)}else{if(l!==Ot)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&vn[o])return void delete vn[o];pn(t)}}},cn.onerror=()=>{throw yn(1),"[ 2021-05-15 onerror ]"},cn.onclose=e=>{4e3===e.code||1001===e.code?yn(4):yn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${X.asQueryArgs(n)}`);return await o.json()},disconnect:function(){cn&&cn.close(4e3),wn=0,vn={}},sendClientEvent:function(e){wn++,vn[wn]=e,fn([Bt,wn,vn[wn]])},onServerChange:function(e){pn=e;for(const t of un)pn(t);un=[]},onConnectionStateChange:function(e){hn=e;for(const t of gn)hn(t);gn=[]},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},Cn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===bn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===bn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const xn={key:0,class:"overlay connection-lost"},kn={key:0,class:"overlay-content"},Pn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),An={key:1,class:"overlay-content"},Sn=n("div",null,"Connecting...",-1);Cn.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",xn,[e.lostConnection?(s(),t("div",kn,[Pn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",An,[Sn])):l("",!0)])):l("",!0)};var Tn=e({name:"help-overlay",emits:{bgclick:null}});const zn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),In=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),Dn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),En=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),Mn=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Nn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),_n=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),Vn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),On=n("tr",null,[n("td",null,"🎯 Center puzzle in screen:"),n("td",null,[n("div",null,[n("kbd",null,"C")])])],-1),Un=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Bn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Rn=n("tr",null,[n("td",null,"👤 Toggle player names:"),n("td",null,[n("div",null,[n("kbd",null,"N")])])],-1),Gn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),$n=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),Ln=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),Fn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);Tn.render=function(e,o,l,a,i,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]=u((()=>{}),["stop"]))},[zn,In,Dn,En,Mn,Nn,_n,Vn,On,Un,Bn,Rn,Gn,$n,Ln,Fn])])};var jn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Wn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Kn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Hn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Yn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function qn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Qn={createCanvas:qn,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=qn(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=qn(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(),o}};const Zn=Z("Debug.js");let Xn=0,Jn=0;var eo=e=>{Xn=performance.now(),Jn=e},to=e=>{const t=performance.now(),n=t-Xn;n>Jn&&Zn.log(e+": "+n),Xn=t};function no(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function oo(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var lo={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:no,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:oo,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 no(oo(e),oo(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const ao=Z("PuzzleGraphics.js");function so(e,t){const n=X.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var io={loadPuzzleBitmaps:async function(e){const t=await Qn.loadImageToBitmap(e.info.imageUrl),n=await Qn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){ao.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=lo.pointAdd(a,{x:o,y:0}),c=lo.pointAdd(r,{x:0,y:o}),u=lo.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;oX.decodePiece(ro[e].puzzle.tiles[t]),Po=(e,t)=>ko(e,t).group,Ao=(e,t)=>{const n=ro[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},So=(e,t)=>{const n=ro[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=ro[e].puzzle.info,o=X.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return lo.pointAdd(o,l)},To=(e,t)=>ko(e,t).pos,zo=e=>{const t=Wo(e),n=Ko(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}},Io=(e,t)=>{const n=No(e),o=ko(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Do=(e,t)=>ko(e,t).z,Eo=(e,t)=>{for(const n of ro[e].puzzle.tiles){const e=X.decodePiece(n);if(e.owner===t)return e.idx}return-1},Mo=e=>ro[e].puzzle.info.tileDrawSize,No=e=>ro[e].puzzle.info.tileSize,_o=e=>ro[e].puzzle.data.maxGroup,Vo=e=>ro[e].puzzle.data.maxZ;function Oo(e,t){const n=ro[e].puzzle.info,o=X.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Uo=(e,t,n)=>{for(const o of t)xo(e,o,{z:n})},Bo=(e,t,n)=>{const o=To(e,t);xo(e,t,{pos:lo.pointAdd(o,n)})},Ro=(e,t,n)=>{const o=Mo(e),l=zo(e),a=n;for(const s of t){const t=ko(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)Bo(e,s,a)},Go=(e,t)=>ko(e,t).owner,$o=(e,t)=>{for(const n of t)xo(e,n,{owner:-1,z:1})},Lo=(e,t,n)=>{for(const o of t)xo(e,o,{owner:n})};function Fo(e,t){const n=ro[e].puzzle.tiles,o=X.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=X.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const jo=(e,t)=>{const n=uo(e,t);return n?n.points:0},Wo=e=>ro[e].puzzle.info.table.width,Ko=e=>ro[e].puzzle.info.table.height;var Ho={setGame:function(e,t){ro[e]=t},exists:function(e){return!!ro[e]||!1},playerExists:go,getActivePlayers:function(e,t){const n=t-30*_;return ho(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return ho(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){go(e,t)?bo(e,t,{ts:n}):po(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:vo,getPieceCount:mo,getImageUrl:function(e){return ro[e].puzzle.info.imageUrl},setImageUrl:function(e,t){ro[e].puzzle.info.imageUrl=t},get:function(e){return ro[e]||null},getAllGames:function(){return Object.values(ro).sort(((e,t)=>wo(e.id)===wo(t.id)?t.puzzle.data.started-e.puzzle.data.started:wo(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=uo(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=uo(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=uo(e,t);return n?n.name:null},getPlayerIndexById:co,getPlayerIdByIndex:function(e,t){return ro[e].players.length>t?X.decodePlayer(ro[e].players[t]).id:null},changePlayer:bo,setPlayer:po,setPiece:function(e,t,n){ro[e].puzzle.tiles[t]=X.encodePiece(n)},setPuzzleData:function(e,t){ro[e].puzzle.data=t},getTableWidth:Wo,getTableHeight:Ko,getPuzzle:e=>ro[e].puzzle,getRng:e=>ro[e].rng.obj,getPuzzleWidth:e=>ro[e].puzzle.info.width,getPuzzleHeight:e=>ro[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return ro[e].puzzle.tiles.map(X.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Eo(e,t);return n<0?null:ro[e].puzzle.tiles[n]},getPieceDrawOffset:e=>ro[e].puzzle.info.tileDrawOffset,getPieceDrawSize:Mo,getFinalPiecePos:So,getStartTs:e=>ro[e].puzzle.data.started,getFinishTs:e=>ro[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=ro[e].puzzle,s=function(e,t){return t in ro[e].evtInfos?ro[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([an,a.data])},d=t=>{i.push([sn,X.encodePiece(ko(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=uo(e,t);n&&i.push([rn,X.encodePlayer(n)])},p=n[0];if(p===qt){const l=n[1];bo(e,t,{bgcolor:l,ts:o}),u()}else if(p===Qt){const l=n[1];bo(e,t,{color:l,ts:o}),u()}else if(p===Zt){const l=`${n[1]}`.substr(0,16);bo(e,t,{name:l,ts:o}),u()}else if(p===Ft){const l=n[1],a=n[2],s=uo(e,t);if(s){const n=s.x-l,i=s.y-a;bo(e,t,{ts:o,x:n,y:i}),u()}}else if(p===jt){const l={x:n[1],y:n[2]};bo(e,t,{d:1,ts:o}),u(),s._last_mouse_down=l;const a=((e,t)=>{const n=ro[e].puzzle.info,o=ro[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=Vo(e)+1;Co(e,{maxZ:n}),r();const o=Fo(e,a);Uo(e,o,Vo(e)),Lo(e,o,t),c(o)}s._last_mouse=l}else if(p===Kt){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)bo(e,t,{x:l,y:a,ts:o}),u();else{const n=Eo(e,t);if(n>=0){bo(e,t,{x:l,y:a,ts:o}),u();const r=Fo(e,n);let d=lo.pointInBounds(i,zo(e))&&lo.pointInBounds(s._last_mouse_down,zo(e));for(const t of r){const n=Io(e,t);if(lo.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Ro(e,r,{x:t,y:n}),c(r)}}else bo(e,t,{ts:o}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===Wt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Eo(e,t);if(g>=0){const n=Fo(e,g);Lo(e,n,0),c(n);const s=To(e,g),i=So(e,g);let h=!1;if(fo(e)===_e.REAL){for(const t of n)if(Ao(e,t)){h=!0;break}}else h=!0;if(h&&lo.pointDistance(i,s){const l=ro[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=Po(e,t),l=Po(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=To(e,t),s=lo.pointAdd(To(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(lo.pointDistance(a,s){const o=ro[e].puzzle.tiles,l=Po(e,t),a=Po(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(Co(e,{maxGroup:_o(e)+1}),r(),s=_o(e));if(xo(e,t,{group:s}),d(t),xo(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=X.decodePiece(r);i.includes(t.group)&&(xo(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=Fo(e,t),((e,t)=>-1===Go(e,t))(e,n))$o(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=Do(e,o);t>n&&(n=t)}return n})(e,l);Uo(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Fo(e,g)){const o=Oo(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])){a=!0;break}}if(a&&yo(e)===De.ANY){const n=jo(e,t)+1;bo(e,t,{d:p,ts:o,points:n}),u()}else bo(e,t,{d:p,ts:o}),u();a&&fo(e)===_e.REAL&&vo(e)===mo(e)&&(Co(e,{finished:o}),r()),a&&l&&l(t)}}else bo(e,t,{d:p,ts:o}),u();s._last_mouse=i}else if(p===Ht){const l=n[1],a=n[2];bo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else if(p===Yt){const l=n[1],a=n[2];bo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else bo(e,t,{ts:o}),u();return function(e,t,n){ro[e].evtInfos[t]=n}(e,t,s),i}};let Yo=-10,qo=20,Qo=2,Zo=15;class Xo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Yo+Math.random()*qo,this.vy=-1*(Qo+Math.random()*Zo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Qo=t/2,Zo=t-Qo;const n=1/4*this.canvas.width/(t/2);Yo=-n,qo=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Xo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Xo(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{localStorage.setItem(e,t)},rl=e=>localStorage.getItem(e);var dl=(e,t)=>{il(e,`${t}`)},cl=(e,t)=>{const n=rl(e);if(null===n)return t;const o=parseInt(n,10);return isNaN(o)?t:o},ul=(e,t)=>{il(e,t?"1":"0")},pl=(e,t)=>{const n=rl(e);return null===n?t:"1"===n},gl=(e,t)=>{il(e,t)},hl=(e,t)=>{const n=rl(e);return null===n?t:n};const ml={"./grab.png":Wn,"./grab_mask.png":Kn,"./hand.png":Hn,"./hand_mask.png":Yn},yl={"./click.mp3":jn};let fl=!0,wl=!0;let vl=!0;async function bl(e,t,n,o,l,a){void 0===window.DEBUG&&(window.DEBUG=!1);const s=yl["./click.mp3"].default,i=new Audio(s),r=await Qn.loadImageToBitmap(ml["./grab.png"].default),d=await Qn.loadImageToBitmap(ml["./hand.png"].default),c=await Qn.loadImageToBitmap(ml["./grab_mask.png"].default),u=await Qn.loadImageToBitmap(ml["./hand_mask.png"].default),p=r.width,g=Math.round(p/2),h=r.height,m=Math.round(h/2),y={},f=async e=>{const t=e.color+" "+e.d;if(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Qn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},w=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,vl=!0})),t}(l,Qn.createCanvas()),v={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};bn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=v.dataOffset;v.dataOffset+=1e4;const n=await bn.requestReplayData(e,t);return v.log=v.log.slice(v.logPointer),v.logPointer=0,v.log.push(...n.log),0===n.log.length&&(v.final=!0),n};let C=()=>0;const x=async()=>{if("play"===o){const o=await bn.connect(n,e,t),l=X.decodeGame(o);Ho.setGame(l.id,l),C=()=>V()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=X.decodeGame(t.game);Ho.setGame(n.id,n),v.lastRealTs=V(),v.gameStartTs=parseInt(t.log[0][4],10),v.lastGameTs=v.gameStartTs,C=()=>v.lastGameTs}}vl=!0};await x();const k=Ho.getPieceDrawOffset(e),P=Ho.getPieceDrawSize(e),A=Ho.getPuzzleWidth(e),S=Ho.getPuzzleHeight(e),T=Ho.getTableWidth(e),z=Ho.getTableHeight(e),I={x:(T-A)/2,y:(z-S)/2},D={w:A,h:S},E={w:P,h:P},M=await io.loadPuzzleBitmaps(Ho.getPuzzle(e)),_=new el(w,Ho.getRng(e));_.init();const O=w.getContext("2d");w.classList.add("loaded");const U=function(){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)},a=(e,t)=>{if(n==e)return!1;const l=1-n/e;return o(-t.x*l,-t.y*l),n=e,!0},s=o=>({x:o.x/n-e,y:o.y/n-t}),i=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),r=e=>({w:e.w*n,h:e.h*n}),d=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,reset:()=>{e=0,t=0,n=1},move:o,canZoom:e=>n!=l(e),zoom:(e,t)=>a(l(e),t),setZoom:a,worldToViewport:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:r,viewportToWorld:e=>{const{x:t,y:n}=s(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:s,viewportDimToWorld:e=>{const{w:t,h:n}=d(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:d}}(),B=()=>{U.reset(),U.move(-(T-w.width)/2,-(z-w.height)/2);const e=U.worldDimToViewport(D),t=w.width-40,n=w.height-40;if(e.w>t||e.h>n||e.w{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&w([jt,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&w([Wt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),w([Kt,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Ht:Yt;w([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&w([Xt]),"replay"===o&&("KeyI"===e.code&&w([tn]),"KeyO"===e.code&&w([nn]),"KeyP"===e.code&&w([en])),"KeyF"===e.code&&(fl=!fl,vl=!0),"KeyG"===e.code&&(wl=!wl,vl=!0),"KeyM"===e.code&&w([Jt]),"KeyN"===e.code&&w([on]),"KeyC"===e.code&&w([ln]))}));const w=e=>{l.push(e)};return{addEvent:w,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});w([Ft,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();w([Ht,...e])}}else if(u&&n.canZoom("out")){const e=f||m();w([Yt,...e])}},setHotkeys:e=>{a=e}}}(w,window,U,o),G=Ho.getImageUrl(e),$=()=>{const t=Ho.getStartTs(e),n=Ho.getFinishTs(e),o=C();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(Ho.getFinishedPiecesCount(e)),a.setPiecesTotal(Ho.getPieceCount(e));const L=C();a.setActivePlayers(Ho.getActivePlayers(e,L)),a.setIdlePlayers(Ho.getIdlePlayers(e,L));const F=!!Ho.getFinishTs(e);let j=F;const W=()=>j&&!F,K=()=>cl(tl,100),H=()=>pl(nl,!1),Y=()=>pl(sl,!0),q=()=>{const e=K();i.volume=e/100,i.play()},Q=()=>"replay"===o?hl(ol,"#222222"):Ho.getPlayerBgColor(e,t)||hl(ol,"#222222"),Z=()=>"replay"===o?hl(ll,"#ffffff"):Ho.getPlayerColor(e,t)||hl(ll,"#ffffff");let J="",ee="",te=!1;const ne=e=>{te=e;const[t,n]=e?[J,"grab"]:[ee,"default"];w.style.cursor=`url('${t}') ${g} ${m}, ${n}`},oe=e=>{J=Qn.colorizedCanvas(r,c,e).toDataURL(),ee=Qn.colorizedCanvas(d,u,e).toDataURL(),ne(te)};oe(Z());const le=()=>{a.setReplaySpeed&&a.setReplaySpeed(v.speeds[v.speedIdx]),a.setReplayPaused&&a.setReplayPaused(v.paused)},ae=()=>{v.speedIdx+1{v.speedIdx>=1&&(v.speedIdx--,le())},ie=()=>{v.paused=!v.paused,le()},re=[];let de;let ce;if("play"===o?re.push(setInterval((()=>{$()}),1e3)):"replay"===o&&le(),"play"===o)bn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case rn:{const n=X.decodePlayer(a);n.id!==t&&(Ho.setPlayer(e,n.id,n),vl=!0)}break;case sn:{const t=X.decodePiece(a);Ho.setPiece(e,t.idx,t),vl=!0}break;case an:Ho.setPuzzleData(e,a),vl=!0}j=!!Ho.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===Gt){const t=o[1];return Ho.addPlayer(e,t,n),!0}if(o[0]===$t){const t=Ho.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Ho.addPlayer(e,t,n),!0}if(o[0]===Lt){const t=Ho.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Ho.handleInput(e,t,l,n),!0}return!1};let n=v.lastGameTs;const o=async()=>{v.logPointer+1>=v.log.length&&await b(e);const l=V();if(v.paused)return v.lastRealTs=l,void(de=setTimeout(o,50));const a=(l-v.lastRealTs)*v.speeds[v.speedIdx];let s=v.lastGameTs+a;for(;;){if(v.paused)break;const e=v.logPointer+1;if(e>=v.log.length)break;const o=v.log[v.logPointer],l=n+o[o.length-1],a=v.log[e],i=a[a.length-1],r=l+i;if(r>s){s+500*N{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/o),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{R.createKeyEvents();for(const n of R.consumeAll())if("play"===o){const o=n[0];if(o===Ft){const e=n[1],t=n[2],o=U.worldDimToViewport({w:e,h:t});vl=!0,U.move(o.w,o.h)}else if(o===Kt){if(ue&&!Ho.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-ue.x),l=Math.round(t.y-ue.y);vl=!0,U.move(o,l),ue=t}}else if(o===Qt)oe(n[1]);else if(o===jt){const e={x:n[1],y:n[2]};ue=U.worldToViewport(e),ne(!0)}else if(o===Wt)ue=null,ne(!1);else if(o===Ht){const e={x:n[1],y:n[2]};vl=!0,U.zoom("in",U.worldToViewport(e))}else if(o===Yt){const e={x:n[1],y:n[2]};vl=!0,U.zoom("out",U.worldToViewport(e))}else o===Xt?a.togglePreview():o===Jt?a.toggleSoundsEnabled():o===on?a.togglePlayerNames():o===ln&&B();const l=C();Ho.handleInput(e,t,n,l,(e=>{H()&&q()})).length>0&&(vl=!0),bn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===en)ie();else if(e===nn)se();else if(e===tn)ae();else if(e===Ft){const e=n[1],t=n[2];vl=!0,U.move(e,t)}else if(e===Kt){if(ue){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-ue.x),l=Math.round(t.y-ue.y);vl=!0,U.move(o,l),ue=t}}else if(e===Qt)oe(n[1]);else if(e===jt){const e={x:n[1],y:n[2]};ue=U.worldToViewport(e),ne(!0)}else if(e===Wt)ue=null,ne(!1);else if(e===Ht){const e={x:n[1],y:n[2]};vl=!0,U.zoom("in",U.worldToViewport(e))}else if(e===Yt){const e={x:n[1],y:n[2]};vl=!0,U.zoom("out",U.worldToViewport(e))}else e===Xt?a.togglePreview():e===Jt?a.toggleSoundsEnabled():e===on?a.togglePlayerNames():e===ln&&B()}j=!!Ho.getFinishTs(e),W()&&(_.update(),vl=!0)},render:async()=>{if(!vl)return;const n=C();let l,s,i;window.DEBUG&&eo(0),O.fillStyle=Q(),O.fillRect(0,0,w.width,w.height),window.DEBUG&&to("clear done"),l=U.worldToViewportRaw(I),s=U.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&to("board done");const r=Ho.getPiecesSortedByZIndex(e);window.DEBUG&&to("get tiles done"),s=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?fl:wl)&&(i=M[e.idx],l=U.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&to("tiles done");const d=[];for(const a of Ho.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(i=await f(a),l=U.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),Y()&&d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,o]of d)O.fillText(e,t,o);window.DEBUG&&to("players done"),a.setActivePlayers(Ho.getActivePlayers(e,n)),a.setIdlePlayers(Ho.getIdlePlayers(e,n)),a.setPiecesDone(Ho.getFinishedPiecesCount(e)),window.DEBUG&&to("HUD done"),W()&&_.render(),vl=!1}}),{setHotkeys:e=>{R.setHotkeys(e)},onBgChange:e=>{gl(ol,e),R.addEvent([qt,e])},onColorChange:e=>{gl(ll,e),R.addEvent([Qt,e])},onNameChange:e=>{gl(al,e),R.addEvent([Zt,e])},onSoundsEnabledChange:e=>{ul(nl,e)},onSoundsVolumeChange:e=>{dl(tl,e),q()},onShowPlayerNamesChange:e=>{ul(sl,e)},replayOnSpeedUp:ae,replayOnSpeedDown:se,replayOnPauseToggle:ie,previewImageUrl:G,player:{background:Q(),color:Z(),name:"replay"===o?hl(al,"anon"):Ho.getPlayerName(e,t)||hl(al,"anon"),soundsEnabled:H(),soundsVolume:K(),showPlayerNames:Y()},disconnect:bn.disconnect,connect:x,unload:()=>{re.forEach((e=>{clearInterval(e)})),de&&clearTimeout(de),ce&&ce.stop()}}}var Cl=e({name:"game",components:{PuzzleStatus:xt,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,ConnectionOverlay:Cn,HelpOverlay:Tn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await bl(`${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},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 xl={id:"game"},kl={class:"menu"},Pl={class:"tabs"},Al=i("🧩 Puzzles");Cl.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),w=a("scores");return s(),t("div",xl,[p(n(u,{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"]),[[x,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",kl,[n("div",Pl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Al])),_: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))},"ℹ️ Hotkeys")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var Sl=e({name:"replay",components:{PuzzleStatus:xt,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,HelpOverlay:Tn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await bl(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 Tl={id:"replay"},zl={class:"menu"},Il={class:"tabs"},Dl=i("🧩 Puzzles");Sl.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),w=a("scores");return s(),t("div",Tl,[p(n(g,{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"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick: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",zl,[n("div",Il,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Dl])),_: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))},"ℹ️ Hotkeys")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=k({history:P(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:it},{name:"game",path:"/g/:id",component:Cl},{name:"replay",path:"/replay/:id",component:Sl}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=A(S);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=X.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 d0cec69..24f5542 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,7 +4,7 @@ 🧩 jigsaw.hyottoko.club - + diff --git a/build/server/main.js b/build/server/main.js index 4a39918..7e7a143 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -11,29 +11,6 @@ import sharp from 'sharp'; import v8 from 'v8'; import bsqlite from 'better-sqlite3'; -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 = {})); -var ShapeMode; -(function (ShapeMode) { - ShapeMode[ShapeMode["NORMAL"] = 0] = "NORMAL"; - ShapeMode[ShapeMode["ANY"] = 1] = "ANY"; - ShapeMode[ShapeMode["FLAT"] = 2] = "FLAT"; -})(ShapeMode || (ShapeMode = {})); -var SnapMode; -(function (SnapMode) { - SnapMode[SnapMode["NORMAL"] = 0] = "NORMAL"; - SnapMode[SnapMode["REAL"] = 1] = "REAL"; -})(SnapMode || (SnapMode = {})); - class Rng { constructor(seed) { this.rand_high = seed || 0xDEADC0DE; @@ -175,9 +152,9 @@ function encodeGame(data) { data.puzzle, data.players, data.evtInfos, - data.scoreMode || ScoreMode.FINAL, - data.shapeMode || ShapeMode.ANY, - data.snapMode || SnapMode.NORMAL, + data.scoreMode, + data.shapeMode, + data.snapMode, ]; } function decodeGame(data) { @@ -494,6 +471,47 @@ var Time = { durationStr, }; +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 = {})); +var ShapeMode; +(function (ShapeMode) { + ShapeMode[ShapeMode["NORMAL"] = 0] = "NORMAL"; + ShapeMode[ShapeMode["ANY"] = 1] = "ANY"; + ShapeMode[ShapeMode["FLAT"] = 2] = "FLAT"; +})(ShapeMode || (ShapeMode = {})); +var SnapMode; +(function (SnapMode) { + SnapMode[SnapMode["NORMAL"] = 0] = "NORMAL"; + SnapMode[SnapMode["REAL"] = 1] = "REAL"; +})(SnapMode || (SnapMode = {})); +const DefaultScoreMode = (v) => { + if (v === ScoreMode.FINAL || v === ScoreMode.ANY) { + return v; + } + return ScoreMode.FINAL; +}; +const DefaultShapeMode = (v) => { + if (v === ShapeMode.NORMAL || v === ShapeMode.ANY || v === ShapeMode.FLAT) { + return v; + } + return ShapeMode.NORMAL; +}; +const DefaultSnapMode = (v) => { + if (v === SnapMode.NORMAL || v === SnapMode.REAL) { + return v; + } + return SnapMode.NORMAL; +}; + const IDLE_TIMEOUT_SEC = 30; // Map const GAMES = {}; @@ -614,10 +632,10 @@ function setImageUrl(gameId, imageUrl) { GAMES[gameId].puzzle.info.imageUrl = imageUrl; } function getScoreMode(gameId) { - return GAMES[gameId].scoreMode || ScoreMode.FINAL; + return GAMES[gameId].scoreMode; } function getSnapMode(gameId) { - return GAMES[gameId].snapMode || SnapMode.NORMAL; + return GAMES[gameId].snapMode; } function isFinished(gameId) { return getFinishedPiecesCount(gameId) === getPieceCount(gameId); @@ -1323,10 +1341,16 @@ const get = (gameId, offset = 0) => { if (!fs.existsSync(file)) { return []; } - const log = fs.readFileSync(file, 'utf-8').split("\n"); - return log.filter(line => !!line).map(line => { + const lines = fs.readFileSync(file, 'utf-8').split("\n"); + const log = lines.filter(line => !!line).map(line => { return JSON.parse(`[${line}]`); }); + if (offset === 0 && log.length > 0) { + log[0][5] = DefaultScoreMode(log[0][5]); + log[0][6] = DefaultShapeMode(log[0][6]); + log[0][7] = DefaultSnapMode(log[0][7]); + } + return log; }; var GameLog = { shouldLog, @@ -1759,9 +1783,9 @@ function loadGame(gameId) { puzzle: game.puzzle, players: game.players, evtInfos: {}, - scoreMode: game.scoreMode || ScoreMode.FINAL, - shapeMode: game.shapeMode || ShapeMode.ANY, - snapMode: game.snapMode || SnapMode.NORMAL, + scoreMode: DefaultScoreMode(game.scoreMode), + shapeMode: DefaultShapeMode(game.shapeMode), + snapMode: DefaultSnapMode(game.snapMode), }; GameCommon.setGame(gameObject.id, gameObject); } @@ -2090,7 +2114,7 @@ app.get('/api/replay-data', async (req, res) => { let game = null; if (offset === 0) { // also need the game - game = await Game.createGameObject(gameId, log[0][2], log[0][3], log[0][4], log[0][5] || ScoreMode.FINAL, log[0][6] || ShapeMode.NORMAL, log[0][7] || SnapMode.NORMAL); + game = await Game.createGameObject(gameId, log[0][2], log[0][3], log[0][4], log[0][5], log[0][6], log[0][7]); } res.send({ log, game: game ? Util.encodeGame(game) : null }); }); diff --git a/src/common/GameCommon.ts b/src/common/GameCommon.ts index c2793aa..e2f18eb 100644 --- a/src/common/GameCommon.ts +++ b/src/common/GameCommon.ts @@ -170,11 +170,11 @@ function setImageUrl(gameId: string, imageUrl: string): void { } function getScoreMode(gameId: string): ScoreMode { - return GAMES[gameId].scoreMode || ScoreMode.FINAL + return GAMES[gameId].scoreMode } function getSnapMode(gameId: string): SnapMode { - return GAMES[gameId].snapMode || SnapMode.NORMAL + return GAMES[gameId].snapMode } function isFinished(gameId: string): boolean { diff --git a/src/common/Types.ts b/src/common/Types.ts index 90de5b3..0e8d21d 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -75,9 +75,9 @@ export interface Game { players: Array puzzle: Puzzle evtInfos: Record - scoreMode?: ScoreMode - shapeMode?: ShapeMode - snapMode?: SnapMode + scoreMode: ScoreMode + shapeMode: ShapeMode + snapMode: SnapMode rng: GameRng } @@ -216,3 +216,24 @@ export enum SnapMode { NORMAL = 0, REAL = 1, } + +export const DefaultScoreMode = (v: any): ScoreMode => { + if (v === ScoreMode.FINAL || v === ScoreMode.ANY) { + return v + } + return ScoreMode.FINAL +} + +export const DefaultShapeMode = (v: any): ShapeMode => { + if (v === ShapeMode.NORMAL || v === ShapeMode.ANY || v === ShapeMode.FLAT) { + return v + } + return ShapeMode.NORMAL +} + +export const DefaultSnapMode = (v: any): SnapMode => { + if (v === SnapMode.NORMAL || v === SnapMode.REAL) { + return v + } + return SnapMode.NORMAL +} diff --git a/src/common/Util.ts b/src/common/Util.ts index 4077834..dbf94b6 100644 --- a/src/common/Util.ts +++ b/src/common/Util.ts @@ -130,9 +130,9 @@ function encodeGame(data: Game): EncodedGame { data.puzzle, data.players, data.evtInfos, - data.scoreMode || ScoreMode.FINAL, - data.shapeMode || ShapeMode.ANY, - data.snapMode || SnapMode.NORMAL, + data.scoreMode, + data.shapeMode, + data.snapMode, ] } diff --git a/src/server/GameLog.ts b/src/server/GameLog.ts index 859610f..c6a14ad 100644 --- a/src/server/GameLog.ts +++ b/src/server/GameLog.ts @@ -1,7 +1,7 @@ import fs from 'fs' import Protocol from '../common/Protocol' import Time from '../common/Time' -import { Timestamp } from '../common/Types' +import { DefaultScoreMode, DefaultShapeMode, DefaultSnapMode, Timestamp } from '../common/Types' import { logger } from './../common/Util' import { DATA_DIR } from './../server/Dirs' @@ -82,10 +82,16 @@ const get = ( return [] } - const log = fs.readFileSync(file, 'utf-8').split("\n") - return log.filter(line => !!line).map(line => { + const lines = fs.readFileSync(file, 'utf-8').split("\n") + const log = lines.filter(line => !!line).map(line => { return JSON.parse(`[${line}]`) }) + if (offset === 0 && log.length > 0) { + log[0][5] = DefaultScoreMode(log[0][5]) + log[0][6] = DefaultShapeMode(log[0][6]) + log[0][7] = DefaultSnapMode(log[0][7]) + } + return log } export default { diff --git a/src/server/GameStorage.ts b/src/server/GameStorage.ts index b02c908..35d0023 100644 --- a/src/server/GameStorage.ts +++ b/src/server/GameStorage.ts @@ -1,6 +1,6 @@ import fs from 'fs' import GameCommon from './../common/GameCommon' -import { Game, Piece, ScoreMode, ShapeMode, SnapMode } from './../common/Types' +import { DefaultScoreMode, DefaultShapeMode, DefaultSnapMode, Game, Piece } from './../common/Types' import Util, { logger } from './../common/Util' import { Rng } from './../common/Rng' import { DATA_DIR } from './Dirs' @@ -58,9 +58,9 @@ function loadGame(gameId: string): void { puzzle: game.puzzle, players: game.players, evtInfos: {}, - scoreMode: game.scoreMode || ScoreMode.FINAL, - shapeMode: game.shapeMode || ShapeMode.ANY, - snapMode: game.snapMode || SnapMode.NORMAL, + scoreMode: DefaultScoreMode(game.scoreMode), + shapeMode: DefaultShapeMode(game.shapeMode), + snapMode: DefaultSnapMode(game.snapMode), } GameCommon.setGame(gameObject.id, gameObject) } diff --git a/src/server/main.ts b/src/server/main.ts index db403c5..119bf9a 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -89,9 +89,9 @@ app.get('/api/replay-data', async (req, res): Promise => { log[0][2], log[0][3], log[0][4], - log[0][5] || ScoreMode.FINAL, - log[0][6] || ShapeMode.NORMAL, - log[0][7] || SnapMode.NORMAL, + log[0][5], + log[0][6], + log[0][7], ) } res.send({ log, game: game ? Util.encodeGame(game) : null }) From 7759cdc806a27db8c8c8513d7f10ae17799ad5a5 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Thu, 8 Jul 2021 00:25:12 +0200 Subject: [PATCH 28/45] show message while cutting puzzle --- .../assets/{index.cc6b4801.js => index.19dfb063.js} | 2 +- build/public/index.html | 2 +- src/frontend/game.ts | 2 ++ src/frontend/views/Game.vue | 8 ++++++++ src/frontend/views/Replay.vue | 8 ++++++++ 5 files changed, 20 insertions(+), 2 deletions(-) rename build/public/assets/{index.cc6b4801.js => index.19dfb063.js} (64%) diff --git a/build/public/assets/index.cc6b4801.js b/build/public/assets/index.19dfb063.js similarity index 64% rename from build/public/assets/index.cc6b4801.js rename to build/public/assets/index.19dfb063.js index 7e3c4be..9a09aad 100644 --- a/build/public/assets/index.cc6b4801.js +++ b/build/public/assets/index.19dfb063.js @@ -1 +1 @@ -import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as w,m as v,n as b,q as C,s as x,u as k,x as P,y as A}from"./vendor.684f7bc8.js";var S=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const T={id:"app"},z={key:0,class:"nav"},I=i("Games overview"),D=i("New game");S.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",T,[e.showNav?(s(),t("ul",z,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[D])),_:1})])])):l("",!0),n(g)])};const E=864e5,M=e=>{const t=Math.floor(e/E);e%=E;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 N=1,_=1e3,V=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>M(t-e),U=M,B=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||V();return`${n} ${O(o,l)}`}}});const R={class:"game-info-text"},G=n("br",null,null,-1),$=n("br",null,null,-1),L=n("br",null,null,-1),F=i(" ↪️ Watch replay ");B.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),G,i(" 👥 "+r(e.game.players),1),$,i(" "+r(e.time(e.game.started,e.game.finished)),1),L])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[F])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:B},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 W=n("h1",null,"Running games",-1),K=n("h1",null,"Finished games",-1);j.render=function(e,o,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(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)),K,(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 H=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});H.render=function(e,o,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y=e({name:"image-library",components:{ImageTeaser:H},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});Y.render=function(e,n,o,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])};class q{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new q(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const Q=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},Z=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=Q(o.getHours(),"00"),a=Q(o.getMinutes(),"00"),s=Q(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var X={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",q.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode,e.shapeMode,e.snapMode]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:q.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const J={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};J.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var ee=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const te=m();y("data-v-a4fa5e7e");const ne={key:0,class:"autocomplete"};f();const oe=te(((e,o,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ne,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));ee.render=oe,ee.__scopeId="data-v-a4fa5e7e";const le=Z("NewImageDialog.vue");var ae=e({name:"new-image-dialog",components:{ResponsiveImage:J,TagsInput:ee},props:{autocompleteTags:{type:Function},uploadProgress:{type:Number},uploading:{type:String}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{uploadProgressPercent(){return this.uploadProgress?Math.round(100*this.uploadProgress):0},canPostToGallery(){return!this.uploading&&!(!this.previewUrl||!this.file)},canSetupGameClick(){return!this.uploading&&!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){le.info("onDragleave"),this.droppable=!1}}});const se=n("div",{class:"drop-target"},null,-1),ie={key:0,class:"has-image"},re={key:1},de={class:"upload"},ce=n("span",{class:"btn"},"Upload File",-1),ue={class:"area-settings"},pe=n("td",null,[n("label",null,"Title")],-1),ge=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),he=n("td",null,[n("label",null,"Tags")],-1),me={class:"area-buttons"},ye=i("🖼️ Post to gallery"),fe=i("🧩 Post to gallery "),we=n("br",null,null,-1),ve=i(" + set up game");ae.render=function(e,o,l,c,h,m){const y=a("responsive-image"),f=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[se,e.previewUrl?(s(),t("div",ie,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(y,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",re,[n("label",de,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ce])]))],34),n("div",ue,[n("table",null,[n("tr",null,[pe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),ge,n("tr",null,[he,n("td",null,[n(f,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",me,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},["postToGallery"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[ye],64))],8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},["setupGame"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[fe,we,ve],64))],8,["disabled"])])])])};var be=e({name:"edit-image-dialog",components:{ResponsiveImage:J,TagsInput:ee},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Ce={class:"area-image"},xe={class:"has-image"},ke={class:"area-settings"},Pe=n("td",null,[n("label",null,"Title")],-1),Ae=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Se=n("td",null,[n("label",null,"Tags")],-1),Te={class:"area-buttons"};var ze,Ie,De,Ee,Me,Ne,_e,Ve;be.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Ce,[n("div",xe,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ke,[n("table",null,[n("tr",null,[Pe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ae,n("tr",null,[Se,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",Te,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])},(Ie=ze||(ze={}))[Ie.Flat=0]="Flat",Ie[Ie.Out=1]="Out",Ie[Ie.In=-1]="In",(Ee=De||(De={}))[Ee.FINAL=0]="FINAL",Ee[Ee.ANY=1]="ANY",(Ne=Me||(Me={}))[Ne.NORMAL=0]="NORMAL",Ne[Ne.ANY=1]="ANY",Ne[Ne.FLAT=2]="FLAT",(Ve=_e||(_e={}))[Ve.NORMAL=0]="NORMAL",Ve[Ve.REAL=1]="REAL";var Oe=e({name:"new-game-dialog",components:{ResponsiveImage:J},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:De.ANY,shapeMode:Me.NORMAL,snapMode:_e.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Ue={class:"area-image"},Be={class:"has-image"},Re={key:0,class:"image-title"},Ge={key:0,class:"image-title-title"},$e={key:1,class:"image-title-dim"},Le={class:"area-settings"},Fe=n("td",null,[n("label",null,"Pieces")],-1),je=n("td",null,[n("label",null,"Scoring: ")],-1),We=i(" Any (Score when pieces are connected to each other or on final location)"),Ke=n("br",null,null,-1),He=i(" Final (Score when pieces are put to their final location)"),Ye=n("td",null,[n("label",null,"Shapes: ")],-1),qe=i(" Normal"),Qe=n("br",null,null,-1),Ze=i(" Any (flat pieces can occur anywhere)"),Xe=n("br",null,null,-1),Je=i(" Flat (all pieces flat on all sides)"),et=n("td",null,[n("label",null,"Snapping: ")],-1),tt=i(" Normal (pieces snap to final destination and to each other)"),nt=n("br",null,null,-1),ot=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),lt={class:"area-buttons"};Oe.render=function(e,o,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Ue,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title||e.image.width||e.image.height?(s(),t("div",Re,[e.image.title?(s(),t("span",Ge,'"'+r(e.image.title)+'"',1)):l("",!0),e.image.width||e.image.height?(s(),t("span",$e,"("+r(e.image.width)+" ✕ "+r(e.image.height)+")",1)):l("",!0)])):l("",!0)]),n("div",Le,[n("table",null,[n("tr",null,[Fe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[je,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[w,e.scoreMode]]),We]),Ke,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[w,e.scoreMode]]),He])])]),n("tr",null,[Ye,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[w,e.shapeMode]]),qe]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[w,e.shapeMode]]),Ze]),Xe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[w,e.shapeMode]]),Je])])]),n("tr",null,[et,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[w,e.snapMode]]),tt]),nt,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[w,e.snapMode]]),ot])])])])]),n("div",lt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};const at=async(e,t,n)=>new Promise(((o,l)=>{const a=new window.XMLHttpRequest;a.open(e,t,!0),a.withCredentials=!0;for(const e in n.headers||{})a.setRequestHeader(e,n.headers[e]);a.addEventListener("load",(function(e){o({status:this.status,text:this.responseText,json:async()=>JSON.parse(this.responseText)})})),a.addEventListener("error",(function(e){l(new Error("xhr error"))})),a.upload&&n.onUploadProgress&&a.upload.addEventListener("progress",(function(e){n.onUploadProgress&&n.onUploadProgress(e)})),a.send(n.body)}));var st=(e,t)=>at("post",e,t),it=e({components:{ImageLibrary:Y,NewImageDialog:ae,EditImageDialog:be,NewGameDialog:Oe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:"",uploading:"",uploadProgress:0}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${X.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){this.uploadProgress=0;const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await st("/api/upload",{body:t,onUploadProgress:e=>{this.uploadProgress=e.loaded/e.total}});return this.uploadProgress=1,await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){this.uploading="postToGallery",await this.uploadImage(e),this.uploading="",this.dialog="",await this.loadImages()},async setupGameClick(e){this.uploading="setupGame";const t=await this.uploadImage(e);this.uploading="",this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const rt={class:"upload-image-teaser"},dt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),ct={key:0},ut=i(" Tags: "),pt=i(" Sort by: "),gt=n("option",{value:"date_desc"},"Newest first",-1),ht=n("option",{value:"date_asc"},"Oldest first",-1),mt=n("option",{value:"alpha_asc"},"A-Z",-1),yt=n("option",{value:"alpha_desc"},"Z-A",-1);it.render=function(e,o,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),w=a("new-game-dialog");return s(),t("div",null,[n("div",rt,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),dt]),n("div",null,[e.tags.length>0?(s(),t("label",ct,[ut,(s(!0),t(d,null,c(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[pt,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[gt,ht,mt,yt],544),[[v,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),uploadProgress:e.uploadProgress,uploading:e.uploading,onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","uploadProgress","uploading","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(w,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ft=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 wt={class:"scores"},vt=n("div",null,"Scores",-1),bt=n("td",null,"⚡",-1),Ct=n("td",null,"💤",-1);ft.render=function(e,o,l,a,i,u){return s(),t("div",wt,[vt,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}},[Ct,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var xt=e({name:"puzzle-status",props:{finished:{type:Boolean,required:!0},duration:{type:Number,required:!0},piecesDone:{type:Number,required:!0},piecesTotal:{type:Number,required:!0}},computed:{icon(){return this.finished?"🏁":"⏳"},durationStr(){return U(this.duration)}}});const kt={class:"timer"};xt.render=function(e,o,l,a,i,d){return s(),t("div",kt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var Pt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const At=m();y("data-v-4d56fc17");const St=n("td",null,[n("label",null,"Background: ")],-1),Tt=n("td",null,[n("label",null,"Color: ")],-1),zt=n("td",null,[n("label",null,"Name: ")],-1),It=n("td",null,[n("label",null,"Sounds: ")],-1),Dt=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Et={class:"sound-volume"},Mt=n("td",null,[n("label",null,"Show player names: ")],-1);f();const Nt=At(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[10]||(o[10]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[9]||(o[9]=u((()=>{}),["stop"]))},[n("tr",null,[St,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[Tt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[zt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[It,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[C,e.modelValue.soundsEnabled]])])]),n("tr",null,[Dt,n("td",Et,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])]),n("tr",null,[Mt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[8]||(o[8]=t=>e.modelValue.showPlayerNames=t)},null,512),[[C,e.modelValue.showPlayerNames]])])])])]))));Pt.render=Nt,Pt.__scopeId="data-v-4d56fc17";var _t=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const Vt={class:"preview"};_t.render=function(e,o,l,a,i,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",Vt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Ot=1,Ut=4,Bt=2,Rt=3,Gt=2,$t=4,Lt=3,Ft=9,jt=1,Wt=2,Kt=3,Ht=4,Yt=5,qt=6,Qt=7,Zt=8,Xt=10,Jt=11,en=12,tn=13,nn=14,on=15,ln=16,an=1,sn=2,rn=3;const dn=Z("Communication.js");let cn,un=[],pn=e=>{un.push(e)},gn=[],hn=e=>{gn.push(e)};let mn=0;const yn=e=>{mn!==e&&(mn=e,hn(e))};function fn(e){if(2===mn)try{cn.send(JSON.stringify(e))}catch(t){dn.info("unable to send message.. maybe because ws is invalid?")}}let wn,vn;var bn={connect:function(e,t,n){return wn=0,vn={},yn(3),new Promise((o=>{cn=new WebSocket(e,n+"|"+t),cn.onopen=()=>{yn(2),fn([Rt])},cn.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Ut){const e=t[1];o(e)}else{if(l!==Ot)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&vn[o])return void delete vn[o];pn(t)}}},cn.onerror=()=>{throw yn(1),"[ 2021-05-15 onerror ]"},cn.onclose=e=>{4e3===e.code||1001===e.code?yn(4):yn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${X.asQueryArgs(n)}`);return await o.json()},disconnect:function(){cn&&cn.close(4e3),wn=0,vn={}},sendClientEvent:function(e){wn++,vn[wn]=e,fn([Bt,wn,vn[wn]])},onServerChange:function(e){pn=e;for(const t of un)pn(t);un=[]},onConnectionStateChange:function(e){hn=e;for(const t of gn)hn(t);gn=[]},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},Cn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===bn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===bn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const xn={key:0,class:"overlay connection-lost"},kn={key:0,class:"overlay-content"},Pn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),An={key:1,class:"overlay-content"},Sn=n("div",null,"Connecting...",-1);Cn.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",xn,[e.lostConnection?(s(),t("div",kn,[Pn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",An,[Sn])):l("",!0)])):l("",!0)};var Tn=e({name:"help-overlay",emits:{bgclick:null}});const zn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),In=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),Dn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),En=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),Mn=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Nn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),_n=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),Vn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),On=n("tr",null,[n("td",null,"🎯 Center puzzle in screen:"),n("td",null,[n("div",null,[n("kbd",null,"C")])])],-1),Un=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Bn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Rn=n("tr",null,[n("td",null,"👤 Toggle player names:"),n("td",null,[n("div",null,[n("kbd",null,"N")])])],-1),Gn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),$n=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),Ln=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),Fn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);Tn.render=function(e,o,l,a,i,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]=u((()=>{}),["stop"]))},[zn,In,Dn,En,Mn,Nn,_n,Vn,On,Un,Bn,Rn,Gn,$n,Ln,Fn])])};var jn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Wn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Kn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Hn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Yn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function qn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Qn={createCanvas:qn,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=qn(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=qn(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(),o}};const Zn=Z("Debug.js");let Xn=0,Jn=0;var eo=e=>{Xn=performance.now(),Jn=e},to=e=>{const t=performance.now(),n=t-Xn;n>Jn&&Zn.log(e+": "+n),Xn=t};function no(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function oo(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var lo={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:no,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:oo,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 no(oo(e),oo(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const ao=Z("PuzzleGraphics.js");function so(e,t){const n=X.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var io={loadPuzzleBitmaps:async function(e){const t=await Qn.loadImageToBitmap(e.info.imageUrl),n=await Qn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){ao.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=lo.pointAdd(a,{x:o,y:0}),c=lo.pointAdd(r,{x:0,y:o}),u=lo.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;oX.decodePiece(ro[e].puzzle.tiles[t]),Po=(e,t)=>ko(e,t).group,Ao=(e,t)=>{const n=ro[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},So=(e,t)=>{const n=ro[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=ro[e].puzzle.info,o=X.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return lo.pointAdd(o,l)},To=(e,t)=>ko(e,t).pos,zo=e=>{const t=Wo(e),n=Ko(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}},Io=(e,t)=>{const n=No(e),o=ko(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Do=(e,t)=>ko(e,t).z,Eo=(e,t)=>{for(const n of ro[e].puzzle.tiles){const e=X.decodePiece(n);if(e.owner===t)return e.idx}return-1},Mo=e=>ro[e].puzzle.info.tileDrawSize,No=e=>ro[e].puzzle.info.tileSize,_o=e=>ro[e].puzzle.data.maxGroup,Vo=e=>ro[e].puzzle.data.maxZ;function Oo(e,t){const n=ro[e].puzzle.info,o=X.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Uo=(e,t,n)=>{for(const o of t)xo(e,o,{z:n})},Bo=(e,t,n)=>{const o=To(e,t);xo(e,t,{pos:lo.pointAdd(o,n)})},Ro=(e,t,n)=>{const o=Mo(e),l=zo(e),a=n;for(const s of t){const t=ko(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)Bo(e,s,a)},Go=(e,t)=>ko(e,t).owner,$o=(e,t)=>{for(const n of t)xo(e,n,{owner:-1,z:1})},Lo=(e,t,n)=>{for(const o of t)xo(e,o,{owner:n})};function Fo(e,t){const n=ro[e].puzzle.tiles,o=X.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=X.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const jo=(e,t)=>{const n=uo(e,t);return n?n.points:0},Wo=e=>ro[e].puzzle.info.table.width,Ko=e=>ro[e].puzzle.info.table.height;var Ho={setGame:function(e,t){ro[e]=t},exists:function(e){return!!ro[e]||!1},playerExists:go,getActivePlayers:function(e,t){const n=t-30*_;return ho(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return ho(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){go(e,t)?bo(e,t,{ts:n}):po(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:vo,getPieceCount:mo,getImageUrl:function(e){return ro[e].puzzle.info.imageUrl},setImageUrl:function(e,t){ro[e].puzzle.info.imageUrl=t},get:function(e){return ro[e]||null},getAllGames:function(){return Object.values(ro).sort(((e,t)=>wo(e.id)===wo(t.id)?t.puzzle.data.started-e.puzzle.data.started:wo(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=uo(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=uo(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=uo(e,t);return n?n.name:null},getPlayerIndexById:co,getPlayerIdByIndex:function(e,t){return ro[e].players.length>t?X.decodePlayer(ro[e].players[t]).id:null},changePlayer:bo,setPlayer:po,setPiece:function(e,t,n){ro[e].puzzle.tiles[t]=X.encodePiece(n)},setPuzzleData:function(e,t){ro[e].puzzle.data=t},getTableWidth:Wo,getTableHeight:Ko,getPuzzle:e=>ro[e].puzzle,getRng:e=>ro[e].rng.obj,getPuzzleWidth:e=>ro[e].puzzle.info.width,getPuzzleHeight:e=>ro[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return ro[e].puzzle.tiles.map(X.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Eo(e,t);return n<0?null:ro[e].puzzle.tiles[n]},getPieceDrawOffset:e=>ro[e].puzzle.info.tileDrawOffset,getPieceDrawSize:Mo,getFinalPiecePos:So,getStartTs:e=>ro[e].puzzle.data.started,getFinishTs:e=>ro[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=ro[e].puzzle,s=function(e,t){return t in ro[e].evtInfos?ro[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([an,a.data])},d=t=>{i.push([sn,X.encodePiece(ko(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=uo(e,t);n&&i.push([rn,X.encodePlayer(n)])},p=n[0];if(p===qt){const l=n[1];bo(e,t,{bgcolor:l,ts:o}),u()}else if(p===Qt){const l=n[1];bo(e,t,{color:l,ts:o}),u()}else if(p===Zt){const l=`${n[1]}`.substr(0,16);bo(e,t,{name:l,ts:o}),u()}else if(p===Ft){const l=n[1],a=n[2],s=uo(e,t);if(s){const n=s.x-l,i=s.y-a;bo(e,t,{ts:o,x:n,y:i}),u()}}else if(p===jt){const l={x:n[1],y:n[2]};bo(e,t,{d:1,ts:o}),u(),s._last_mouse_down=l;const a=((e,t)=>{const n=ro[e].puzzle.info,o=ro[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=Vo(e)+1;Co(e,{maxZ:n}),r();const o=Fo(e,a);Uo(e,o,Vo(e)),Lo(e,o,t),c(o)}s._last_mouse=l}else if(p===Kt){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)bo(e,t,{x:l,y:a,ts:o}),u();else{const n=Eo(e,t);if(n>=0){bo(e,t,{x:l,y:a,ts:o}),u();const r=Fo(e,n);let d=lo.pointInBounds(i,zo(e))&&lo.pointInBounds(s._last_mouse_down,zo(e));for(const t of r){const n=Io(e,t);if(lo.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Ro(e,r,{x:t,y:n}),c(r)}}else bo(e,t,{ts:o}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===Wt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Eo(e,t);if(g>=0){const n=Fo(e,g);Lo(e,n,0),c(n);const s=To(e,g),i=So(e,g);let h=!1;if(fo(e)===_e.REAL){for(const t of n)if(Ao(e,t)){h=!0;break}}else h=!0;if(h&&lo.pointDistance(i,s){const l=ro[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=Po(e,t),l=Po(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=To(e,t),s=lo.pointAdd(To(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(lo.pointDistance(a,s){const o=ro[e].puzzle.tiles,l=Po(e,t),a=Po(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(Co(e,{maxGroup:_o(e)+1}),r(),s=_o(e));if(xo(e,t,{group:s}),d(t),xo(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=X.decodePiece(r);i.includes(t.group)&&(xo(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=Fo(e,t),((e,t)=>-1===Go(e,t))(e,n))$o(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=Do(e,o);t>n&&(n=t)}return n})(e,l);Uo(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Fo(e,g)){const o=Oo(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])){a=!0;break}}if(a&&yo(e)===De.ANY){const n=jo(e,t)+1;bo(e,t,{d:p,ts:o,points:n}),u()}else bo(e,t,{d:p,ts:o}),u();a&&fo(e)===_e.REAL&&vo(e)===mo(e)&&(Co(e,{finished:o}),r()),a&&l&&l(t)}}else bo(e,t,{d:p,ts:o}),u();s._last_mouse=i}else if(p===Ht){const l=n[1],a=n[2];bo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else if(p===Yt){const l=n[1],a=n[2];bo(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else bo(e,t,{ts:o}),u();return function(e,t,n){ro[e].evtInfos[t]=n}(e,t,s),i}};let Yo=-10,qo=20,Qo=2,Zo=15;class Xo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Yo+Math.random()*qo,this.vy=-1*(Qo+Math.random()*Zo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Qo=t/2,Zo=t-Qo;const n=1/4*this.canvas.width/(t/2);Yo=-n,qo=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Xo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Xo(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{localStorage.setItem(e,t)},rl=e=>localStorage.getItem(e);var dl=(e,t)=>{il(e,`${t}`)},cl=(e,t)=>{const n=rl(e);if(null===n)return t;const o=parseInt(n,10);return isNaN(o)?t:o},ul=(e,t)=>{il(e,t?"1":"0")},pl=(e,t)=>{const n=rl(e);return null===n?t:"1"===n},gl=(e,t)=>{il(e,t)},hl=(e,t)=>{const n=rl(e);return null===n?t:n};const ml={"./grab.png":Wn,"./grab_mask.png":Kn,"./hand.png":Hn,"./hand_mask.png":Yn},yl={"./click.mp3":jn};let fl=!0,wl=!0;let vl=!0;async function bl(e,t,n,o,l,a){void 0===window.DEBUG&&(window.DEBUG=!1);const s=yl["./click.mp3"].default,i=new Audio(s),r=await Qn.loadImageToBitmap(ml["./grab.png"].default),d=await Qn.loadImageToBitmap(ml["./hand.png"].default),c=await Qn.loadImageToBitmap(ml["./grab_mask.png"].default),u=await Qn.loadImageToBitmap(ml["./hand_mask.png"].default),p=r.width,g=Math.round(p/2),h=r.height,m=Math.round(h/2),y={},f=async e=>{const t=e.color+" "+e.d;if(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(Qn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},w=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,vl=!0})),t}(l,Qn.createCanvas()),v={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};bn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=v.dataOffset;v.dataOffset+=1e4;const n=await bn.requestReplayData(e,t);return v.log=v.log.slice(v.logPointer),v.logPointer=0,v.log.push(...n.log),0===n.log.length&&(v.final=!0),n};let C=()=>0;const x=async()=>{if("play"===o){const o=await bn.connect(n,e,t),l=X.decodeGame(o);Ho.setGame(l.id,l),C=()=>V()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=X.decodeGame(t.game);Ho.setGame(n.id,n),v.lastRealTs=V(),v.gameStartTs=parseInt(t.log[0][4],10),v.lastGameTs=v.gameStartTs,C=()=>v.lastGameTs}}vl=!0};await x();const k=Ho.getPieceDrawOffset(e),P=Ho.getPieceDrawSize(e),A=Ho.getPuzzleWidth(e),S=Ho.getPuzzleHeight(e),T=Ho.getTableWidth(e),z=Ho.getTableHeight(e),I={x:(T-A)/2,y:(z-S)/2},D={w:A,h:S},E={w:P,h:P},M=await io.loadPuzzleBitmaps(Ho.getPuzzle(e)),_=new el(w,Ho.getRng(e));_.init();const O=w.getContext("2d");w.classList.add("loaded");const U=function(){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)},a=(e,t)=>{if(n==e)return!1;const l=1-n/e;return o(-t.x*l,-t.y*l),n=e,!0},s=o=>({x:o.x/n-e,y:o.y/n-t}),i=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),r=e=>({w:e.w*n,h:e.h*n}),d=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,reset:()=>{e=0,t=0,n=1},move:o,canZoom:e=>n!=l(e),zoom:(e,t)=>a(l(e),t),setZoom:a,worldToViewport:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:r,viewportToWorld:e=>{const{x:t,y:n}=s(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:s,viewportDimToWorld:e=>{const{w:t,h:n}=d(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:d}}(),B=()=>{U.reset(),U.move(-(T-w.width)/2,-(z-w.height)/2);const e=U.worldDimToViewport(D),t=w.width-40,n=w.height-40;if(e.w>t||e.h>n||e.w{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&w([jt,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&w([Wt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),w([Kt,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Ht:Yt;w([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&w([Xt]),"replay"===o&&("KeyI"===e.code&&w([tn]),"KeyO"===e.code&&w([nn]),"KeyP"===e.code&&w([en])),"KeyF"===e.code&&(fl=!fl,vl=!0),"KeyG"===e.code&&(wl=!wl,vl=!0),"KeyM"===e.code&&w([Jt]),"KeyN"===e.code&&w([on]),"KeyC"===e.code&&w([ln]))}));const w=e=>{l.push(e)};return{addEvent:w,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});w([Ft,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();w([Ht,...e])}}else if(u&&n.canZoom("out")){const e=f||m();w([Yt,...e])}},setHotkeys:e=>{a=e}}}(w,window,U,o),G=Ho.getImageUrl(e),$=()=>{const t=Ho.getStartTs(e),n=Ho.getFinishTs(e),o=C();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(Ho.getFinishedPiecesCount(e)),a.setPiecesTotal(Ho.getPieceCount(e));const L=C();a.setActivePlayers(Ho.getActivePlayers(e,L)),a.setIdlePlayers(Ho.getIdlePlayers(e,L));const F=!!Ho.getFinishTs(e);let j=F;const W=()=>j&&!F,K=()=>cl(tl,100),H=()=>pl(nl,!1),Y=()=>pl(sl,!0),q=()=>{const e=K();i.volume=e/100,i.play()},Q=()=>"replay"===o?hl(ol,"#222222"):Ho.getPlayerBgColor(e,t)||hl(ol,"#222222"),Z=()=>"replay"===o?hl(ll,"#ffffff"):Ho.getPlayerColor(e,t)||hl(ll,"#ffffff");let J="",ee="",te=!1;const ne=e=>{te=e;const[t,n]=e?[J,"grab"]:[ee,"default"];w.style.cursor=`url('${t}') ${g} ${m}, ${n}`},oe=e=>{J=Qn.colorizedCanvas(r,c,e).toDataURL(),ee=Qn.colorizedCanvas(d,u,e).toDataURL(),ne(te)};oe(Z());const le=()=>{a.setReplaySpeed&&a.setReplaySpeed(v.speeds[v.speedIdx]),a.setReplayPaused&&a.setReplayPaused(v.paused)},ae=()=>{v.speedIdx+1{v.speedIdx>=1&&(v.speedIdx--,le())},ie=()=>{v.paused=!v.paused,le()},re=[];let de;let ce;if("play"===o?re.push(setInterval((()=>{$()}),1e3)):"replay"===o&&le(),"play"===o)bn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case rn:{const n=X.decodePlayer(a);n.id!==t&&(Ho.setPlayer(e,n.id,n),vl=!0)}break;case sn:{const t=X.decodePiece(a);Ho.setPiece(e,t.idx,t),vl=!0}break;case an:Ho.setPuzzleData(e,a),vl=!0}j=!!Ho.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===Gt){const t=o[1];return Ho.addPlayer(e,t,n),!0}if(o[0]===$t){const t=Ho.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Ho.addPlayer(e,t,n),!0}if(o[0]===Lt){const t=Ho.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Ho.handleInput(e,t,l,n),!0}return!1};let n=v.lastGameTs;const o=async()=>{v.logPointer+1>=v.log.length&&await b(e);const l=V();if(v.paused)return v.lastRealTs=l,void(de=setTimeout(o,50));const a=(l-v.lastRealTs)*v.speeds[v.speedIdx];let s=v.lastGameTs+a;for(;;){if(v.paused)break;const e=v.logPointer+1;if(e>=v.log.length)break;const o=v.log[v.logPointer],l=n+o[o.length-1],a=v.log[e],i=a[a.length-1],r=l+i;if(r>s){s+500*N{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/o),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{R.createKeyEvents();for(const n of R.consumeAll())if("play"===o){const o=n[0];if(o===Ft){const e=n[1],t=n[2],o=U.worldDimToViewport({w:e,h:t});vl=!0,U.move(o.w,o.h)}else if(o===Kt){if(ue&&!Ho.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-ue.x),l=Math.round(t.y-ue.y);vl=!0,U.move(o,l),ue=t}}else if(o===Qt)oe(n[1]);else if(o===jt){const e={x:n[1],y:n[2]};ue=U.worldToViewport(e),ne(!0)}else if(o===Wt)ue=null,ne(!1);else if(o===Ht){const e={x:n[1],y:n[2]};vl=!0,U.zoom("in",U.worldToViewport(e))}else if(o===Yt){const e={x:n[1],y:n[2]};vl=!0,U.zoom("out",U.worldToViewport(e))}else o===Xt?a.togglePreview():o===Jt?a.toggleSoundsEnabled():o===on?a.togglePlayerNames():o===ln&&B();const l=C();Ho.handleInput(e,t,n,l,(e=>{H()&&q()})).length>0&&(vl=!0),bn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===en)ie();else if(e===nn)se();else if(e===tn)ae();else if(e===Ft){const e=n[1],t=n[2];vl=!0,U.move(e,t)}else if(e===Kt){if(ue){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-ue.x),l=Math.round(t.y-ue.y);vl=!0,U.move(o,l),ue=t}}else if(e===Qt)oe(n[1]);else if(e===jt){const e={x:n[1],y:n[2]};ue=U.worldToViewport(e),ne(!0)}else if(e===Wt)ue=null,ne(!1);else if(e===Ht){const e={x:n[1],y:n[2]};vl=!0,U.zoom("in",U.worldToViewport(e))}else if(e===Yt){const e={x:n[1],y:n[2]};vl=!0,U.zoom("out",U.worldToViewport(e))}else e===Xt?a.togglePreview():e===Jt?a.toggleSoundsEnabled():e===on?a.togglePlayerNames():e===ln&&B()}j=!!Ho.getFinishTs(e),W()&&(_.update(),vl=!0)},render:async()=>{if(!vl)return;const n=C();let l,s,i;window.DEBUG&&eo(0),O.fillStyle=Q(),O.fillRect(0,0,w.width,w.height),window.DEBUG&&to("clear done"),l=U.worldToViewportRaw(I),s=U.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&to("board done");const r=Ho.getPiecesSortedByZIndex(e);window.DEBUG&&to("get tiles done"),s=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?fl:wl)&&(i=M[e.idx],l=U.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&to("tiles done");const d=[];for(const a of Ho.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(i=await f(a),l=U.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),Y()&&d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,o]of d)O.fillText(e,t,o);window.DEBUG&&to("players done"),a.setActivePlayers(Ho.getActivePlayers(e,n)),a.setIdlePlayers(Ho.getIdlePlayers(e,n)),a.setPiecesDone(Ho.getFinishedPiecesCount(e)),window.DEBUG&&to("HUD done"),W()&&_.render(),vl=!1}}),{setHotkeys:e=>{R.setHotkeys(e)},onBgChange:e=>{gl(ol,e),R.addEvent([qt,e])},onColorChange:e=>{gl(ll,e),R.addEvent([Qt,e])},onNameChange:e=>{gl(al,e),R.addEvent([Zt,e])},onSoundsEnabledChange:e=>{ul(nl,e)},onSoundsVolumeChange:e=>{dl(tl,e),q()},onShowPlayerNamesChange:e=>{ul(sl,e)},replayOnSpeedUp:ae,replayOnSpeedDown:se,replayOnPauseToggle:ie,previewImageUrl:G,player:{background:Q(),color:Z(),name:"replay"===o?hl(al,"anon"):Ho.getPlayerName(e,t)||hl(al,"anon"),soundsEnabled:H(),soundsVolume:K(),showPlayerNames:Y()},disconnect:bn.disconnect,connect:x,unload:()=>{re.forEach((e=>{clearInterval(e)})),de&&clearTimeout(de),ce&&ce.stop()}}}var Cl=e({name:"game",components:{PuzzleStatus:xt,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,ConnectionOverlay:Cn,HelpOverlay:Tn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await bl(`${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},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 xl={id:"game"},kl={class:"menu"},Pl={class:"tabs"},Al=i("🧩 Puzzles");Cl.render=function(e,l,i,r,d,c){const u=a("settings-overlay"),g=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),w=a("scores");return s(),t("div",xl,[p(n(u,{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"]),[[x,"settings"===e.overlay]]),p(n(g,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",kl,[n("div",Pl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Al])),_: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))},"ℹ️ Hotkeys")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var Sl=e({name:"replay",components:{PuzzleStatus:xt,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,HelpOverlay:Tn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await bl(`${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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 Tl={id:"replay"},zl={class:"menu"},Il={class:"tabs"},Dl=i("🧩 Puzzles");Sl.render=function(e,l,i,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),w=a("scores");return s(),t("div",Tl,[p(n(g,{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"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick: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",zl,[n("div",Il,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Dl])),_: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))},"ℹ️ Hotkeys")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=k({history:P(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:it},{name:"game",path:"/g/:id",component:Cl},{name:"replay",path:"/replay/:id",component:Sl}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=A(S);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=X.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); +import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as u,g as c,h as p,v as g,i as h,j as m,p as y,k as f,l as w,m as v,n as b,q as C,s as x,u as k,x as P,y as A}from"./vendor.684f7bc8.js";var S=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const z={id:"app"},T={key:0,class:"nav"},I=i("Games overview"),D=i("New game");S.render=function(e,i,r,d,u,c){const p=a("router-link"),g=a("router-view");return s(),t("div",z,[e.showNav?(s(),t("ul",T,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[D])),_:1})])])):l("",!0),n(g)])};const E=864e5,M=e=>{const t=Math.floor(e/E);e%=E;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 N=1,_=1e3,V=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>M(t-e),U=M,B=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||V();return`${n} ${O(o,l)}`}}});const R={class:"game-info-text"},G=n("br",null,null,-1),$=n("br",null,null,-1),L=n("br",null,null,-1),F=i(" ↪️ Watch replay ");B.render=function(e,d,u,c,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),G,i(" 👥 "+r(e.game.players),1),$,i(" "+r(e.time(e.game.started,e.game.finished)),1),L])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[F])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:B},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 W=n("h1",null,"Running games",-1),K=n("h1",null,"Finished games",-1);j.render=function(e,o,l,i,r,c){const p=a("game-teaser");return s(),t("div",null,[W,(s(!0),t(d,null,u(e.gamesRunning,((e,o)=>(s(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128)),K,(s(!0),t(d,null,u(e.gamesFinished,((e,o)=>(s(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128))])};var H=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});H.render=function(e,o,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=c(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y=e({name:"image-library",components:{ImageTeaser:H},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});Y.render=function(e,n,o,l,i,r){const c=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,u(e.images,((n,o)=>(s(),t(c,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])};class q{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new q(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const Q=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},Z=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=Q(o.getHours(),"00"),a=Q(o.getMinutes(),"00"),s=Q(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var X={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",q.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode,e.shapeMode,e.snapMode]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:q.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const J={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};J.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var ee=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const te=m();y("data-v-a4fa5e7e");const ne={key:0,class:"autocomplete"};f();const oe=te(((e,o,a,i,c,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ne,[n("ul",null,[(s(!0),t(d,null,u(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,u(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));ee.render=oe,ee.__scopeId="data-v-a4fa5e7e";const le=Z("NewImageDialog.vue");var ae=e({name:"new-image-dialog",components:{ResponsiveImage:J,TagsInput:ee},props:{autocompleteTags:{type:Function},uploadProgress:{type:Number},uploading:{type:String}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{uploadProgressPercent(){return this.uploadProgress?Math.round(100*this.uploadProgress):0},canPostToGallery(){return!this.uploading&&!(!this.previewUrl||!this.file)},canSetupGameClick(){return!this.uploading&&!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){le.info("onDragleave"),this.droppable=!1}}});const se=n("div",{class:"drop-target"},null,-1),ie={key:0,class:"has-image"},re={key:1},de={class:"upload"},ue=n("span",{class:"btn"},"Upload File",-1),ce={class:"area-settings"},pe=n("td",null,[n("label",null,"Title")],-1),ge=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),he=n("td",null,[n("label",null,"Tags")],-1),me={class:"area-buttons"},ye=i("🖼️ Post to gallery"),fe=i("🧩 Post to gallery "),we=n("br",null,null,-1),ve=i(" + set up game");ae.render=function(e,o,l,u,h,m){const y=a("responsive-image"),f=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=c((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[se,e.previewUrl?(s(),t("div",ie,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(y,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",re,[n("label",de,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ue])]))],34),n("div",ce,[n("table",null,[n("tr",null,[pe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),ge,n("tr",null,[he,n("td",null,[n(f,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",me,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},["postToGallery"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[ye],64))],8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},["setupGame"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[fe,we,ve],64))],8,["disabled"])])])])};var be=e({name:"edit-image-dialog",components:{ResponsiveImage:J,TagsInput:ee},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Ce={class:"area-image"},xe={class:"has-image"},ke={class:"area-settings"},Pe=n("td",null,[n("label",null,"Title")],-1),Ae=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Se=n("td",null,[n("label",null,"Tags")],-1),ze={class:"area-buttons"};var Te,Ie,De,Ee,Me,Ne,_e,Ve;be.render=function(e,o,l,i,r,d){const u=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=c((()=>{}),["stop"]))},[n("div",Ce,[n("div",xe,[n(u,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ke,[n("table",null,[n("tr",null,[Pe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ae,n("tr",null,[Se,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",ze,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])},(Ie=Te||(Te={}))[Ie.Flat=0]="Flat",Ie[Ie.Out=1]="Out",Ie[Ie.In=-1]="In",(Ee=De||(De={}))[Ee.FINAL=0]="FINAL",Ee[Ee.ANY=1]="ANY",(Ne=Me||(Me={}))[Ne.NORMAL=0]="NORMAL",Ne[Ne.ANY=1]="ANY",Ne[Ne.FLAT=2]="FLAT",(Ve=_e||(_e={}))[Ve.NORMAL=0]="NORMAL",Ve[Ve.REAL=1]="REAL";var Oe=e({name:"new-game-dialog",components:{ResponsiveImage:J},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:De.ANY,shapeMode:Me.NORMAL,snapMode:_e.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Ue={class:"area-image"},Be={class:"has-image"},Re={key:0,class:"image-title"},Ge={key:0,class:"image-title-title"},$e={key:1,class:"image-title-dim"},Le={class:"area-settings"},Fe=n("td",null,[n("label",null,"Pieces")],-1),je=n("td",null,[n("label",null,"Scoring: ")],-1),We=i(" Any (Score when pieces are connected to each other or on final location)"),Ke=n("br",null,null,-1),He=i(" Final (Score when pieces are put to their final location)"),Ye=n("td",null,[n("label",null,"Shapes: ")],-1),qe=i(" Normal"),Qe=n("br",null,null,-1),Ze=i(" Any (flat pieces can occur anywhere)"),Xe=n("br",null,null,-1),Je=i(" Flat (all pieces flat on all sides)"),et=n("td",null,[n("label",null,"Snapping: ")],-1),tt=i(" Normal (pieces snap to final destination and to each other)"),nt=n("br",null,null,-1),ot=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),lt={class:"area-buttons"};Oe.render=function(e,o,i,d,u,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=c((()=>{}),["stop"]))},[n("div",Ue,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title||e.image.width||e.image.height?(s(),t("div",Re,[e.image.title?(s(),t("span",Ge,'"'+r(e.image.title)+'"',1)):l("",!0),e.image.width||e.image.height?(s(),t("span",$e,"("+r(e.image.width)+" ✕ "+r(e.image.height)+")",1)):l("",!0)])):l("",!0)]),n("div",Le,[n("table",null,[n("tr",null,[Fe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[je,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[w,e.scoreMode]]),We]),Ke,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[w,e.scoreMode]]),He])])]),n("tr",null,[Ye,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[w,e.shapeMode]]),qe]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[w,e.shapeMode]]),Ze]),Xe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[w,e.shapeMode]]),Je])])]),n("tr",null,[et,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[w,e.snapMode]]),tt]),nt,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[w,e.snapMode]]),ot])])])])]),n("div",lt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};const at=async(e,t,n)=>new Promise(((o,l)=>{const a=new window.XMLHttpRequest;a.open(e,t,!0),a.withCredentials=!0;for(const e in n.headers||{})a.setRequestHeader(e,n.headers[e]);a.addEventListener("load",(function(e){o({status:this.status,text:this.responseText,json:async()=>JSON.parse(this.responseText)})})),a.addEventListener("error",(function(e){l(new Error("xhr error"))})),a.upload&&n.onUploadProgress&&a.upload.addEventListener("progress",(function(e){n.onUploadProgress&&n.onUploadProgress(e)})),a.send(n.body)}));var st=(e,t)=>at("post",e,t),it=e({components:{ImageLibrary:Y,NewImageDialog:ae,EditImageDialog:be,NewGameDialog:Oe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:"",uploading:"",uploadProgress:0}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${X.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){this.uploadProgress=0;const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await st("/api/upload",{body:t,onUploadProgress:e=>{this.uploadProgress=e.loaded/e.total}});return this.uploadProgress=1,await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){this.uploading="postToGallery",await this.uploadImage(e),this.uploading="",this.dialog="",await this.loadImages()},async setupGameClick(e){this.uploading="setupGame";const t=await this.uploadImage(e);this.uploading="",this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const rt={class:"upload-image-teaser"},dt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),ut={key:0},ct=i(" Tags: "),pt=i(" Sort by: "),gt=n("option",{value:"date_desc"},"Newest first",-1),ht=n("option",{value:"date_asc"},"Oldest first",-1),mt=n("option",{value:"alpha_asc"},"A-Z",-1),yt=n("option",{value:"alpha_desc"},"Z-A",-1);it.render=function(e,o,i,c,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),w=a("new-game-dialog");return s(),t("div",null,[n("div",rt,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),dt]),n("div",null,[e.tags.length>0?(s(),t("label",ut,[ct,(s(!0),t(d,null,u(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[pt,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[gt,ht,mt,yt],544),[[v,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),uploadProgress:e.uploadProgress,uploading:e.uploading,onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","uploadProgress","uploading","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(w,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ft=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 wt={class:"scores"},vt=n("div",null,"Scores",-1),bt=n("td",null,"⚡",-1),Ct=n("td",null,"💤",-1);ft.render=function(e,o,l,a,i,c){return s(),t("div",wt,[vt,n("table",null,[(s(!0),t(d,null,u(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,u(e.idles,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[Ct,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var xt=e({name:"puzzle-status",props:{finished:{type:Boolean,required:!0},duration:{type:Number,required:!0},piecesDone:{type:Number,required:!0},piecesTotal:{type:Number,required:!0}},computed:{icon(){return this.finished?"🏁":"⏳"},durationStr(){return U(this.duration)}}});const kt={class:"timer"};xt.render=function(e,o,l,a,i,d){return s(),t("div",kt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var Pt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const At=m();y("data-v-4d56fc17");const St=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),It=n("td",null,[n("label",null,"Sounds: ")],-1),Dt=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Et={class:"sound-volume"},Mt=n("td",null,[n("label",null,"Show player names: ")],-1);f();const Nt=At(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[10]||(o[10]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[9]||(o[9]=c((()=>{}),["stop"]))},[n("tr",null,[St,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[zt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[Tt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[It,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[C,e.modelValue.soundsEnabled]])])]),n("tr",null,[Dt,n("td",Et,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])]),n("tr",null,[Mt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[8]||(o[8]=t=>e.modelValue.showPlayerNames=t)},null,512),[[C,e.modelValue.showPlayerNames]])])])])]))));Pt.render=Nt,Pt.__scopeId="data-v-4d56fc17";var _t=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const Vt={class:"preview"};_t.render=function(e,o,l,a,i,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",Vt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Ot=1,Ut=4,Bt=2,Rt=3,Gt=2,$t=4,Lt=3,Ft=9,jt=1,Wt=2,Kt=3,Ht=4,Yt=5,qt=6,Qt=7,Zt=8,Xt=10,Jt=11,en=12,tn=13,nn=14,on=15,ln=16,an=1,sn=2,rn=3;const dn=Z("Communication.js");let un,cn=[],pn=e=>{cn.push(e)},gn=[],hn=e=>{gn.push(e)};let mn=0;const yn=e=>{mn!==e&&(mn=e,hn(e))};function fn(e){if(2===mn)try{un.send(JSON.stringify(e))}catch(t){dn.info("unable to send message.. maybe because ws is invalid?")}}let wn,vn;var bn={connect:function(e,t,n){return wn=0,vn={},yn(3),new Promise((o=>{un=new WebSocket(e,n+"|"+t),un.onopen=()=>{yn(2),fn([Rt])},un.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Ut){const e=t[1];o(e)}else{if(l!==Ot)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&vn[o])return void delete vn[o];pn(t)}}},un.onerror=()=>{throw yn(1),"[ 2021-05-15 onerror ]"},un.onclose=e=>{4e3===e.code||1001===e.code?yn(4):yn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${X.asQueryArgs(n)}`);return await o.json()},disconnect:function(){un&&un.close(4e3),wn=0,vn={}},sendClientEvent:function(e){wn++,vn[wn]=e,fn([Bt,wn,vn[wn]])},onServerChange:function(e){pn=e;for(const t of cn)pn(t);cn=[]},onConnectionStateChange:function(e){hn=e;for(const t of gn)hn(t);gn=[]},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},Cn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===bn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===bn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const xn={key:0,class:"overlay connection-lost"},kn={key:0,class:"overlay-content"},Pn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),An={key:1,class:"overlay-content"},Sn=n("div",null,"Connecting...",-1);Cn.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",xn,[e.lostConnection?(s(),t("div",kn,[Pn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",An,[Sn])):l("",!0)])):l("",!0)};var zn=e({name:"help-overlay",emits:{bgclick:null}});const Tn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),In=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),Dn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),En=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),Mn=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Nn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),_n=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),Vn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),On=n("tr",null,[n("td",null,"🎯 Center puzzle in screen:"),n("td",null,[n("div",null,[n("kbd",null,"C")])])],-1),Un=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Bn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Rn=n("tr",null,[n("td",null,"👤 Toggle player names:"),n("td",null,[n("div",null,[n("kbd",null,"N")])])],-1),Gn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),$n=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),Ln=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),Fn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);zn.render=function(e,o,l,a,i,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]=c((()=>{}),["stop"]))},[Tn,In,Dn,En,Mn,Nn,_n,Vn,On,Un,Bn,Rn,Gn,$n,Ln,Fn])])};var jn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Wn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Kn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Hn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Yn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function qn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Qn={createCanvas:qn,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=qn(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=qn(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(),o}};const Zn=Z("Debug.js");let Xn=0,Jn=0;var eo=e=>{Xn=performance.now(),Jn=e},to=e=>{const t=performance.now(),n=t-Xn;n>Jn&&Zn.log(e+": "+n),Xn=t};function no(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function oo(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var lo={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:no,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:oo,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 no(oo(e),oo(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const ao=Z("PuzzleGraphics.js");function so(e,t){const n=X.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var io={loadPuzzleBitmaps:async function(e){const t=await Qn.loadImageToBitmap(e.info.imageUrl),n=await Qn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){ao.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function u(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=lo.pointAdd(a,{x:o,y:0}),u=lo.pointAdd(r,{x:0,y:o}),c=lo.pointSub(u,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;oX.decodePiece(ro[e].puzzle.tiles[t]),Po=(e,t)=>ko(e,t).group,Ao=(e,t)=>{const n=ro[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},So=(e,t)=>{const n=ro[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=ro[e].puzzle.info,o=X.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return lo.pointAdd(o,l)},zo=(e,t)=>ko(e,t).pos,To=e=>{const t=Wo(e),n=Ko(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}},Io=(e,t)=>{const n=No(e),o=ko(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Do=(e,t)=>ko(e,t).z,Eo=(e,t)=>{for(const n of ro[e].puzzle.tiles){const e=X.decodePiece(n);if(e.owner===t)return e.idx}return-1},Mo=e=>ro[e].puzzle.info.tileDrawSize,No=e=>ro[e].puzzle.info.tileSize,_o=e=>ro[e].puzzle.data.maxGroup,Vo=e=>ro[e].puzzle.data.maxZ;function Oo(e,t){const n=ro[e].puzzle.info,o=X.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Uo=(e,t,n)=>{for(const o of t)xo(e,o,{z:n})},Bo=(e,t,n)=>{const o=zo(e,t);xo(e,t,{pos:lo.pointAdd(o,n)})},Ro=(e,t,n)=>{const o=Mo(e),l=To(e),a=n;for(const s of t){const t=ko(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)Bo(e,s,a)},Go=(e,t)=>ko(e,t).owner,$o=(e,t)=>{for(const n of t)xo(e,n,{owner:-1,z:1})},Lo=(e,t,n)=>{for(const o of t)xo(e,o,{owner:n})};function Fo(e,t){const n=ro[e].puzzle.tiles,o=X.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=X.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const jo=(e,t)=>{const n=co(e,t);return n?n.points:0},Wo=e=>ro[e].puzzle.info.table.width,Ko=e=>ro[e].puzzle.info.table.height;var Ho={setGame:function(e,t){ro[e]=t},exists:function(e){return!!ro[e]||!1},playerExists:go,getActivePlayers:function(e,t){const n=t-30*_;return ho(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return ho(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){go(e,t)?bo(e,t,{ts:n}):po(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:vo,getPieceCount:mo,getImageUrl:function(e){return ro[e].puzzle.info.imageUrl},setImageUrl:function(e,t){ro[e].puzzle.info.imageUrl=t},get:function(e){return ro[e]||null},getAllGames:function(){return Object.values(ro).sort(((e,t)=>wo(e.id)===wo(t.id)?t.puzzle.data.started-e.puzzle.data.started:wo(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=co(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=co(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=co(e,t);return n?n.name:null},getPlayerIndexById:uo,getPlayerIdByIndex:function(e,t){return ro[e].players.length>t?X.decodePlayer(ro[e].players[t]).id:null},changePlayer:bo,setPlayer:po,setPiece:function(e,t,n){ro[e].puzzle.tiles[t]=X.encodePiece(n)},setPuzzleData:function(e,t){ro[e].puzzle.data=t},getTableWidth:Wo,getTableHeight:Ko,getPuzzle:e=>ro[e].puzzle,getRng:e=>ro[e].rng.obj,getPuzzleWidth:e=>ro[e].puzzle.info.width,getPuzzleHeight:e=>ro[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return ro[e].puzzle.tiles.map(X.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Eo(e,t);return n<0?null:ro[e].puzzle.tiles[n]},getPieceDrawOffset:e=>ro[e].puzzle.info.tileDrawOffset,getPieceDrawSize:Mo,getFinalPiecePos:So,getStartTs:e=>ro[e].puzzle.data.started,getFinishTs:e=>ro[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=ro[e].puzzle,s=function(e,t){return t in ro[e].evtInfos?ro[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([an,a.data])},d=t=>{i.push([sn,X.encodePiece(ko(e,t))])},u=e=>{for(const t of e)d(t)},c=()=>{const n=co(e,t);n&&i.push([rn,X.encodePlayer(n)])},p=n[0];if(p===qt){const l=n[1];bo(e,t,{bgcolor:l,ts:o}),c()}else if(p===Qt){const l=n[1];bo(e,t,{color:l,ts:o}),c()}else if(p===Zt){const l=`${n[1]}`.substr(0,16);bo(e,t,{name:l,ts:o}),c()}else if(p===Ft){const l=n[1],a=n[2],s=co(e,t);if(s){const n=s.x-l,i=s.y-a;bo(e,t,{ts:o,x:n,y:i}),c()}}else if(p===jt){const l={x:n[1],y:n[2]};bo(e,t,{d:1,ts:o}),c(),s._last_mouse_down=l;const a=((e,t)=>{const n=ro[e].puzzle.info,o=ro[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=Vo(e)+1;Co(e,{maxZ:n}),r();const o=Fo(e,a);Uo(e,o,Vo(e)),Lo(e,o,t),u(o)}s._last_mouse=l}else if(p===Kt){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)bo(e,t,{x:l,y:a,ts:o}),c();else{const n=Eo(e,t);if(n>=0){bo(e,t,{x:l,y:a,ts:o}),c();const r=Fo(e,n);let d=lo.pointInBounds(i,To(e))&&lo.pointInBounds(s._last_mouse_down,To(e));for(const t of r){const n=Io(e,t);if(lo.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Ro(e,r,{x:t,y:n}),u(r)}}else bo(e,t,{ts:o}),c();s._last_mouse_down=i}s._last_mouse=i}else if(p===Wt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Eo(e,t);if(g>=0){const n=Fo(e,g);Lo(e,n,0),u(n);const s=zo(e,g),i=So(e,g);let h=!1;if(fo(e)===_e.REAL){for(const t of n)if(Ao(e,t)){h=!0;break}}else h=!0;if(h&&lo.pointDistance(i,s){const l=ro[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=Po(e,t),l=Po(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=zo(e,t),s=lo.pointAdd(zo(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(lo.pointDistance(a,s){const o=ro[e].puzzle.tiles,l=Po(e,t),a=Po(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(Co(e,{maxGroup:_o(e)+1}),r(),s=_o(e));if(xo(e,t,{group:s}),d(t),xo(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=X.decodePiece(r);i.includes(t.group)&&(xo(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=Fo(e,t),((e,t)=>-1===Go(e,t))(e,n))$o(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=Do(e,o);t>n&&(n=t)}return n})(e,l);Uo(e,l,t)}return u(l),!0}return!1};let a=!1;for(const t of Fo(e,g)){const o=Oo(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])){a=!0;break}}if(a&&yo(e)===De.ANY){const n=jo(e,t)+1;bo(e,t,{d:p,ts:o,points:n}),c()}else bo(e,t,{d:p,ts:o}),c();a&&fo(e)===_e.REAL&&vo(e)===mo(e)&&(Co(e,{finished:o}),r()),a&&l&&l(t)}}else bo(e,t,{d:p,ts:o}),c();s._last_mouse=i}else if(p===Ht){const l=n[1],a=n[2];bo(e,t,{x:l,y:a,ts:o}),c(),s._last_mouse={x:l,y:a}}else if(p===Yt){const l=n[1],a=n[2];bo(e,t,{x:l,y:a,ts:o}),c(),s._last_mouse={x:l,y:a}}else bo(e,t,{ts:o}),c();return function(e,t,n){ro[e].evtInfos[t]=n}(e,t,s),i}};let Yo=-10,qo=20,Qo=2,Zo=15;class Xo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Yo+Math.random()*qo,this.vy=-1*(Qo+Math.random()*Zo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Qo=t/2,Zo=t-Qo;const n=1/4*this.canvas.width/(t/2);Yo=-n,qo=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Xo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Xo(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{localStorage.setItem(e,t)},rl=e=>localStorage.getItem(e);var dl=(e,t)=>{il(e,`${t}`)},ul=(e,t)=>{const n=rl(e);if(null===n)return t;const o=parseInt(n,10);return isNaN(o)?t:o},cl=(e,t)=>{il(e,t?"1":"0")},pl=(e,t)=>{const n=rl(e);return null===n?t:"1"===n},gl=(e,t)=>{il(e,t)},hl=(e,t)=>{const n=rl(e);return null===n?t:n};const ml={"./grab.png":Wn,"./grab_mask.png":Kn,"./hand.png":Hn,"./hand_mask.png":Yn},yl={"./click.mp3":jn};let fl=!0,wl=!0;let vl=!0;async function bl(e,t,n,o,l,a){void 0===window.DEBUG&&(window.DEBUG=!1);const s=yl["./click.mp3"].default,i=new Audio(s),r=await Qn.loadImageToBitmap(ml["./grab.png"].default),d=await Qn.loadImageToBitmap(ml["./hand.png"].default),u=await Qn.loadImageToBitmap(ml["./grab_mask.png"].default),c=await Qn.loadImageToBitmap(ml["./hand_mask.png"].default),p=r.width,g=Math.round(p/2),h=r.height,m=Math.round(h/2),y={},f=async e=>{const t=e.color+" "+e.d;if(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?u:c;y[t]=await createImageBitmap(Qn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},w=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,vl=!0})),t}(l,Qn.createCanvas()),v={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};bn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=v.dataOffset;v.dataOffset+=1e4;const n=await bn.requestReplayData(e,t);return v.log=v.log.slice(v.logPointer),v.logPointer=0,v.log.push(...n.log),0===n.log.length&&(v.final=!0),n};let C=()=>0;const x=async()=>{if("play"===o){const o=await bn.connect(n,e,t),l=X.decodeGame(o);Ho.setGame(l.id,l),C=()=>V()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=X.decodeGame(t.game);Ho.setGame(n.id,n),v.lastRealTs=V(),v.gameStartTs=parseInt(t.log[0][4],10),v.lastGameTs=v.gameStartTs,C=()=>v.lastGameTs}}vl=!0};await x();const k=Ho.getPieceDrawOffset(e),P=Ho.getPieceDrawSize(e),A=Ho.getPuzzleWidth(e),S=Ho.getPuzzleHeight(e),z=Ho.getTableWidth(e),T=Ho.getTableHeight(e),I={x:(z-A)/2,y:(T-S)/2},D={w:A,h:S},E={w:P,h:P},M=await io.loadPuzzleBitmaps(Ho.getPuzzle(e)),_=new el(w,Ho.getRng(e));_.init();const O=w.getContext("2d");w.classList.add("loaded"),a.setPuzzleCut();const U=function(){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)},a=(e,t)=>{if(n==e)return!1;const l=1-n/e;return o(-t.x*l,-t.y*l),n=e,!0},s=o=>({x:o.x/n-e,y:o.y/n-t}),i=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),r=e=>({w:e.w*n,h:e.h*n}),d=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,reset:()=>{e=0,t=0,n=1},move:o,canZoom:e=>n!=l(e),zoom:(e,t)=>a(l(e),t),setZoom:a,worldToViewport:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:r,viewportToWorld:e=>{const{x:t,y:n}=s(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:s,viewportDimToWorld:e=>{const{w:t,h:n}=d(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:d}}(),B=()=>{U.reset(),U.move(-(z-w.width)/2,-(T-w.height)/2);const e=U.worldDimToViewport(D),t=w.width-40,n=w.height-40;if(e.w>t||e.h>n||e.w{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?c=e:"KeyE"===t.code&&(u=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&w([jt,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&w([Wt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),w([Kt,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Ht:Yt;w([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&w([Xt]),"replay"===o&&("KeyI"===e.code&&w([tn]),"KeyO"===e.code&&w([nn]),"KeyP"===e.code&&w([en])),"KeyF"===e.code&&(fl=!fl,vl=!0),"KeyG"===e.code&&(wl=!wl,vl=!0),"KeyM"===e.code&&w([Jt]),"KeyN"===e.code&&w([on]),"KeyC"===e.code&&w([ln]))}));const w=e=>{l.push(e)};return{addEvent:w,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});w([Ft,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(u&&c);else if(u){if(n.canZoom("in")){const e=f||m();w([Ht,...e])}}else if(c&&n.canZoom("out")){const e=f||m();w([Yt,...e])}},setHotkeys:e=>{a=e}}}(w,window,U,o),G=Ho.getImageUrl(e),$=()=>{const t=Ho.getStartTs(e),n=Ho.getFinishTs(e),o=C();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(Ho.getFinishedPiecesCount(e)),a.setPiecesTotal(Ho.getPieceCount(e));const L=C();a.setActivePlayers(Ho.getActivePlayers(e,L)),a.setIdlePlayers(Ho.getIdlePlayers(e,L));const F=!!Ho.getFinishTs(e);let j=F;const W=()=>j&&!F,K=()=>ul(tl,100),H=()=>pl(nl,!1),Y=()=>pl(sl,!0),q=()=>{const e=K();i.volume=e/100,i.play()},Q=()=>"replay"===o?hl(ol,"#222222"):Ho.getPlayerBgColor(e,t)||hl(ol,"#222222"),Z=()=>"replay"===o?hl(ll,"#ffffff"):Ho.getPlayerColor(e,t)||hl(ll,"#ffffff");let J="",ee="",te=!1;const ne=e=>{te=e;const[t,n]=e?[J,"grab"]:[ee,"default"];w.style.cursor=`url('${t}') ${g} ${m}, ${n}`},oe=e=>{J=Qn.colorizedCanvas(r,u,e).toDataURL(),ee=Qn.colorizedCanvas(d,c,e).toDataURL(),ne(te)};oe(Z());const le=()=>{a.setReplaySpeed&&a.setReplaySpeed(v.speeds[v.speedIdx]),a.setReplayPaused&&a.setReplayPaused(v.paused)},ae=()=>{v.speedIdx+1{v.speedIdx>=1&&(v.speedIdx--,le())},ie=()=>{v.paused=!v.paused,le()},re=[];let de;let ue;if("play"===o?re.push(setInterval((()=>{$()}),1e3)):"replay"===o&&le(),"play"===o)bn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case rn:{const n=X.decodePlayer(a);n.id!==t&&(Ho.setPlayer(e,n.id,n),vl=!0)}break;case sn:{const t=X.decodePiece(a);Ho.setPiece(e,t.idx,t),vl=!0}break;case an:Ho.setPuzzleData(e,a),vl=!0}j=!!Ho.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===Gt){const t=o[1];return Ho.addPlayer(e,t,n),!0}if(o[0]===$t){const t=Ho.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Ho.addPlayer(e,t,n),!0}if(o[0]===Lt){const t=Ho.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Ho.handleInput(e,t,l,n),!0}return!1};let n=v.lastGameTs;const o=async()=>{v.logPointer+1>=v.log.length&&await b(e);const l=V();if(v.paused)return v.lastRealTs=l,void(de=setTimeout(o,50));const a=(l-v.lastRealTs)*v.speeds[v.speedIdx];let s=v.lastGameTs+a;for(;;){if(v.paused)break;const e=v.logPointer+1;if(e>=v.log.length)break;const o=v.log[v.logPointer],l=n+o[o.length-1],a=v.log[e],i=a[a.length-1],r=l+i;if(r>s){s+500*N{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,u=0,c=window.performance.now();const p=()=>{for(d=window.performance.now(),u+=Math.min(1,(d-c)/1e3);u>r;)u-=r,l(i);a(u/o),c=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{R.createKeyEvents();for(const n of R.consumeAll())if("play"===o){const o=n[0];if(o===Ft){const e=n[1],t=n[2],o=U.worldDimToViewport({w:e,h:t});vl=!0,U.move(o.w,o.h)}else if(o===Kt){if(ce&&!Ho.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-ce.x),l=Math.round(t.y-ce.y);vl=!0,U.move(o,l),ce=t}}else if(o===Qt)oe(n[1]);else if(o===jt){const e={x:n[1],y:n[2]};ce=U.worldToViewport(e),ne(!0)}else if(o===Wt)ce=null,ne(!1);else if(o===Ht){const e={x:n[1],y:n[2]};vl=!0,U.zoom("in",U.worldToViewport(e))}else if(o===Yt){const e={x:n[1],y:n[2]};vl=!0,U.zoom("out",U.worldToViewport(e))}else o===Xt?a.togglePreview():o===Jt?a.toggleSoundsEnabled():o===on?a.togglePlayerNames():o===ln&&B();const l=C();Ho.handleInput(e,t,n,l,(e=>{H()&&q()})).length>0&&(vl=!0),bn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===en)ie();else if(e===nn)se();else if(e===tn)ae();else if(e===Ft){const e=n[1],t=n[2];vl=!0,U.move(e,t)}else if(e===Kt){if(ce){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-ce.x),l=Math.round(t.y-ce.y);vl=!0,U.move(o,l),ce=t}}else if(e===Qt)oe(n[1]);else if(e===jt){const e={x:n[1],y:n[2]};ce=U.worldToViewport(e),ne(!0)}else if(e===Wt)ce=null,ne(!1);else if(e===Ht){const e={x:n[1],y:n[2]};vl=!0,U.zoom("in",U.worldToViewport(e))}else if(e===Yt){const e={x:n[1],y:n[2]};vl=!0,U.zoom("out",U.worldToViewport(e))}else e===Xt?a.togglePreview():e===Jt?a.toggleSoundsEnabled():e===on?a.togglePlayerNames():e===ln&&B()}j=!!Ho.getFinishTs(e),W()&&(_.update(),vl=!0)},render:async()=>{if(!vl)return;const n=C();let l,s,i;window.DEBUG&&eo(0),O.fillStyle=Q(),O.fillRect(0,0,w.width,w.height),window.DEBUG&&to("clear done"),l=U.worldToViewportRaw(I),s=U.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&to("board done");const r=Ho.getPiecesSortedByZIndex(e);window.DEBUG&&to("get tiles done"),s=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?fl:wl)&&(i=M[e.idx],l=U.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&to("tiles done");const d=[];for(const a of Ho.getActivePlayers(e,n))u=a,("replay"===o||u.id!==t)&&(i=await f(a),l=U.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),Y()&&d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var u;O.fillStyle="white",O.textAlign="center";for(const[e,t,o]of d)O.fillText(e,t,o);window.DEBUG&&to("players done"),a.setActivePlayers(Ho.getActivePlayers(e,n)),a.setIdlePlayers(Ho.getIdlePlayers(e,n)),a.setPiecesDone(Ho.getFinishedPiecesCount(e)),window.DEBUG&&to("HUD done"),W()&&_.render(),vl=!1}}),{setHotkeys:e=>{R.setHotkeys(e)},onBgChange:e=>{gl(ol,e),R.addEvent([qt,e])},onColorChange:e=>{gl(ll,e),R.addEvent([Qt,e])},onNameChange:e=>{gl(al,e),R.addEvent([Zt,e])},onSoundsEnabledChange:e=>{cl(nl,e)},onSoundsVolumeChange:e=>{dl(tl,e),q()},onShowPlayerNamesChange:e=>{cl(sl,e)},replayOnSpeedUp:ae,replayOnSpeedDown:se,replayOnPauseToggle:ie,previewImageUrl:G,player:{background:Q(),color:Z(),name:"replay"===o?hl(al,"anon"):Ho.getPlayerName(e,t)||hl(al,"anon"),soundsEnabled:H(),soundsVolume:K(),showPlayerNames:Y()},disconnect:bn.disconnect,connect:x,unload:()=>{re.forEach((e=>{clearInterval(e)})),de&&clearTimeout(de),ue&&ue.stop()}}}var Cl=e({name:"game",components:{PuzzleStatus:xt,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,ConnectionOverlay:Cn,HelpOverlay:zn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,cuttingPuzzle:!0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await bl(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setPuzzleCut:()=>{this.cuttingPuzzle=!1},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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 xl={id:"game"},kl={key:0,class:"overlay"},Pl=n("div",{class:"overlay-content"},[n("div",null,"⏳ Cutting puzzle, please wait... ⏳")],-1),Al={class:"menu"},Sl={class:"tabs"},zl=i("🧩 Puzzles");Cl.render=function(e,i,r,d,u,c){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("connection-overlay"),f=a("puzzle-status"),w=a("router-link"),v=a("scores");return s(),t("div",xl,[p(n(g,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(m,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),e.cuttingPuzzle?(s(),t("div",kl,[Pl])):l("",!0),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",Al,[n("div",Sl,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[zl])),_:1}),n("div",{class:"opener",onClick:i[5]||(i[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("help",!0))},"ℹ️ Hotkeys")])]),n(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var Tl=e({name:"replay",components:{PuzzleStatus:xt,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,HelpOverlay:zn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,cuttingPuzzle:!0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await bl(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"replay",this.$el,{setPuzzleCut:()=>{this.cuttingPuzzle=!1},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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 Il={id:"replay"},Dl={key:0,class:"overlay"},El=n("div",{class:"overlay-content"},[n("div",null,"⏳ Cutting puzzle, please wait... ⏳")],-1),Ml={class:"menu"},Nl={class:"tabs"},_l=i("🧩 Puzzles");Tl.render=function(e,i,d,u,c,g){const h=a("settings-overlay"),m=a("preview-overlay"),y=a("help-overlay"),f=a("puzzle-status"),w=a("router-link"),v=a("scores");return s(),t("div",Il,[p(n(h,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(m,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(y,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),e.cuttingPuzzle?(s(),t("div",Dl,[El])):l("",!0),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:i[5]||(i[5]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",Ml,[n("div",Nl,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[_l])),_:1}),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("help",!0))},"ℹ️ Hotkeys")])]),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=k({history:P(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:it},{name:"game",path:"/g/:id",component:Cl},{name:"replay",path:"/replay/:id",component:Tl}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=A(S);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=X.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 24f5542..dadb3e4 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,7 +4,7 @@ 🧩 jigsaw.hyottoko.club - + diff --git a/src/frontend/game.ts b/src/frontend/game.ts index 94c4ad0..9f66e8d 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -46,6 +46,7 @@ let PIECE_VIEW_FIXED = true let PIECE_VIEW_LOOSE = true interface Hud { + setPuzzleCut: () => void setActivePlayers: (v: Array) => void setIdlePlayers: (v: Array) => void setFinished: (v: boolean) => void @@ -415,6 +416,7 @@ export async function main( const ctx = canvas.getContext('2d') as CanvasRenderingContext2D canvas.classList.add('loaded') + HUD.setPuzzleCut() // initialize some view data // this global data will change according to input events diff --git a/src/frontend/views/Game.vue b/src/frontend/views/Game.vue index 17fea3c..d1789f0 100644 --- a/src/frontend/views/Game.vue +++ b/src/frontend/views/Game.vue @@ -4,6 +4,12 @@ +
+
+
⏳ Cutting puzzle, please wait... ⏳
+
+
+ { this.cuttingPuzzle = false }, setActivePlayers: (v: Array) => { this.activePlayers = v }, setIdlePlayers: (v: Array) => { this.idlePlayers = v }, setFinished: (v: boolean) => { this.finished = v }, diff --git a/src/frontend/views/Replay.vue b/src/frontend/views/Replay.vue index b86874c..c8f64ae 100644 --- a/src/frontend/views/Replay.vue +++ b/src/frontend/views/Replay.vue @@ -4,6 +4,12 @@ +
+
+
⏳ Cutting puzzle, please wait... ⏳
+
+
+ { this.cuttingPuzzle = false }, setActivePlayers: (v: Array) => { this.activePlayers = v }, setIdlePlayers: (v: Array) => { this.idlePlayers = v }, setFinished: (v: boolean) => { this.finished = v }, From 2fb0e959ae42b70c33d6d64e295f3a5d169dfd96 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Fri, 9 Jul 2021 01:17:26 +0200 Subject: [PATCH 29/45] add info layer that shows info about current puzzle --- src/common/Types.ts | 3 +- src/frontend/PuzzleGraphics.ts | 2 +- src/frontend/components/InfoOverlay.vue | 68 +++++++++++++++++++++++++ src/frontend/game.ts | 1 + src/frontend/views/Game.vue | 7 ++- src/frontend/views/Replay.vue | 7 ++- src/server/Puzzle.ts | 2 + 7 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 src/frontend/components/InfoOverlay.vue diff --git a/src/common/Types.ts b/src/common/Types.ts index 0e8d21d..261e210 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -154,8 +154,9 @@ export interface PieceChange { export interface PuzzleInfo { table: PuzzleTable - targetTiles: number, + targetTiles: number imageUrl: string + imageTitle: string width: number height: number diff --git a/src/frontend/PuzzleGraphics.ts b/src/frontend/PuzzleGraphics.ts index 6debdbe..6934cda 100644 --- a/src/frontend/PuzzleGraphics.ts +++ b/src/frontend/PuzzleGraphics.ts @@ -3,7 +3,7 @@ import Geometry, { Rect } from '../common/Geometry' import Graphics from './Graphics' import Util, { logger } from './../common/Util' -import { Puzzle, PuzzleInfo, PieceShape, EncodedPiece } from './../common/GameCommon' +import { Puzzle, PuzzleInfo, PieceShape, EncodedPiece } from './../common/Types' const log = logger('PuzzleGraphics.js') diff --git a/src/frontend/components/InfoOverlay.vue b/src/frontend/components/InfoOverlay.vue new file mode 100644 index 0000000..7f76d2d --- /dev/null +++ b/src/frontend/components/InfoOverlay.vue @@ -0,0 +1,68 @@ + + diff --git a/src/frontend/game.ts b/src/frontend/game.ts index 9f66e8d..ea457d1 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -969,6 +969,7 @@ export async function main( soundsVolume: playerSoundVolume(), showPlayerNames: showPlayerNames(), }, + game: Game.get(gameId), disconnect: Communication.disconnect, connect: connect, unload: unload, diff --git a/src/frontend/views/Game.vue b/src/frontend/views/Game.vue index d1789f0..e0d3394 100644 --- a/src/frontend/views/Game.vue +++ b/src/frontend/views/Game.vue @@ -2,6 +2,7 @@
+
@@ -27,7 +28,8 @@ 🧩 Puzzles
🖼️ Preview
🛠️ Settings
-
ℹ️ Hotkeys
+
ℹ️ Info
+
⌨️ Hotkeys
@@ -41,6 +43,7 @@ import Scores from './../components/Scores.vue' import PuzzleStatus from './../components/PuzzleStatus.vue' import SettingsOverlay from './../components/SettingsOverlay.vue' import PreviewOverlay from './../components/PreviewOverlay.vue' +import InfoOverlay from './../components/InfoOverlay.vue' import ConnectionOverlay from './../components/ConnectionOverlay.vue' import HelpOverlay from './../components/HelpOverlay.vue' @@ -54,6 +57,7 @@ export default defineComponent({ Scores, SettingsOverlay, PreviewOverlay, + InfoOverlay, ConnectionOverlay, HelpOverlay, }, @@ -81,6 +85,7 @@ export default defineComponent({ soundsVolume: 100, showPlayerNames: true, }, + game: null, previewImageUrl: '', setHotkeys: (v: boolean) => {}, onBgChange: (v: string) => {}, diff --git a/src/frontend/views/Replay.vue b/src/frontend/views/Replay.vue index c8f64ae..7239d2a 100644 --- a/src/frontend/views/Replay.vue +++ b/src/frontend/views/Replay.vue @@ -2,6 +2,7 @@
+
@@ -29,7 +30,8 @@ 🧩 Puzzles
🖼️ Preview
🛠️ Settings
-
ℹ️ Hotkeys
+
ℹ️ Info
+
⌨️ Hotkeys
@@ -43,6 +45,7 @@ import Scores from './../components/Scores.vue' import PuzzleStatus from './../components/PuzzleStatus.vue' import SettingsOverlay from './../components/SettingsOverlay.vue' import PreviewOverlay from './../components/PreviewOverlay.vue' +import InfoOverlay from './../components/InfoOverlay.vue' import HelpOverlay from './../components/HelpOverlay.vue' import { main, MODE_REPLAY } from './../game' @@ -55,6 +58,7 @@ export default defineComponent({ Scores, SettingsOverlay, PreviewOverlay, + InfoOverlay, HelpOverlay, }, data() { @@ -81,6 +85,7 @@ export default defineComponent({ soundsVolume: 100, showPlayerNames: true, }, + game: null, previewImageUrl: '', setHotkeys: (v: boolean) => {}, onBgChange: (v: string) => {}, diff --git a/src/server/Puzzle.ts b/src/server/Puzzle.ts index 4b65f2c..bd0ad98 100644 --- a/src/server/Puzzle.ts +++ b/src/server/Puzzle.ts @@ -7,6 +7,7 @@ import { Dim, Point } from '../common/Geometry' export interface PuzzleCreationImageInfo { file: string url: string + title: string } export interface PuzzleCreationInfo { @@ -140,6 +141,7 @@ async function createPuzzle( // information that was used to create the puzzle targetTiles: targetTiles, imageUrl, + imageTitle: image.title || '', width: info.width, // actual puzzle width (same as bitmap.width) height: info.height, // actual puzzle height (same as bitmap.height) From 0cb1cec2108c0f3fc67f7b4a50d588395a9ffbb8 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Fri, 9 Jul 2021 01:19:35 +0200 Subject: [PATCH 30/45] type hints --- src/frontend/views/Game.vue | 4 ++-- src/frontend/views/Replay.vue | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/frontend/views/Game.vue b/src/frontend/views/Game.vue index e0d3394..c228211 100644 --- a/src/frontend/views/Game.vue +++ b/src/frontend/views/Game.vue @@ -48,7 +48,7 @@ import ConnectionOverlay from './../components/ConnectionOverlay.vue' import HelpOverlay from './../components/HelpOverlay.vue' import { main, MODE_PLAY } from './../game' -import { Player } from '../../common/Types' +import { Game, Player } from '../../common/Types' export default defineComponent({ name: 'game', @@ -85,7 +85,7 @@ export default defineComponent({ soundsVolume: 100, showPlayerNames: true, }, - game: null, + game: null as Game|null, previewImageUrl: '', setHotkeys: (v: boolean) => {}, onBgChange: (v: string) => {}, diff --git a/src/frontend/views/Replay.vue b/src/frontend/views/Replay.vue index 7239d2a..1bf775a 100644 --- a/src/frontend/views/Replay.vue +++ b/src/frontend/views/Replay.vue @@ -85,7 +85,7 @@ export default defineComponent({ soundsVolume: 100, showPlayerNames: true, }, - game: null, + game: null as Game|null, previewImageUrl: '', setHotkeys: (v: boolean) => {}, onBgChange: (v: string) => {}, From 518092d269ab8ec698cd407dac5021162214ffb6 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sun, 11 Jul 2021 16:37:34 +0200 Subject: [PATCH 31/45] info overlay + script to update images in games and logs --- build/public/assets/index.19dfb063.js | 1 - build/public/assets/index.93936dee.js | 1 + build/public/index.html | 2 +- build/server/main.js | 26 +++---- scripts/fix_games_image_info.ts | 90 +++++++++++++++++++++++++ scripts/fix_image.ts | 23 ------- src/common/GameCommon.ts | 12 ++-- src/common/Types.ts | 18 ++++- src/frontend/components/InfoOverlay.vue | 8 +-- src/server/Game.ts | 8 +-- src/server/GameLog.ts | 2 + src/server/Images.ts | 29 +------- src/server/Puzzle.ts | 17 ++--- src/server/main.ts | 4 +- 14 files changed, 148 insertions(+), 93 deletions(-) delete mode 100644 build/public/assets/index.19dfb063.js create mode 100644 build/public/assets/index.93936dee.js create mode 100644 scripts/fix_games_image_info.ts delete mode 100644 scripts/fix_image.ts diff --git a/build/public/assets/index.19dfb063.js b/build/public/assets/index.19dfb063.js deleted file mode 100644 index 9a09aad..0000000 --- a/build/public/assets/index.19dfb063.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as u,g as c,h as p,v as g,i as h,j as m,p as y,k as f,l as w,m as v,n as b,q as C,s as x,u as k,x as P,y as A}from"./vendor.684f7bc8.js";var S=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const z={id:"app"},T={key:0,class:"nav"},I=i("Games overview"),D=i("New game");S.render=function(e,i,r,d,u,c){const p=a("router-link"),g=a("router-view");return s(),t("div",z,[e.showNav?(s(),t("ul",T,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[D])),_:1})])])):l("",!0),n(g)])};const E=864e5,M=e=>{const t=Math.floor(e/E);e%=E;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 N=1,_=1e3,V=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>M(t-e),U=M,B=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||V();return`${n} ${O(o,l)}`}}});const R={class:"game-info-text"},G=n("br",null,null,-1),$=n("br",null,null,-1),L=n("br",null,null,-1),F=i(" ↪️ Watch replay ");B.render=function(e,d,u,c,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),G,i(" 👥 "+r(e.game.players),1),$,i(" "+r(e.time(e.game.started,e.game.finished)),1),L])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[F])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:B},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 W=n("h1",null,"Running games",-1),K=n("h1",null,"Finished games",-1);j.render=function(e,o,l,i,r,c){const p=a("game-teaser");return s(),t("div",null,[W,(s(!0),t(d,null,u(e.gamesRunning,((e,o)=>(s(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128)),K,(s(!0),t(d,null,u(e.gamesFinished,((e,o)=>(s(),t("div",{class:"game-teaser-wrap",key:o},[n(p,{game:e},null,8,["game"])])))),128))])};var H=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});H.render=function(e,o,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=c(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y=e({name:"image-library",components:{ImageTeaser:H},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});Y.render=function(e,n,o,l,i,r){const c=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,u(e.images,((n,o)=>(s(),t(c,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])};class q{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new q(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const Q=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},Z=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=Q(o.getHours(),"00"),a=Q(o.getMinutes(),"00"),s=Q(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var X={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",q.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode,e.shapeMode,e.snapMode]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:q.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const J={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};J.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var ee=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const te=m();y("data-v-a4fa5e7e");const ne={key:0,class:"autocomplete"};f();const oe=te(((e,o,a,i,c,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ne,[n("ul",null,[(s(!0),t(d,null,u(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,u(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));ee.render=oe,ee.__scopeId="data-v-a4fa5e7e";const le=Z("NewImageDialog.vue");var ae=e({name:"new-image-dialog",components:{ResponsiveImage:J,TagsInput:ee},props:{autocompleteTags:{type:Function},uploadProgress:{type:Number},uploading:{type:String}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{uploadProgressPercent(){return this.uploadProgress?Math.round(100*this.uploadProgress):0},canPostToGallery(){return!this.uploading&&!(!this.previewUrl||!this.file)},canSetupGameClick(){return!this.uploading&&!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){le.info("onDragleave"),this.droppable=!1}}});const se=n("div",{class:"drop-target"},null,-1),ie={key:0,class:"has-image"},re={key:1},de={class:"upload"},ue=n("span",{class:"btn"},"Upload File",-1),ce={class:"area-settings"},pe=n("td",null,[n("label",null,"Title")],-1),ge=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),he=n("td",null,[n("label",null,"Tags")],-1),me={class:"area-buttons"},ye=i("🖼️ Post to gallery"),fe=i("🧩 Post to gallery "),we=n("br",null,null,-1),ve=i(" + set up game");ae.render=function(e,o,l,u,h,m){const y=a("responsive-image"),f=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=c((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[se,e.previewUrl?(s(),t("div",ie,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(y,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",re,[n("label",de,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ue])]))],34),n("div",ce,[n("table",null,[n("tr",null,[pe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),ge,n("tr",null,[he,n("td",null,[n(f,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",me,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},["postToGallery"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[ye],64))],8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},["setupGame"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[fe,we,ve],64))],8,["disabled"])])])])};var be=e({name:"edit-image-dialog",components:{ResponsiveImage:J,TagsInput:ee},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Ce={class:"area-image"},xe={class:"has-image"},ke={class:"area-settings"},Pe=n("td",null,[n("label",null,"Title")],-1),Ae=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Se=n("td",null,[n("label",null,"Tags")],-1),ze={class:"area-buttons"};var Te,Ie,De,Ee,Me,Ne,_e,Ve;be.render=function(e,o,l,i,r,d){const u=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=c((()=>{}),["stop"]))},[n("div",Ce,[n("div",xe,[n(u,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ke,[n("table",null,[n("tr",null,[Pe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ae,n("tr",null,[Se,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",ze,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])},(Ie=Te||(Te={}))[Ie.Flat=0]="Flat",Ie[Ie.Out=1]="Out",Ie[Ie.In=-1]="In",(Ee=De||(De={}))[Ee.FINAL=0]="FINAL",Ee[Ee.ANY=1]="ANY",(Ne=Me||(Me={}))[Ne.NORMAL=0]="NORMAL",Ne[Ne.ANY=1]="ANY",Ne[Ne.FLAT=2]="FLAT",(Ve=_e||(_e={}))[Ve.NORMAL=0]="NORMAL",Ve[Ve.REAL=1]="REAL";var Oe=e({name:"new-game-dialog",components:{ResponsiveImage:J},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:De.ANY,shapeMode:Me.NORMAL,snapMode:_e.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Ue={class:"area-image"},Be={class:"has-image"},Re={key:0,class:"image-title"},Ge={key:0,class:"image-title-title"},$e={key:1,class:"image-title-dim"},Le={class:"area-settings"},Fe=n("td",null,[n("label",null,"Pieces")],-1),je=n("td",null,[n("label",null,"Scoring: ")],-1),We=i(" Any (Score when pieces are connected to each other or on final location)"),Ke=n("br",null,null,-1),He=i(" Final (Score when pieces are put to their final location)"),Ye=n("td",null,[n("label",null,"Shapes: ")],-1),qe=i(" Normal"),Qe=n("br",null,null,-1),Ze=i(" Any (flat pieces can occur anywhere)"),Xe=n("br",null,null,-1),Je=i(" Flat (all pieces flat on all sides)"),et=n("td",null,[n("label",null,"Snapping: ")],-1),tt=i(" Normal (pieces snap to final destination and to each other)"),nt=n("br",null,null,-1),ot=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),lt={class:"area-buttons"};Oe.render=function(e,o,i,d,u,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=c((()=>{}),["stop"]))},[n("div",Ue,[n("div",Be,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title||e.image.width||e.image.height?(s(),t("div",Re,[e.image.title?(s(),t("span",Ge,'"'+r(e.image.title)+'"',1)):l("",!0),e.image.width||e.image.height?(s(),t("span",$e,"("+r(e.image.width)+" ✕ "+r(e.image.height)+")",1)):l("",!0)])):l("",!0)]),n("div",Le,[n("table",null,[n("tr",null,[Fe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[je,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[w,e.scoreMode]]),We]),Ke,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[w,e.scoreMode]]),He])])]),n("tr",null,[Ye,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[w,e.shapeMode]]),qe]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[w,e.shapeMode]]),Ze]),Xe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[w,e.shapeMode]]),Je])])]),n("tr",null,[et,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[w,e.snapMode]]),tt]),nt,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[w,e.snapMode]]),ot])])])])]),n("div",lt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};const at=async(e,t,n)=>new Promise(((o,l)=>{const a=new window.XMLHttpRequest;a.open(e,t,!0),a.withCredentials=!0;for(const e in n.headers||{})a.setRequestHeader(e,n.headers[e]);a.addEventListener("load",(function(e){o({status:this.status,text:this.responseText,json:async()=>JSON.parse(this.responseText)})})),a.addEventListener("error",(function(e){l(new Error("xhr error"))})),a.upload&&n.onUploadProgress&&a.upload.addEventListener("progress",(function(e){n.onUploadProgress&&n.onUploadProgress(e)})),a.send(n.body)}));var st=(e,t)=>at("post",e,t),it=e({components:{ImageLibrary:Y,NewImageDialog:ae,EditImageDialog:be,NewGameDialog:Oe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:"",uploading:"",uploadProgress:0}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${X.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){this.uploadProgress=0;const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await st("/api/upload",{body:t,onUploadProgress:e=>{this.uploadProgress=e.loaded/e.total}});return this.uploadProgress=1,await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){this.uploading="postToGallery",await this.uploadImage(e),this.uploading="",this.dialog="",await this.loadImages()},async setupGameClick(e){this.uploading="setupGame";const t=await this.uploadImage(e);this.uploading="",this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const rt={class:"upload-image-teaser"},dt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),ut={key:0},ct=i(" Tags: "),pt=i(" Sort by: "),gt=n("option",{value:"date_desc"},"Newest first",-1),ht=n("option",{value:"date_asc"},"Oldest first",-1),mt=n("option",{value:"alpha_asc"},"A-Z",-1),yt=n("option",{value:"alpha_desc"},"Z-A",-1);it.render=function(e,o,i,c,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),w=a("new-game-dialog");return s(),t("div",null,[n("div",rt,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),dt]),n("div",null,[e.tags.length>0?(s(),t("label",ut,[ct,(s(!0),t(d,null,u(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[pt,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[gt,ht,mt,yt],544),[[v,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),uploadProgress:e.uploadProgress,uploading:e.uploading,onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","uploadProgress","uploading","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(w,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ft=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 wt={class:"scores"},vt=n("div",null,"Scores",-1),bt=n("td",null,"⚡",-1),Ct=n("td",null,"💤",-1);ft.render=function(e,o,l,a,i,c){return s(),t("div",wt,[vt,n("table",null,[(s(!0),t(d,null,u(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,u(e.idles,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[Ct,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var xt=e({name:"puzzle-status",props:{finished:{type:Boolean,required:!0},duration:{type:Number,required:!0},piecesDone:{type:Number,required:!0},piecesTotal:{type:Number,required:!0}},computed:{icon(){return this.finished?"🏁":"⏳"},durationStr(){return U(this.duration)}}});const kt={class:"timer"};xt.render=function(e,o,l,a,i,d){return s(),t("div",kt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var Pt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const At=m();y("data-v-4d56fc17");const St=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),It=n("td",null,[n("label",null,"Sounds: ")],-1),Dt=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Et={class:"sound-volume"},Mt=n("td",null,[n("label",null,"Show player names: ")],-1);f();const Nt=At(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[10]||(o[10]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[9]||(o[9]=c((()=>{}),["stop"]))},[n("tr",null,[St,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[zt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[Tt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[It,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[C,e.modelValue.soundsEnabled]])])]),n("tr",null,[Dt,n("td",Et,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])]),n("tr",null,[Mt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[8]||(o[8]=t=>e.modelValue.showPlayerNames=t)},null,512),[[C,e.modelValue.showPlayerNames]])])])])]))));Pt.render=Nt,Pt.__scopeId="data-v-4d56fc17";var _t=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const Vt={class:"preview"};_t.render=function(e,o,l,a,i,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",Vt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Ot=1,Ut=4,Bt=2,Rt=3,Gt=2,$t=4,Lt=3,Ft=9,jt=1,Wt=2,Kt=3,Ht=4,Yt=5,qt=6,Qt=7,Zt=8,Xt=10,Jt=11,en=12,tn=13,nn=14,on=15,ln=16,an=1,sn=2,rn=3;const dn=Z("Communication.js");let un,cn=[],pn=e=>{cn.push(e)},gn=[],hn=e=>{gn.push(e)};let mn=0;const yn=e=>{mn!==e&&(mn=e,hn(e))};function fn(e){if(2===mn)try{un.send(JSON.stringify(e))}catch(t){dn.info("unable to send message.. maybe because ws is invalid?")}}let wn,vn;var bn={connect:function(e,t,n){return wn=0,vn={},yn(3),new Promise((o=>{un=new WebSocket(e,n+"|"+t),un.onopen=()=>{yn(2),fn([Rt])},un.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Ut){const e=t[1];o(e)}else{if(l!==Ot)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&vn[o])return void delete vn[o];pn(t)}}},un.onerror=()=>{throw yn(1),"[ 2021-05-15 onerror ]"},un.onclose=e=>{4e3===e.code||1001===e.code?yn(4):yn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${X.asQueryArgs(n)}`);return await o.json()},disconnect:function(){un&&un.close(4e3),wn=0,vn={}},sendClientEvent:function(e){wn++,vn[wn]=e,fn([Bt,wn,vn[wn]])},onServerChange:function(e){pn=e;for(const t of cn)pn(t);cn=[]},onConnectionStateChange:function(e){hn=e;for(const t of gn)hn(t);gn=[]},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},Cn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===bn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===bn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const xn={key:0,class:"overlay connection-lost"},kn={key:0,class:"overlay-content"},Pn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),An={key:1,class:"overlay-content"},Sn=n("div",null,"Connecting...",-1);Cn.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",xn,[e.lostConnection?(s(),t("div",kn,[Pn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",An,[Sn])):l("",!0)])):l("",!0)};var zn=e({name:"help-overlay",emits:{bgclick:null}});const Tn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),In=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),Dn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),En=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),Mn=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Nn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),_n=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),Vn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),On=n("tr",null,[n("td",null,"🎯 Center puzzle in screen:"),n("td",null,[n("div",null,[n("kbd",null,"C")])])],-1),Un=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Bn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Rn=n("tr",null,[n("td",null,"👤 Toggle player names:"),n("td",null,[n("div",null,[n("kbd",null,"N")])])],-1),Gn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),$n=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),Ln=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),Fn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);zn.render=function(e,o,l,a,i,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]=c((()=>{}),["stop"]))},[Tn,In,Dn,En,Mn,Nn,_n,Vn,On,Un,Bn,Rn,Gn,$n,Ln,Fn])])};var jn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Wn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Kn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Hn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Yn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function qn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Qn={createCanvas:qn,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=qn(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=qn(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(),o}};const Zn=Z("Debug.js");let Xn=0,Jn=0;var eo=e=>{Xn=performance.now(),Jn=e},to=e=>{const t=performance.now(),n=t-Xn;n>Jn&&Zn.log(e+": "+n),Xn=t};function no(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function oo(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var lo={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:no,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:oo,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 no(oo(e),oo(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const ao=Z("PuzzleGraphics.js");function so(e,t){const n=X.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var io={loadPuzzleBitmaps:async function(e){const t=await Qn.loadImageToBitmap(e.info.imageUrl),n=await Qn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){ao.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function u(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=lo.pointAdd(a,{x:o,y:0}),u=lo.pointAdd(r,{x:0,y:o}),c=lo.pointSub(u,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;oX.decodePiece(ro[e].puzzle.tiles[t]),Po=(e,t)=>ko(e,t).group,Ao=(e,t)=>{const n=ro[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},So=(e,t)=>{const n=ro[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=ro[e].puzzle.info,o=X.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return lo.pointAdd(o,l)},zo=(e,t)=>ko(e,t).pos,To=e=>{const t=Wo(e),n=Ko(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}},Io=(e,t)=>{const n=No(e),o=ko(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Do=(e,t)=>ko(e,t).z,Eo=(e,t)=>{for(const n of ro[e].puzzle.tiles){const e=X.decodePiece(n);if(e.owner===t)return e.idx}return-1},Mo=e=>ro[e].puzzle.info.tileDrawSize,No=e=>ro[e].puzzle.info.tileSize,_o=e=>ro[e].puzzle.data.maxGroup,Vo=e=>ro[e].puzzle.data.maxZ;function Oo(e,t){const n=ro[e].puzzle.info,o=X.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Uo=(e,t,n)=>{for(const o of t)xo(e,o,{z:n})},Bo=(e,t,n)=>{const o=zo(e,t);xo(e,t,{pos:lo.pointAdd(o,n)})},Ro=(e,t,n)=>{const o=Mo(e),l=To(e),a=n;for(const s of t){const t=ko(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)Bo(e,s,a)},Go=(e,t)=>ko(e,t).owner,$o=(e,t)=>{for(const n of t)xo(e,n,{owner:-1,z:1})},Lo=(e,t,n)=>{for(const o of t)xo(e,o,{owner:n})};function Fo(e,t){const n=ro[e].puzzle.tiles,o=X.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=X.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const jo=(e,t)=>{const n=co(e,t);return n?n.points:0},Wo=e=>ro[e].puzzle.info.table.width,Ko=e=>ro[e].puzzle.info.table.height;var Ho={setGame:function(e,t){ro[e]=t},exists:function(e){return!!ro[e]||!1},playerExists:go,getActivePlayers:function(e,t){const n=t-30*_;return ho(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return ho(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){go(e,t)?bo(e,t,{ts:n}):po(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:vo,getPieceCount:mo,getImageUrl:function(e){return ro[e].puzzle.info.imageUrl},setImageUrl:function(e,t){ro[e].puzzle.info.imageUrl=t},get:function(e){return ro[e]||null},getAllGames:function(){return Object.values(ro).sort(((e,t)=>wo(e.id)===wo(t.id)?t.puzzle.data.started-e.puzzle.data.started:wo(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=co(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=co(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=co(e,t);return n?n.name:null},getPlayerIndexById:uo,getPlayerIdByIndex:function(e,t){return ro[e].players.length>t?X.decodePlayer(ro[e].players[t]).id:null},changePlayer:bo,setPlayer:po,setPiece:function(e,t,n){ro[e].puzzle.tiles[t]=X.encodePiece(n)},setPuzzleData:function(e,t){ro[e].puzzle.data=t},getTableWidth:Wo,getTableHeight:Ko,getPuzzle:e=>ro[e].puzzle,getRng:e=>ro[e].rng.obj,getPuzzleWidth:e=>ro[e].puzzle.info.width,getPuzzleHeight:e=>ro[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return ro[e].puzzle.tiles.map(X.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Eo(e,t);return n<0?null:ro[e].puzzle.tiles[n]},getPieceDrawOffset:e=>ro[e].puzzle.info.tileDrawOffset,getPieceDrawSize:Mo,getFinalPiecePos:So,getStartTs:e=>ro[e].puzzle.data.started,getFinishTs:e=>ro[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=ro[e].puzzle,s=function(e,t){return t in ro[e].evtInfos?ro[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([an,a.data])},d=t=>{i.push([sn,X.encodePiece(ko(e,t))])},u=e=>{for(const t of e)d(t)},c=()=>{const n=co(e,t);n&&i.push([rn,X.encodePlayer(n)])},p=n[0];if(p===qt){const l=n[1];bo(e,t,{bgcolor:l,ts:o}),c()}else if(p===Qt){const l=n[1];bo(e,t,{color:l,ts:o}),c()}else if(p===Zt){const l=`${n[1]}`.substr(0,16);bo(e,t,{name:l,ts:o}),c()}else if(p===Ft){const l=n[1],a=n[2],s=co(e,t);if(s){const n=s.x-l,i=s.y-a;bo(e,t,{ts:o,x:n,y:i}),c()}}else if(p===jt){const l={x:n[1],y:n[2]};bo(e,t,{d:1,ts:o}),c(),s._last_mouse_down=l;const a=((e,t)=>{const n=ro[e].puzzle.info,o=ro[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=Vo(e)+1;Co(e,{maxZ:n}),r();const o=Fo(e,a);Uo(e,o,Vo(e)),Lo(e,o,t),u(o)}s._last_mouse=l}else if(p===Kt){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)bo(e,t,{x:l,y:a,ts:o}),c();else{const n=Eo(e,t);if(n>=0){bo(e,t,{x:l,y:a,ts:o}),c();const r=Fo(e,n);let d=lo.pointInBounds(i,To(e))&&lo.pointInBounds(s._last_mouse_down,To(e));for(const t of r){const n=Io(e,t);if(lo.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Ro(e,r,{x:t,y:n}),u(r)}}else bo(e,t,{ts:o}),c();s._last_mouse_down=i}s._last_mouse=i}else if(p===Wt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Eo(e,t);if(g>=0){const n=Fo(e,g);Lo(e,n,0),u(n);const s=zo(e,g),i=So(e,g);let h=!1;if(fo(e)===_e.REAL){for(const t of n)if(Ao(e,t)){h=!0;break}}else h=!0;if(h&&lo.pointDistance(i,s){const l=ro[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=Po(e,t),l=Po(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=zo(e,t),s=lo.pointAdd(zo(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(lo.pointDistance(a,s){const o=ro[e].puzzle.tiles,l=Po(e,t),a=Po(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(Co(e,{maxGroup:_o(e)+1}),r(),s=_o(e));if(xo(e,t,{group:s}),d(t),xo(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=X.decodePiece(r);i.includes(t.group)&&(xo(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=Fo(e,t),((e,t)=>-1===Go(e,t))(e,n))$o(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=Do(e,o);t>n&&(n=t)}return n})(e,l);Uo(e,l,t)}return u(l),!0}return!1};let a=!1;for(const t of Fo(e,g)){const o=Oo(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])){a=!0;break}}if(a&&yo(e)===De.ANY){const n=jo(e,t)+1;bo(e,t,{d:p,ts:o,points:n}),c()}else bo(e,t,{d:p,ts:o}),c();a&&fo(e)===_e.REAL&&vo(e)===mo(e)&&(Co(e,{finished:o}),r()),a&&l&&l(t)}}else bo(e,t,{d:p,ts:o}),c();s._last_mouse=i}else if(p===Ht){const l=n[1],a=n[2];bo(e,t,{x:l,y:a,ts:o}),c(),s._last_mouse={x:l,y:a}}else if(p===Yt){const l=n[1],a=n[2];bo(e,t,{x:l,y:a,ts:o}),c(),s._last_mouse={x:l,y:a}}else bo(e,t,{ts:o}),c();return function(e,t,n){ro[e].evtInfos[t]=n}(e,t,s),i}};let Yo=-10,qo=20,Qo=2,Zo=15;class Xo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Yo+Math.random()*qo,this.vy=-1*(Qo+Math.random()*Zo),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Qo=t/2,Zo=t-Qo;const n=1/4*this.canvas.width/(t/2);Yo=-n,qo=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Xo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Xo(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{localStorage.setItem(e,t)},rl=e=>localStorage.getItem(e);var dl=(e,t)=>{il(e,`${t}`)},ul=(e,t)=>{const n=rl(e);if(null===n)return t;const o=parseInt(n,10);return isNaN(o)?t:o},cl=(e,t)=>{il(e,t?"1":"0")},pl=(e,t)=>{const n=rl(e);return null===n?t:"1"===n},gl=(e,t)=>{il(e,t)},hl=(e,t)=>{const n=rl(e);return null===n?t:n};const ml={"./grab.png":Wn,"./grab_mask.png":Kn,"./hand.png":Hn,"./hand_mask.png":Yn},yl={"./click.mp3":jn};let fl=!0,wl=!0;let vl=!0;async function bl(e,t,n,o,l,a){void 0===window.DEBUG&&(window.DEBUG=!1);const s=yl["./click.mp3"].default,i=new Audio(s),r=await Qn.loadImageToBitmap(ml["./grab.png"].default),d=await Qn.loadImageToBitmap(ml["./hand.png"].default),u=await Qn.loadImageToBitmap(ml["./grab_mask.png"].default),c=await Qn.loadImageToBitmap(ml["./hand_mask.png"].default),p=r.width,g=Math.round(p/2),h=r.height,m=Math.round(h/2),y={},f=async e=>{const t=e.color+" "+e.d;if(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?u:c;y[t]=await createImageBitmap(Qn.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},w=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,vl=!0})),t}(l,Qn.createCanvas()),v={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};bn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=v.dataOffset;v.dataOffset+=1e4;const n=await bn.requestReplayData(e,t);return v.log=v.log.slice(v.logPointer),v.logPointer=0,v.log.push(...n.log),0===n.log.length&&(v.final=!0),n};let C=()=>0;const x=async()=>{if("play"===o){const o=await bn.connect(n,e,t),l=X.decodeGame(o);Ho.setGame(l.id,l),C=()=>V()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=X.decodeGame(t.game);Ho.setGame(n.id,n),v.lastRealTs=V(),v.gameStartTs=parseInt(t.log[0][4],10),v.lastGameTs=v.gameStartTs,C=()=>v.lastGameTs}}vl=!0};await x();const k=Ho.getPieceDrawOffset(e),P=Ho.getPieceDrawSize(e),A=Ho.getPuzzleWidth(e),S=Ho.getPuzzleHeight(e),z=Ho.getTableWidth(e),T=Ho.getTableHeight(e),I={x:(z-A)/2,y:(T-S)/2},D={w:A,h:S},E={w:P,h:P},M=await io.loadPuzzleBitmaps(Ho.getPuzzle(e)),_=new el(w,Ho.getRng(e));_.init();const O=w.getContext("2d");w.classList.add("loaded"),a.setPuzzleCut();const U=function(){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)},a=(e,t)=>{if(n==e)return!1;const l=1-n/e;return o(-t.x*l,-t.y*l),n=e,!0},s=o=>({x:o.x/n-e,y:o.y/n-t}),i=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),r=e=>({w:e.w*n,h:e.h*n}),d=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,reset:()=>{e=0,t=0,n=1},move:o,canZoom:e=>n!=l(e),zoom:(e,t)=>a(l(e),t),setZoom:a,worldToViewport:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:r,viewportToWorld:e=>{const{x:t,y:n}=s(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:s,viewportDimToWorld:e=>{const{w:t,h:n}=d(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:d}}(),B=()=>{U.reset(),U.move(-(z-w.width)/2,-(T-w.height)/2);const e=U.worldDimToViewport(D),t=w.width-40,n=w.height-40;if(e.w>t||e.h>n||e.w{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?c=e:"KeyE"===t.code&&(u=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&w([jt,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&w([Wt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),w([Kt,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Ht:Yt;w([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&w([Xt]),"replay"===o&&("KeyI"===e.code&&w([tn]),"KeyO"===e.code&&w([nn]),"KeyP"===e.code&&w([en])),"KeyF"===e.code&&(fl=!fl,vl=!0),"KeyG"===e.code&&(wl=!wl,vl=!0),"KeyM"===e.code&&w([Jt]),"KeyN"===e.code&&w([on]),"KeyC"===e.code&&w([ln]))}));const w=e=>{l.push(e)};return{addEvent:w,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});w([Ft,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(u&&c);else if(u){if(n.canZoom("in")){const e=f||m();w([Ht,...e])}}else if(c&&n.canZoom("out")){const e=f||m();w([Yt,...e])}},setHotkeys:e=>{a=e}}}(w,window,U,o),G=Ho.getImageUrl(e),$=()=>{const t=Ho.getStartTs(e),n=Ho.getFinishTs(e),o=C();a.setFinished(!!n),a.setDuration((n||o)-t)};$(),a.setPiecesDone(Ho.getFinishedPiecesCount(e)),a.setPiecesTotal(Ho.getPieceCount(e));const L=C();a.setActivePlayers(Ho.getActivePlayers(e,L)),a.setIdlePlayers(Ho.getIdlePlayers(e,L));const F=!!Ho.getFinishTs(e);let j=F;const W=()=>j&&!F,K=()=>ul(tl,100),H=()=>pl(nl,!1),Y=()=>pl(sl,!0),q=()=>{const e=K();i.volume=e/100,i.play()},Q=()=>"replay"===o?hl(ol,"#222222"):Ho.getPlayerBgColor(e,t)||hl(ol,"#222222"),Z=()=>"replay"===o?hl(ll,"#ffffff"):Ho.getPlayerColor(e,t)||hl(ll,"#ffffff");let J="",ee="",te=!1;const ne=e=>{te=e;const[t,n]=e?[J,"grab"]:[ee,"default"];w.style.cursor=`url('${t}') ${g} ${m}, ${n}`},oe=e=>{J=Qn.colorizedCanvas(r,u,e).toDataURL(),ee=Qn.colorizedCanvas(d,c,e).toDataURL(),ne(te)};oe(Z());const le=()=>{a.setReplaySpeed&&a.setReplaySpeed(v.speeds[v.speedIdx]),a.setReplayPaused&&a.setReplayPaused(v.paused)},ae=()=>{v.speedIdx+1{v.speedIdx>=1&&(v.speedIdx--,le())},ie=()=>{v.paused=!v.paused,le()},re=[];let de;let ue;if("play"===o?re.push(setInterval((()=>{$()}),1e3)):"replay"===o&&le(),"play"===o)bn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case rn:{const n=X.decodePlayer(a);n.id!==t&&(Ho.setPlayer(e,n.id,n),vl=!0)}break;case sn:{const t=X.decodePiece(a);Ho.setPiece(e,t.idx,t),vl=!0}break;case an:Ho.setPuzzleData(e,a),vl=!0}j=!!Ho.getFinishTs(e)}));else if("replay"===o){const t=(t,n)=>{const o=t;if(o[0]===Gt){const t=o[1];return Ho.addPlayer(e,t,n),!0}if(o[0]===$t){const t=Ho.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Ho.addPlayer(e,t,n),!0}if(o[0]===Lt){const t=Ho.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return Ho.handleInput(e,t,l,n),!0}return!1};let n=v.lastGameTs;const o=async()=>{v.logPointer+1>=v.log.length&&await b(e);const l=V();if(v.paused)return v.lastRealTs=l,void(de=setTimeout(o,50));const a=(l-v.lastRealTs)*v.speeds[v.speedIdx];let s=v.lastGameTs+a;for(;;){if(v.paused)break;const e=v.logPointer+1;if(e>=v.log.length)break;const o=v.log[v.logPointer],l=n+o[o.length-1],a=v.log[e],i=a[a.length-1],r=l+i;if(r>s){s+500*N{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,u=0,c=window.performance.now();const p=()=>{for(d=window.performance.now(),u+=Math.min(1,(d-c)/1e3);u>r;)u-=r,l(i);a(u/o),c=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{R.createKeyEvents();for(const n of R.consumeAll())if("play"===o){const o=n[0];if(o===Ft){const e=n[1],t=n[2],o=U.worldDimToViewport({w:e,h:t});vl=!0,U.move(o.w,o.h)}else if(o===Kt){if(ce&&!Ho.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-ce.x),l=Math.round(t.y-ce.y);vl=!0,U.move(o,l),ce=t}}else if(o===Qt)oe(n[1]);else if(o===jt){const e={x:n[1],y:n[2]};ce=U.worldToViewport(e),ne(!0)}else if(o===Wt)ce=null,ne(!1);else if(o===Ht){const e={x:n[1],y:n[2]};vl=!0,U.zoom("in",U.worldToViewport(e))}else if(o===Yt){const e={x:n[1],y:n[2]};vl=!0,U.zoom("out",U.worldToViewport(e))}else o===Xt?a.togglePreview():o===Jt?a.toggleSoundsEnabled():o===on?a.togglePlayerNames():o===ln&&B();const l=C();Ho.handleInput(e,t,n,l,(e=>{H()&&q()})).length>0&&(vl=!0),bn.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===en)ie();else if(e===nn)se();else if(e===tn)ae();else if(e===Ft){const e=n[1],t=n[2];vl=!0,U.move(e,t)}else if(e===Kt){if(ce){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-ce.x),l=Math.round(t.y-ce.y);vl=!0,U.move(o,l),ce=t}}else if(e===Qt)oe(n[1]);else if(e===jt){const e={x:n[1],y:n[2]};ce=U.worldToViewport(e),ne(!0)}else if(e===Wt)ce=null,ne(!1);else if(e===Ht){const e={x:n[1],y:n[2]};vl=!0,U.zoom("in",U.worldToViewport(e))}else if(e===Yt){const e={x:n[1],y:n[2]};vl=!0,U.zoom("out",U.worldToViewport(e))}else e===Xt?a.togglePreview():e===Jt?a.toggleSoundsEnabled():e===on?a.togglePlayerNames():e===ln&&B()}j=!!Ho.getFinishTs(e),W()&&(_.update(),vl=!0)},render:async()=>{if(!vl)return;const n=C();let l,s,i;window.DEBUG&&eo(0),O.fillStyle=Q(),O.fillRect(0,0,w.width,w.height),window.DEBUG&&to("clear done"),l=U.worldToViewportRaw(I),s=U.worldDimToViewportRaw(D),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&to("board done");const r=Ho.getPiecesSortedByZIndex(e);window.DEBUG&&to("get tiles done"),s=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?fl:wl)&&(i=M[e.idx],l=U.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&to("tiles done");const d=[];for(const a of Ho.getActivePlayers(e,n))u=a,("replay"===o||u.id!==t)&&(i=await f(a),l=U.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),Y()&&d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var u;O.fillStyle="white",O.textAlign="center";for(const[e,t,o]of d)O.fillText(e,t,o);window.DEBUG&&to("players done"),a.setActivePlayers(Ho.getActivePlayers(e,n)),a.setIdlePlayers(Ho.getIdlePlayers(e,n)),a.setPiecesDone(Ho.getFinishedPiecesCount(e)),window.DEBUG&&to("HUD done"),W()&&_.render(),vl=!1}}),{setHotkeys:e=>{R.setHotkeys(e)},onBgChange:e=>{gl(ol,e),R.addEvent([qt,e])},onColorChange:e=>{gl(ll,e),R.addEvent([Qt,e])},onNameChange:e=>{gl(al,e),R.addEvent([Zt,e])},onSoundsEnabledChange:e=>{cl(nl,e)},onSoundsVolumeChange:e=>{dl(tl,e),q()},onShowPlayerNamesChange:e=>{cl(sl,e)},replayOnSpeedUp:ae,replayOnSpeedDown:se,replayOnPauseToggle:ie,previewImageUrl:G,player:{background:Q(),color:Z(),name:"replay"===o?hl(al,"anon"):Ho.getPlayerName(e,t)||hl(al,"anon"),soundsEnabled:H(),soundsVolume:K(),showPlayerNames:Y()},disconnect:bn.disconnect,connect:x,unload:()=>{re.forEach((e=>{clearInterval(e)})),de&&clearTimeout(de),ue&&ue.stop()}}}var Cl=e({name:"game",components:{PuzzleStatus:xt,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,ConnectionOverlay:Cn,HelpOverlay:zn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,cuttingPuzzle:!0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await bl(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setPuzzleCut:()=>{this.cuttingPuzzle=!1},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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 xl={id:"game"},kl={key:0,class:"overlay"},Pl=n("div",{class:"overlay-content"},[n("div",null,"⏳ Cutting puzzle, please wait... ⏳")],-1),Al={class:"menu"},Sl={class:"tabs"},zl=i("🧩 Puzzles");Cl.render=function(e,i,r,d,u,c){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("connection-overlay"),f=a("puzzle-status"),w=a("router-link"),v=a("scores");return s(),t("div",xl,[p(n(g,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(m,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),e.cuttingPuzzle?(s(),t("div",kl,[Pl])):l("",!0),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",Al,[n("div",Sl,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[zl])),_:1}),n("div",{class:"opener",onClick:i[5]||(i[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("help",!0))},"ℹ️ Hotkeys")])]),n(v,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var Tl=e({name:"replay",components:{PuzzleStatus:xt,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,HelpOverlay:zn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,cuttingPuzzle:!0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await bl(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"replay",this.$el,{setPuzzleCut:()=>{this.cuttingPuzzle=!1},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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 Il={id:"replay"},Dl={key:0,class:"overlay"},El=n("div",{class:"overlay-content"},[n("div",null,"⏳ Cutting puzzle, please wait... ⏳")],-1),Ml={class:"menu"},Nl={class:"tabs"},_l=i("🧩 Puzzles");Tl.render=function(e,i,d,u,c,g){const h=a("settings-overlay"),m=a("preview-overlay"),y=a("help-overlay"),f=a("puzzle-status"),w=a("router-link"),v=a("scores");return s(),t("div",Il,[p(n(h,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(m,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),p(n(y,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),e.cuttingPuzzle?(s(),t("div",Dl,[El])):l("",!0),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:i[5]||(i[5]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",Ml,[n("div",Nl,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[_l])),_:1}),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("help",!0))},"ℹ️ Hotkeys")])]),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=k({history:P(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:it},{name:"game",path:"/g/:id",component:Cl},{name:"replay",path:"/replay/:id",component:Tl}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=A(S);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=X.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); diff --git a/build/public/assets/index.93936dee.js b/build/public/assets/index.93936dee.js new file mode 100644 index 0000000..e6ab743 --- /dev/null +++ b/build/public/assets/index.93936dee.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as l,b as o,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as C,s as x,u as k,x as P,y as A}from"./vendor.684f7bc8.js";var S=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const z={id:"app"},T={key:0,class:"nav"},I=i("Games overview"),M=i("New game");S.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",z,[e.showNav?(s(),t("ul",T,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:l((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:l((()=>[M])),_:1})])])):o("",!0),n(g)])};const E=864e5,D=e=>{const t=Math.floor(e/E);e%=E;const n=Math.floor(e/36e5);e%=36e5;const l=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${l}m ${Math.floor(e/1e3)}s`};var N=1,_=1e3,V=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>D(t-e),B=D,U=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",l=e,o=t||V();return`${n} ${O(l,o)}`}}});const R={class:"game-info-text"},$=n("br",null,null,-1),G=n("br",null,null,-1),L=n("br",null,null,-1),F=i(" ↪️ Watch replay ");U.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:l((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),$,i(" 👥 "+r(e.game.players),1),G,i(" "+r(e.time(e.game.started,e.game.finished)),1),L])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:l((()=>[F])),_:1},8,["to"])):o("",!0)],4)};var j=e({components:{GameTeaser:U},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 W=n("h1",null,"Running games",-1),K=n("h1",null,"Finished games",-1);j.render=function(e,l,o,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(s(!0),t(d,null,c(e.gamesRunning,((e,l)=>(s(),t("div",{class:"game-teaser-wrap",key:l},[n(p,{game:e},null,8,["game"])])))),128)),K,(s(!0),t(d,null,c(e.gamesFinished,((e,l)=>(s(),t("div",{class:"game-teaser-wrap",key:l},[n(p,{game:e},null,8,["game"])])))),128))])};var H=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});H.render=function(e,l,o,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:l[2]||(l[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:l[1]||(l[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y=e({name:"image-library",components:{ImageTeaser:H},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});Y.render=function(e,n,l,o,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,l)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:l},null,8,["image","onClick","onEditClick"])))),128))])};class q{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),l=t[n];t[n]=t[e],t[e]=l}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new q(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const Q=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},Z=(...e)=>{const t=t=>(...n)=>{const l=new Date,o=Q(l.getHours(),"00"),a=Q(l.getMinutes(),"00"),s=Q(l.getSeconds(),"00");console[t](`${o}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var X={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",q.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode,e.shapeMode,e.snapMode]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:q.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const l=[n,e[n]].map(encodeURIComponent);t.push(l.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const J={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};J.render=function(e,n,l,o,a,i){return s(),t("div",{style:i.style,title:l.title},null,12,["title"])};var ee=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const te=m();y("data-v-a4fa5e7e");const ne={key:0,class:"autocomplete"};f();const le=te(((e,l,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.input=t),placeholder:"Plants, People",onChange:l[2]||(l[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:l[3]||(l[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:l[4]||(l[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ne,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,l)=>(s(),t("li",{key:l,class:{active:l===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):o("",!0),(s(!0),t(d,null,c(e.values,((n,l)=>(s(),t("span",{key:l,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));ee.render=le,ee.__scopeId="data-v-a4fa5e7e";const oe=Z("NewImageDialog.vue");var ae=e({name:"new-image-dialog",components:{ResponsiveImage:J,TagsInput:ee},props:{autocompleteTags:{type:Function},uploadProgress:{type:Number},uploading:{type:String}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{uploadProgressPercent(){return this.uploadProgress?Math.round(100*this.uploadProgress):0},canPostToGallery(){return!this.uploading&&!(!this.previewUrl||!this.file)},canSetupGameClick(){return!this.uploading&&!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const l=n[0];return l.type.startsWith("image/")?l:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){oe.info("onDragleave"),this.droppable=!1}}});const se=n("div",{class:"drop-target"},null,-1),ie={key:0,class:"has-image"},re={key:1},de={class:"upload"},ce=n("span",{class:"btn"},"Upload File",-1),ue={class:"area-settings"},pe=n("td",null,[n("label",null,"Title")],-1),ge=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),he=n("td",null,[n("label",null,"Tags")],-1),me={class:"area-buttons"},ye=i("🖼️ Post to gallery"),fe=i("🧩 Post to gallery "),ve=n("br",null,null,-1),we=i(" + set up game");ae.render=function(e,l,o,c,h,m){const y=a("responsive-image"),f=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:l[11]||(l[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[10]||(l[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:l[3]||(l[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:l[4]||(l[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:l[5]||(l[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[se,e.previewUrl?(s(),t("div",ie,[n("span",{class:"remove btn",onClick:l[1]||(l[1]=t=>e.previewUrl="")},"X"),n(y,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",re,[n("label",de,[n("input",{type:"file",style:{display:"none"},onChange:l[2]||(l[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ce])]))],34),n("div",ue,[n("table",null,[n("tr",null,[pe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":l[6]||(l[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),ge,n("tr",null,[he,n("td",null,[n(f,{modelValue:e.tags,"onUpdate:modelValue":l[7]||(l[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",me,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:l[8]||(l[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},["postToGallery"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[ye],64))],8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:l[9]||(l[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},["setupGame"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[fe,ve,we],64))],8,["disabled"])])])])};var be=e({name:"edit-image-dialog",components:{ResponsiveImage:J,TagsInput:ee},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Ce={class:"area-image"},xe={class:"has-image"},ke={class:"area-settings"},Pe=n("td",null,[n("label",null,"Title")],-1),Ae=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Se=n("td",null,[n("label",null,"Tags")],-1),ze={class:"area-buttons"};var Te,Ie,Me,Ee,De,Ne,_e,Ve;be.render=function(e,l,o,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("div",Ce,[n("div",xe,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ke,[n("table",null,[n("tr",null,[Pe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ae,n("tr",null,[Se,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[2]||(l[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",ze,[n("button",{class:"btn",onClick:l[3]||(l[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])},(Ie=Te||(Te={}))[Ie.Flat=0]="Flat",Ie[Ie.Out=1]="Out",Ie[Ie.In=-1]="In",(Ee=Me||(Me={}))[Ee.FINAL=0]="FINAL",Ee[Ee.ANY=1]="ANY",(Ne=De||(De={}))[Ne.NORMAL=0]="NORMAL",Ne[Ne.ANY=1]="ANY",Ne[Ne.FLAT=2]="FLAT",(Ve=_e||(_e={}))[Ve.NORMAL=0]="NORMAL",Ve[Ve.REAL=1]="REAL";var Oe=e({name:"new-game-dialog",components:{ResponsiveImage:J},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Me.ANY,shapeMode:De.NORMAL,snapMode:_e.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Be={class:"area-image"},Ue={class:"has-image"},Re={key:0,class:"image-title"},$e={key:0,class:"image-title-title"},Ge={key:1,class:"image-title-dim"},Le={class:"area-settings"},Fe=n("td",null,[n("label",null,"Pieces")],-1),je=n("td",null,[n("label",null,"Scoring: ")],-1),We=i(" Any (Score when pieces are connected to each other or on final location)"),Ke=n("br",null,null,-1),He=i(" Final (Score when pieces are put to their final location)"),Ye=n("td",null,[n("label",null,"Shapes: ")],-1),qe=i(" Normal"),Qe=n("br",null,null,-1),Ze=i(" Any (flat pieces can occur anywhere)"),Xe=n("br",null,null,-1),Je=i(" Flat (all pieces flat on all sides)"),et=n("td",null,[n("label",null,"Snapping: ")],-1),tt=i(" Normal (pieces snap to final destination and to each other)"),nt=n("br",null,null,-1),lt=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),ot={class:"area-buttons"};Oe.render=function(e,l,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:l[11]||(l[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[10]||(l[10]=u((()=>{}),["stop"]))},[n("div",Be,[n("div",Ue,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title||e.image.width||e.image.height?(s(),t("div",Re,[e.image.title?(s(),t("span",$e,'"'+r(e.image.title)+'"',1)):o("",!0),e.image.width||e.image.height?(s(),t("span",Ge,"("+r(e.image.width)+" ✕ "+r(e.image.height)+")",1)):o("",!0)])):o("",!0)]),n("div",Le,[n("table",null,[n("tr",null,[Fe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[je,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[2]||(l[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),We]),Ke,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[3]||(l[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),He])])]),n("tr",null,[Ye,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[4]||(l[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),qe]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[5]||(l[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ze]),Xe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[6]||(l[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),Je])])]),n("tr",null,[et,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[7]||(l[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),tt]),nt,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[8]||(l[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),lt])])])])]),n("div",ot,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:l[9]||(l[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};const at=async(e,t,n)=>new Promise(((l,o)=>{const a=new window.XMLHttpRequest;a.open(e,t,!0),a.withCredentials=!0;for(const e in n.headers||{})a.setRequestHeader(e,n.headers[e]);a.addEventListener("load",(function(e){l({status:this.status,text:this.responseText,json:async()=>JSON.parse(this.responseText)})})),a.addEventListener("error",(function(e){o(new Error("xhr error"))})),a.upload&&n.onUploadProgress&&a.upload.addEventListener("progress",(function(e){n.onUploadProgress&&n.onUploadProgress(e)})),a.send(n.body)}));var st=(e,t)=>at("post",e,t),it=e({components:{ImageLibrary:Y,NewImageDialog:ae,EditImageDialog:be,NewGameDialog:Oe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:"",uploading:"",uploadProgress:0}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${X.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){this.uploadProgress=0;const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await st("/api/upload",{body:t,onUploadProgress:e=>{this.uploadProgress=e.loaded/e.total}});return this.uploadProgress=1,await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){this.uploading="postToGallery",await this.uploadImage(e),this.uploading="",this.dialog="",await this.loadImages()},async setupGameClick(e){this.uploading="setupGame";const t=await this.uploadImage(e);this.uploading="",this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const rt={class:"upload-image-teaser"},dt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),ct={key:0},ut=i(" Tags: "),pt=i(" Sort by: "),gt=n("option",{value:"date_desc"},"Newest first",-1),ht=n("option",{value:"date_asc"},"Oldest first",-1),mt=n("option",{value:"alpha_asc"},"A-Z",-1),yt=n("option",{value:"alpha_desc"},"Z-A",-1);it.render=function(e,l,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[n("div",rt,[n("div",{class:"btn btn-big",onClick:l[1]||(l[1]=t=>e.dialog="new-image")},"Upload your image"),dt]),n("div",null,[e.tags.length>0?(s(),t("label",ct,[ut,(s(!0),t(d,null,c(e.relevantTags,((n,l)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:l,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):o("",!0),n("label",null,[pt,p(n("select",{"onUpdate:modelValue":l[2]||(l[2]=t=>e.filters.sort=t),onChange:l[3]||(l[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[gt,ht,mt,yt],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:l[4]||(l[4]=t=>e.dialog=""),uploadProgress:e.uploadProgress,uploading:e.uploading,onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","uploadProgress","uploading","onPostToGalleryClick","onSetupGameClick"])):o("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:l[5]||(l[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):o("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:l[6]||(l[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):o("",!0)])};var ft=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"},wt=n("div",null,"Scores",-1),bt=n("td",null,"⚡",-1),Ct=n("td",null,"💤",-1);ft.render=function(e,l,o,a,i,u){return s(),t("div",vt,[wt,n("table",null,[(s(!0),t(d,null,c(e.actives,((e,l)=>(s(),t("tr",{key:l,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,l)=>(s(),t("tr",{key:l,style:{color:e.color}},[Ct,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var xt=e({name:"puzzle-status",props:{finished:{type:Boolean,required:!0},duration:{type:Number,required:!0},piecesDone:{type:Number,required:!0},piecesTotal:{type:Number,required:!0}},computed:{icon(){return this.finished?"🏁":"⏳"},durationStr(){return B(this.duration)}}});const kt={class:"timer"};xt.render=function(e,l,o,a,i,d){return s(),t("div",kt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var Pt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const At=m();y("data-v-4d56fc17");const St=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),It=n("td",null,[n("label",null,"Sounds: ")],-1),Mt=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Et={class:"sound-volume"},Dt=n("td",null,[n("label",null,"Show player names: ")],-1);f();const Nt=At(((e,l,o,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:l[10]||(l[10]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:l[9]||(l[9]=u((()=>{}),["stop"]))},[n("tr",null,[St,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":l[1]||(l[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[zt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":l[2]||(l[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[Tt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":l[3]||(l[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[It,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":l[4]||(l[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[C,e.modelValue.soundsEnabled]])])]),n("tr",null,[Mt,n("td",Et,[n("span",{onClick:l[5]||(l[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:l[6]||(l[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:l[7]||(l[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])]),n("tr",null,[Dt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":l[8]||(l[8]=t=>e.modelValue.showPlayerNames=t)},null,512),[[C,e.modelValue.showPlayerNames]])])])])]))));Pt.render=Nt,Pt.__scopeId="data-v-4d56fc17";var _t=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const Vt={class:"preview"};_t.render=function(e,l,o,a,i,r){return s(),t("div",{class:"overlay",onClick:l[1]||(l[1]=t=>e.$emit("bgclick"))},[n("div",Vt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Ot=e({name:"help-overlay",emits:{bgclick:null},props:{game:{type:Object,required:!0}},computed:{scoreMode(){switch(this.game.scoreMode){case Me.ANY:return["Any","Score when pieces are connected to each other or on final location"];case Me.FINAL:default:return["Final","Score when pieces are put to their final location"]}},shapeMode(){switch(this.game.shapeMode){case De.FLAT:return["Flat","all pieces flat on all sides"];case De.ANY:return["Any","flat pieces can occur anywhere"];case De.NORMAL:default:return["Normal",""]}},snapMode(){switch(this.game.snapMode){case _e.REAL:return["Real","pieces snap only to corners, already snapped pieces and to each other"];case _e.NORMAL:default:return["Normal","pieces snap to final destination and to each other"]}}}});const Bt=n("tr",null,[n("td",{colspan:"2"},"Info about this puzzle")],-1),Ut=n("td",null,"Image Title: ",-1),Rt=n("td",null,"Snap Mode: ",-1),$t=n("td",null,"Shape Mode: ",-1),Gt=n("td",null,"Score Mode: ",-1);Ot.render=function(e,l,o,a,i,d){return s(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[Bt,n("tr",null,[Ut,n("td",null,r(e.game.puzzle.info.image.title),1)]),n("tr",null,[Rt,n("td",null,[n("span",{title:e.snapMode[1]},r(e.scoreMode[0]),9,["title"])])]),n("tr",null,[$t,n("td",null,[n("span",{title:e.snapMode[1]},r(e.shapeMode[0]),9,["title"])])]),n("tr",null,[Gt,n("td",null,[n("span",{title:e.snapMode[1]},r(e.snapMode[0]),9,["title"])])])])])};var Lt=1,Ft=4,jt=2,Wt=3,Kt=2,Ht=4,Yt=3,qt=9,Qt=1,Zt=2,Xt=3,Jt=4,en=5,tn=6,nn=7,ln=8,on=10,an=11,sn=12,rn=13,dn=14,cn=15,un=16,pn=1,gn=2,hn=3;const mn=Z("Communication.js");let yn,fn=[],vn=e=>{fn.push(e)},wn=[],bn=e=>{wn.push(e)};let Cn=0;const xn=e=>{Cn!==e&&(Cn=e,bn(e))};function kn(e){if(2===Cn)try{yn.send(JSON.stringify(e))}catch(t){mn.info("unable to send message.. maybe because ws is invalid?")}}let Pn,An;var Sn={connect:function(e,t,n){return Pn=0,An={},xn(3),new Promise((l=>{yn=new WebSocket(e,n+"|"+t),yn.onopen=()=>{xn(2),kn([Wt])},yn.onmessage=e=>{const t=JSON.parse(e.data),o=t[0];if(o===Ft){const e=t[1];l(e)}else{if(o!==Lt)throw`[ 2021-05-09 invalid connect msgType ${o} ]`;{const e=t[1],l=t[2];if(e===n&&An[l])return void delete An[l];vn(t)}}},yn.onerror=()=>{throw xn(1),"[ 2021-05-15 onerror ]"},yn.onclose=e=>{4e3===e.code||1001===e.code?xn(4):xn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},l=await fetch(`/api/replay-data${X.asQueryArgs(n)}`);return await l.json()},disconnect:function(){yn&&yn.close(4e3),Pn=0,An={}},sendClientEvent:function(e){Pn++,An[Pn]=e,kn([jt,Pn,An[Pn]])},onServerChange:function(e){vn=e;for(const t of fn)vn(t);fn=[]},onConnectionStateChange:function(e){bn=e;for(const t of wn)bn(t);wn=[]},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},zn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Sn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Sn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const Tn={key:0,class:"overlay connection-lost"},In={key:0,class:"overlay-content"},Mn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),En={key:1,class:"overlay-content"},Dn=n("div",null,"Connecting...",-1);zn.render=function(e,l,a,i,r,d){return e.show?(s(),t("div",Tn,[e.lostConnection?(s(),t("div",In,[Mn,n("span",{class:"btn",onClick:l[1]||(l[1]=t=>e.$emit("reconnect"))},"Reconnect")])):o("",!0),e.connecting?(s(),t("div",En,[Dn])):o("",!0)])):o("",!0)};var Nn=e({name:"help-overlay",emits:{bgclick:null}});const _n=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),Vn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),On=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),Bn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),Un=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Rn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),$n=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),Gn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Ln=n("tr",null,[n("td",null,"🎯 Center puzzle in screen:"),n("td",null,[n("div",null,[n("kbd",null,"C")])])],-1),Fn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),jn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Wn=n("tr",null,[n("td",null,"👤 Toggle player names:"),n("td",null,[n("div",null,[n("kbd",null,"N")])])],-1),Kn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),Hn=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),Yn=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),qn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);Nn.render=function(e,l,o,a,i,r){return s(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[_n,Vn,On,Bn,Un,Rn,$n,Gn,Ln,Fn,jn,Wn,Kn,Hn,Yn,qn])])};var Qn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Zn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Xn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Jn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),el=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function tl(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var nl={createCanvas:tl,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const l=tl(t,n);return l.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(l)},colorizedCanvas:function(e,t,n){const l=tl(e.width,e.height),o=l.getContext("2d");return o.save(),o.drawImage(t,0,0),o.fillStyle=n,o.globalCompositeOperation="source-in",o.fillRect(0,0,t.width,t.height),o.restore(),o.save(),o.globalCompositeOperation="destination-over",o.drawImage(e,0,0),o.restore(),l}};const ll=Z("Debug.js");let ol=0,al=0;var sl=e=>{ol=performance.now(),al=e},il=e=>{const t=performance.now(),n=t-ol;n>al&&ll.log(e+": "+n),ol=t};function rl(e,t){const n=e.x-t.x,l=e.y-t.y;return Math.sqrt(n*n+l*l)}function dl(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var cl={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:rl,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:dl,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 rl(dl(e),dl(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const ul=Z("PuzzleGraphics.js");function pl(e,t){const n=X.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var gl={loadPuzzleBitmaps:async function(e){const t=await nl.loadImageToBitmap(e.info.imageUrl),n=await nl.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){ul.log("start createPuzzleTileBitmaps");const l=n.tileSize,o=n.tileMarginWidth,a=n.tileDrawSize,s=l/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:o,y:o},r=cl.pointAdd(a,{x:l,y:0}),c=cl.pointAdd(r,{x:0,y:l}),u=cl.pointSub(c,{x:l,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let l=0;lX.decodePiece(hl[e].puzzle.tiles[t]),Il=(e,t)=>Tl(e,t).group,Ml=(e,t)=>{const n=hl[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},El=(e,t)=>{const n=hl[e].puzzle.info,l={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},o=function(e,t){const n=hl[e].puzzle.info,l=X.coordByPieceIdx(n,t),o=l.x*n.tileSize,a=l.y*n.tileSize;return{x:o,y:a}}(e,t);return cl.pointAdd(l,o)},Dl=(e,t)=>Tl(e,t).pos,Nl=e=>{const t=Ql(e),n=Zl(e),l=Math.round(t/4),o=Math.round(n/4);return{x:0-l,y:0-o,w:t+2*l,h:n+2*o}},_l=(e,t)=>{const n=Ul(e),l=Tl(e,t);return{x:l.pos.x,y:l.pos.y,w:n,h:n}},Vl=(e,t)=>Tl(e,t).z,Ol=(e,t)=>{for(const n of hl[e].puzzle.tiles){const e=X.decodePiece(n);if(e.owner===t)return e.idx}return-1},Bl=e=>hl[e].puzzle.info.tileDrawSize,Ul=e=>hl[e].puzzle.info.tileSize,Rl=e=>hl[e].puzzle.data.maxGroup,$l=e=>hl[e].puzzle.data.maxZ;function Gl(e,t){const n=hl[e].puzzle.info,l=X.coordByPieceIdx(n,t);return[l.y>0?t-n.tilesX:-1,l.x0?t-1:-1]}const Ll=(e,t,n)=>{for(const l of t)zl(e,l,{z:n})},Fl=(e,t,n)=>{const l=Dl(e,t);zl(e,t,{pos:cl.pointAdd(l,n)})},jl=(e,t,n)=>{const l=Bl(e),o=Nl(e),a=n;for(const s of t){const t=Tl(e,s);t.pos.x+n.xo.x+o.w&&(a.x=Math.min(o.x+o.w-t.pos.x+l,a.x)),t.pos.y+n.yo.y+o.h&&(a.y=Math.min(o.y+o.h-t.pos.y+l,a.y))}for(const s of t)Fl(e,s,a)},Wl=(e,t)=>Tl(e,t).owner,Kl=(e,t)=>{for(const n of t)zl(e,n,{owner:-1,z:1})},Hl=(e,t,n)=>{for(const l of t)zl(e,l,{owner:n})};function Yl(e,t){const n=hl[e].puzzle.tiles,l=X.decodePiece(n[t]),o=[];if(l.group)for(const a of n){const e=X.decodePiece(a);e.group===l.group&&o.push(e.idx)}else o.push(l.idx);return o}const ql=(e,t)=>{const n=yl(e,t);return n?n.points:0},Ql=e=>hl[e].puzzle.info.table.width,Zl=e=>hl[e].puzzle.info.table.height;var Xl={setGame:function(e,t){hl[e]=t},exists:function(e){return!!hl[e]||!1},playerExists:vl,getActivePlayers:function(e,t){const n=t-30*_;return wl(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return wl(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){vl(e,t)?Al(e,t,{ts:n}):fl(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:Pl,getPieceCount:bl,getImageUrl:function(e){var t;const n=(null==(t=hl[e].puzzle.info.image)?void 0:t.url)||hl[e].puzzle.info.imageUrl;if(!n)throw new Error("[2021-07-11] no image url set");return n},get:function(e){return hl[e]||null},getAllGames:function(){return Object.values(hl).sort(((e,t)=>kl(e.id)===kl(t.id)?t.puzzle.data.started-e.puzzle.data.started:kl(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=yl(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=yl(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=yl(e,t);return n?n.name:null},getPlayerIndexById:ml,getPlayerIdByIndex:function(e,t){return hl[e].players.length>t?X.decodePlayer(hl[e].players[t]).id:null},changePlayer:Al,setPlayer:fl,setPiece:function(e,t,n){hl[e].puzzle.tiles[t]=X.encodePiece(n)},setPuzzleData:function(e,t){hl[e].puzzle.data=t},getTableWidth:Ql,getTableHeight:Zl,getPuzzle:e=>hl[e].puzzle,getRng:e=>hl[e].rng.obj,getPuzzleWidth:e=>hl[e].puzzle.info.width,getPuzzleHeight:e=>hl[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return hl[e].puzzle.tiles.map(X.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Ol(e,t);return n<0?null:hl[e].puzzle.tiles[n]},getPieceDrawOffset:e=>hl[e].puzzle.info.tileDrawOffset,getPieceDrawSize:Bl,getFinalPiecePos:El,getStartTs:e=>hl[e].puzzle.data.started,getFinishTs:e=>hl[e].puzzle.data.finished,handleInput:function(e,t,n,l,o){const a=hl[e].puzzle,s=function(e,t){return t in hl[e].evtInfos?hl[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([pn,a.data])},d=t=>{i.push([gn,X.encodePiece(Tl(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=yl(e,t);n&&i.push([hn,X.encodePlayer(n)])},p=n[0];if(p===tn){const o=n[1];Al(e,t,{bgcolor:o,ts:l}),u()}else if(p===nn){const o=n[1];Al(e,t,{color:o,ts:l}),u()}else if(p===ln){const o=`${n[1]}`.substr(0,16);Al(e,t,{name:o,ts:l}),u()}else if(p===qt){const o=n[1],a=n[2],s=yl(e,t);if(s){const n=s.x-o,i=s.y-a;Al(e,t,{ts:l,x:n,y:i}),u()}}else if(p===Qt){const o={x:n[1],y:n[2]};Al(e,t,{d:1,ts:l}),u(),s._last_mouse_down=o;const a=((e,t)=>{const n=hl[e].puzzle.info,l=hl[e].puzzle.tiles;let o=-1,a=-1;for(let s=0;so)&&(o=e.z,a=s)}return a})(e,o);if(a>=0){const n=$l(e)+1;Sl(e,{maxZ:n}),r();const l=Yl(e,a);Ll(e,l,$l(e)),Hl(e,l,t),c(l)}s._last_mouse=o}else if(p===Xt){const o=n[1],a=n[2],i={x:o,y:a};if(null===s._last_mouse_down)Al(e,t,{x:o,y:a,ts:l}),u();else{const n=Ol(e,t);if(n>=0){Al(e,t,{x:o,y:a,ts:l}),u();const r=Yl(e,n);let d=cl.pointInBounds(i,Nl(e))&&cl.pointInBounds(s._last_mouse_down,Nl(e));for(const t of r){const n=_l(e,t);if(cl.pointInBounds(i,n)){d=!0;break}}if(d){const t=o-s._last_mouse_down.x,n=a-s._last_mouse_down.y;jl(e,r,{x:t,y:n}),c(r)}}else Al(e,t,{ts:l}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===Zt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Ol(e,t);if(g>=0){const n=Yl(e,g);Hl(e,n,0),c(n);const s=Dl(e,g),i=El(e,g);let h=!1;if(xl(e)===_e.REAL){for(const t of n)if(Ml(e,t)){h=!0;break}}else h=!0;if(h&&cl.pointDistance(i,s){const o=hl[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const l=Il(e,t),o=Il(e,n);return!(!l||l!==o)})(e,t,n))return!1;const a=Dl(e,t),s=cl.pointAdd(Dl(e,n),{x:l[0]*o.tileSize,y:l[1]*o.tileSize});if(cl.pointDistance(a,s){const l=hl[e].puzzle.tiles,o=Il(e,t),a=Il(e,n);let s;const i=[];o&&i.push(o),a&&i.push(a),o?s=o:a?s=a:(Sl(e,{maxGroup:Rl(e)+1}),r(),s=Rl(e));if(zl(e,t,{group:s}),d(t),zl(e,n,{group:s}),d(n),i.length>0)for(const r of l){const t=X.decodePiece(r);i.includes(t.group)&&(zl(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),o=Yl(e,t),((e,t)=>-1===Wl(e,t))(e,n))Kl(e,o);else{const t=((e,t)=>{let n=0;for(const l of t){const t=Vl(e,l);t>n&&(n=t)}return n})(e,o);Ll(e,o,t)}return c(o),!0}return!1};let a=!1;for(const t of Yl(e,g)){const l=Gl(e,t);if(n(e,t,l[0],[0,1])||n(e,t,l[1],[-1,0])||n(e,t,l[2],[0,-1])||n(e,t,l[3],[1,0])){a=!0;break}}if(a&&Cl(e)===Me.ANY){const n=ql(e,t)+1;Al(e,t,{d:p,ts:l,points:n}),u()}else Al(e,t,{d:p,ts:l}),u();a&&xl(e)===_e.REAL&&Pl(e)===bl(e)&&(Sl(e,{finished:l}),r()),a&&o&&o(t)}}else Al(e,t,{d:p,ts:l}),u();s._last_mouse=i}else if(p===Jt){const o=n[1],a=n[2];Al(e,t,{x:o,y:a,ts:l}),u(),s._last_mouse={x:o,y:a}}else if(p===en){const o=n[1],a=n[2];Al(e,t,{x:o,y:a,ts:l}),u(),s._last_mouse={x:o,y:a}}else Al(e,t,{ts:l}),u();return function(e,t,n){hl[e].evtInfos[t]=n}(e,t,s),i}};let Jl=-10,eo=20,to=2,no=15;class lo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Jl+Math.random()*eo,this.vy=-1*(to+Math.random()*no),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;to=t/2,no=t-to;const n=1/4*this.canvas.width/(t/2);Jl=-n,eo=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new lo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new lo(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{localStorage.setItem(e,t)},ho=e=>localStorage.getItem(e);var mo=(e,t)=>{go(e,`${t}`)},yo=(e,t)=>{const n=ho(e);if(null===n)return t;const l=parseInt(n,10);return isNaN(l)?t:l},fo=(e,t)=>{go(e,t?"1":"0")},vo=(e,t)=>{const n=ho(e);return null===n?t:"1"===n},wo=(e,t)=>{go(e,t)},bo=(e,t)=>{const n=ho(e);return null===n?t:n};const Co={"./grab.png":Zn,"./grab_mask.png":Xn,"./hand.png":Jn,"./hand_mask.png":el},xo={"./click.mp3":Qn};let ko=!0,Po=!0;let Ao=!0;async function So(e,t,n,l,o,a){void 0===window.DEBUG&&(window.DEBUG=!1);const s=xo["./click.mp3"].default,i=new Audio(s),r=await nl.loadImageToBitmap(Co["./grab.png"].default),d=await nl.loadImageToBitmap(Co["./hand.png"].default),c=await nl.loadImageToBitmap(Co["./grab_mask.png"].default),u=await nl.loadImageToBitmap(Co["./hand_mask.png"].default),p=r.width,g=Math.round(p/2),h=r.height,m=Math.round(h/2),y={},f=async e=>{const t=e.color+" "+e.d;if(!y[t]){const n=e.d?r:d;if(e.color){const l=e.d?c:u;y[t]=await createImageBitmap(nl.colorizedCanvas(n,l,e.color))}else y[t]=n}return y[t]},v=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,Ao=!0})),t}(o,nl.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};Sn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await Sn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let C=()=>0;const x=async()=>{if("play"===l){const l=await Sn.connect(n,e,t),o=X.decodeGame(l);Xl.setGame(o.id,o),C=()=>V()}else{if("replay"!==l)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=X.decodeGame(t.game);Xl.setGame(n.id,n),w.lastRealTs=V(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,C=()=>w.lastGameTs}}Ao=!0};await x();const k=Xl.getPieceDrawOffset(e),P=Xl.getPieceDrawSize(e),A=Xl.getPuzzleWidth(e),S=Xl.getPuzzleHeight(e),z=Xl.getTableWidth(e),T=Xl.getTableHeight(e),I={x:(z-A)/2,y:(T-S)/2},M={w:A,h:S},E={w:P,h:P},D=await gl.loadPuzzleBitmaps(Xl.getPuzzle(e)),_=new ao(v,Xl.getRng(e));_.init();const O=v.getContext("2d");v.classList.add("loaded"),a.setPuzzleCut();const B=function(){let e=0,t=0,n=1;const l=(l,o)=>{e+=l/n,t+=o/n},o=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},a=(e,t)=>{if(n==e)return!1;const o=1-n/e;return l(-t.x*o,-t.y*o),n=e,!0},s=l=>({x:l.x/n-e,y:l.y/n-t}),i=l=>({x:(l.x+e)*n,y:(l.y+t)*n}),r=e=>({w:e.w*n,h:e.h*n}),d=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,reset:()=>{e=0,t=0,n=1},move:l,canZoom:e=>n!=o(e),zoom:(e,t)=>a(o(e),t),setZoom:a,worldToViewport:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:r,viewportToWorld:e=>{const{x:t,y:n}=s(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:s,viewportDimToWorld:e=>{const{w:t,h:n}=d(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:d}}(),U=()=>{B.reset(),B.move(-(z-v.width)/2,-(T-v.height)/2);const e=B.worldDimToViewport(M),t=v.width-40,n=v.height-40;if(e.w>t||e.h>n||e.w{const l=n.viewportToWorld({x:e,y:t});return[l.x,l.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Qt,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Zt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([Xt,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Jt:en;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([on]),"replay"===l&&("KeyI"===e.code&&v([rn]),"KeyO"===e.code&&v([dn]),"KeyP"===e.code&&v([sn])),"KeyF"===e.code&&(ko=!ko,Ao=!0),"KeyG"===e.code&&(Po=!Po,Ao=!0),"KeyM"===e.code&&v([an]),"KeyN"===e.code&&v([cn]),"KeyC"===e.code&&v([un]))}));const v=e=>{o.push(e)};return{addEvent:v,consumeAll:()=>{if(0===o.length)return[];const e=o.slice();return o=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const l=(p?24:12)*Math.sqrt(n.getCurrentZoom()),o=n.viewportDimToWorld({w:e*l,h:t*l});v([qt,o.w,o.h]),f&&(f[0]-=o.w,f[1]-=o.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Jt,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([en,...e])}},setHotkeys:e=>{a=e}}}(v,window,B,l),$=Xl.getImageUrl(e),G=()=>{const t=Xl.getStartTs(e),n=Xl.getFinishTs(e),l=C();a.setFinished(!!n),a.setDuration((n||l)-t)};G(),a.setPiecesDone(Xl.getFinishedPiecesCount(e)),a.setPiecesTotal(Xl.getPieceCount(e));const L=C();a.setActivePlayers(Xl.getActivePlayers(e,L)),a.setIdlePlayers(Xl.getIdlePlayers(e,L));const F=!!Xl.getFinishTs(e);let j=F;const W=()=>j&&!F,K=()=>yo(so,100),H=()=>vo(io,!1),Y=()=>vo(po,!0),q=()=>{const e=K();i.volume=e/100,i.play()},Q=()=>"replay"===l?bo(ro,"#222222"):Xl.getPlayerBgColor(e,t)||bo(ro,"#222222"),Z=()=>"replay"===l?bo(co,"#ffffff"):Xl.getPlayerColor(e,t)||bo(co,"#ffffff");let J="",ee="",te=!1;const ne=e=>{te=e;const[t,n]=e?[J,"grab"]:[ee,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},le=e=>{J=nl.colorizedCanvas(r,c,e).toDataURL(),ee=nl.colorizedCanvas(d,u,e).toDataURL(),ne(te)};le(Z());const oe=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ae=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,oe())},ie=()=>{w.paused=!w.paused,oe()},re=[];let de;let ce;if("play"===l?re.push(setInterval((()=>{G()}),1e3)):"replay"===l&&oe(),"play"===l)Sn.onServerChange((n=>{n[0],n[1],n[2];const l=n[3];for(const[o,a]of l)switch(o){case hn:{const n=X.decodePlayer(a);n.id!==t&&(Xl.setPlayer(e,n.id,n),Ao=!0)}break;case gn:{const t=X.decodePiece(a);Xl.setPiece(e,t.idx,t),Ao=!0}break;case pn:Xl.setPuzzleData(e,a),Ao=!0}j=!!Xl.getFinishTs(e)}));else if("replay"===l){const t=(t,n)=>{const l=t;if(l[0]===Kt){const t=l[1];return Xl.addPlayer(e,t,n),!0}if(l[0]===Ht){const t=Xl.getPlayerIdByIndex(e,l[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Xl.addPlayer(e,t,n),!0}if(l[0]===Yt){const t=Xl.getPlayerIdByIndex(e,l[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const o=l[2];return Xl.handleInput(e,t,o,n),!0}return!1};let n=w.lastGameTs;const l=async()=>{w.logPointer+1>=w.log.length&&await b(e);const o=V();if(w.paused)return w.lastRealTs=o,void(de=setTimeout(l,50));const a=(o-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const l=w.log[w.logPointer],o=n+l[l.length-1],a=w.log[e],i=a[a.length-1],r=o+i;if(r>s){s+500*N{let t=!1;const n=e.fps||60,l=e.slow||1,o=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=l*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,o(i);a(c/l),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{R.createKeyEvents();for(const n of R.consumeAll())if("play"===l){const l=n[0];if(l===qt){const e=n[1],t=n[2],l=B.worldDimToViewport({w:e,h:t});Ao=!0,B.move(l.w,l.h)}else if(l===Xt){if(ue&&!Xl.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),l=Math.round(t.x-ue.x),o=Math.round(t.y-ue.y);Ao=!0,B.move(l,o),ue=t}}else if(l===nn)le(n[1]);else if(l===Qt){const e={x:n[1],y:n[2]};ue=B.worldToViewport(e),ne(!0)}else if(l===Zt)ue=null,ne(!1);else if(l===Jt){const e={x:n[1],y:n[2]};Ao=!0,B.zoom("in",B.worldToViewport(e))}else if(l===en){const e={x:n[1],y:n[2]};Ao=!0,B.zoom("out",B.worldToViewport(e))}else l===on?a.togglePreview():l===an?a.toggleSoundsEnabled():l===cn?a.togglePlayerNames():l===un&&U();const o=C();Xl.handleInput(e,t,n,o,(e=>{H()&&q()})).length>0&&(Ao=!0),Sn.sendClientEvent(n)}else if("replay"===l){const e=n[0];if(e===sn)ie();else if(e===dn)se();else if(e===rn)ae();else if(e===qt){const e=n[1],t=n[2];Ao=!0,B.move(e,t)}else if(e===Xt){if(ue){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),l=Math.round(t.x-ue.x),o=Math.round(t.y-ue.y);Ao=!0,B.move(l,o),ue=t}}else if(e===nn)le(n[1]);else if(e===Qt){const e={x:n[1],y:n[2]};ue=B.worldToViewport(e),ne(!0)}else if(e===Zt)ue=null,ne(!1);else if(e===Jt){const e={x:n[1],y:n[2]};Ao=!0,B.zoom("in",B.worldToViewport(e))}else if(e===en){const e={x:n[1],y:n[2]};Ao=!0,B.zoom("out",B.worldToViewport(e))}else e===on?a.togglePreview():e===an?a.toggleSoundsEnabled():e===cn?a.togglePlayerNames():e===un&&U()}j=!!Xl.getFinishTs(e),W()&&(_.update(),Ao=!0)},render:async()=>{if(!Ao)return;const n=C();let o,s,i;window.DEBUG&&sl(0),O.fillStyle=Q(),O.fillRect(0,0,v.width,v.height),window.DEBUG&&il("clear done"),o=B.worldToViewportRaw(I),s=B.worldDimToViewportRaw(M),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(o.x,o.y,s.w,s.h),window.DEBUG&&il("board done");const r=Xl.getPiecesSortedByZIndex(e);window.DEBUG&&il("get tiles done"),s=B.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?ko:Po)&&(i=D[e.idx],o=B.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,o.x,o.y,s.w,s.h));window.DEBUG&&il("tiles done");const d=[];for(const a of Xl.getActivePlayers(e,n))c=a,("replay"===l||c.id!==t)&&(i=await f(a),o=B.worldToViewport(a),O.drawImage(i,o.x-g,o.y-m),Y()&&d.push([`${a.name} (${a.points})`,o.x,o.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,l]of d)O.fillText(e,t,l);window.DEBUG&&il("players done"),a.setActivePlayers(Xl.getActivePlayers(e,n)),a.setIdlePlayers(Xl.getIdlePlayers(e,n)),a.setPiecesDone(Xl.getFinishedPiecesCount(e)),window.DEBUG&&il("HUD done"),W()&&_.render(),Ao=!1}}),{setHotkeys:e=>{R.setHotkeys(e)},onBgChange:e=>{wo(ro,e),R.addEvent([tn,e])},onColorChange:e=>{wo(co,e),R.addEvent([nn,e])},onNameChange:e=>{wo(uo,e),R.addEvent([ln,e])},onSoundsEnabledChange:e=>{fo(io,e)},onSoundsVolumeChange:e=>{mo(so,e),q()},onShowPlayerNamesChange:e=>{fo(po,e)},replayOnSpeedUp:ae,replayOnSpeedDown:se,replayOnPauseToggle:ie,previewImageUrl:$,player:{background:Q(),color:Z(),name:"replay"===l?bo(uo,"anon"):Xl.getPlayerName(e,t)||bo(uo,"anon"),soundsEnabled:H(),soundsVolume:K(),showPlayerNames:Y()},game:Xl.get(e),disconnect:Sn.disconnect,connect:x,unload:()=>{re.forEach((e=>{clearInterval(e)})),de&&clearTimeout(de),ce&&ce.stop()}}}var zo=e({name:"game",components:{PuzzleStatus:xt,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,InfoOverlay:Ot,ConnectionOverlay:zn,HelpOverlay:Nn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,cuttingPuzzle:!0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},game:null,previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await So(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setPuzzleCut:()=>{this.cuttingPuzzle=!1},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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 To={id:"game"},Io={key:1,class:"overlay"},Mo=n("div",{class:"overlay-content"},[n("div",null,"⏳ Cutting puzzle, please wait... ⏳")],-1),Eo={class:"menu"},Do={class:"tabs"},No=i("🧩 Puzzles");zo.render=function(e,i,r,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("info-overlay"),y=a("help-overlay"),f=a("connection-overlay"),v=a("puzzle-status"),w=a("router-link"),b=a("scores");return s(),t("div",To,[p(n(g,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),e.g.game?p((s(),t(m,{key:0,onBgclick:i[4]||(i[4]=t=>e.toggle("info",!0)),game:e.g.game},null,8,["game"])),[[x,"info"===e.overlay]]):o("",!0),p(n(y,{onBgclick:i[5]||(i[5]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),e.cuttingPuzzle?(s(),t("div",Io,[Mo])):o("",!0),n(f,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(v,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",Eo,[n("div",Do,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[No])),_:1}),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("info",!0))},"ℹ️ Info"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("help",!0))},"⌨️ Hotkeys")])]),n(b,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var _o=e({name:"replay",components:{PuzzleStatus:xt,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,InfoOverlay:Ot,HelpOverlay:Nn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,cuttingPuzzle:!0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},game:null,previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await So(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"replay",this.$el,{setPuzzleCut:()=>{this.cuttingPuzzle=!1},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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 Vo={id:"replay"},Oo={key:1,class:"overlay"},Bo=n("div",{class:"overlay-content"},[n("div",null,"⏳ Cutting puzzle, please wait... ⏳")],-1),Uo={class:"menu"},Ro={class:"tabs"},$o=i("🧩 Puzzles");_o.render=function(e,i,d,c,u,g){const h=a("settings-overlay"),m=a("preview-overlay"),y=a("info-overlay"),f=a("help-overlay"),v=a("puzzle-status"),w=a("router-link"),b=a("scores");return s(),t("div",Vo,[p(n(h,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(m,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),e.g.game?p((s(),t(y,{key:0,onBgclick:i[4]||(i[4]=t=>e.toggle("info",!0)),game:e.g.game},null,8,["game"])),[[x,"info"===e.overlay]]):o("",!0),p(n(f,{onBgclick:i[5]||(i[5]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),e.cuttingPuzzle?(s(),t("div",Oo,[Bo])):o("",!0),n(v,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:l((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[8]||(i[8]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",Uo,[n("div",Ro,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[$o])),_:1}),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[11]||(i[11]=t=>e.toggle("info",!0))},"ℹ️ Info"),n("div",{class:"opener",onClick:i[12]||(i[12]=t=>e.toggle("help",!0))},"⌨️ Hotkeys")])]),n(b,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=k({history:P(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:it},{name:"game",path:"/g/:id",component:zo},{name:"replay",path:"/replay/:id",component:_o}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const l=A(S);l.config.globalProperties.$config=t,l.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=X.uniqId(),localStorage.setItem("ID",e)),e}(),l.use(n),l.mount("#app")})(); diff --git a/build/public/index.html b/build/public/index.html index dadb3e4..3df4956 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,7 +4,7 @@ 🧩 jigsaw.hyottoko.club - + diff --git a/build/server/main.js b/build/server/main.js index 7e7a143..82e7590 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -626,10 +626,12 @@ function getPieceCount(gameId) { return GAMES[gameId].puzzle.tiles.length; } function getImageUrl(gameId) { - return GAMES[gameId].puzzle.info.imageUrl; -} -function setImageUrl(gameId, imageUrl) { - GAMES[gameId].puzzle.info.imageUrl = imageUrl; + const imageUrl = GAMES[gameId].puzzle.info.image?.url + || GAMES[gameId].puzzle.info.imageUrl; + if (!imageUrl) { + throw new Error('[2021-07-11] no image url set'); + } + return imageUrl; } function getScoreMode(gameId) { return GAMES[gameId].scoreMode; @@ -1243,7 +1245,6 @@ var GameCommon = { getFinishedPiecesCount, getPieceCount, getImageUrl, - setImageUrl, get: get$1, getAllGames, getPlayerBgColor, @@ -1358,6 +1359,8 @@ var GameLog = { exists, log: _log, get, + filename, + idxname, }; const log$4 = logger('Images.ts'); @@ -1433,7 +1436,6 @@ const imageFromDb = (db, imageId) => { return { id: i.id, filename: i.filename, - file: `${UPLOAD_DIR}/${i.filename}`, url: `${UPLOAD_URL}/${encodeURIComponent(i.filename)}`, title: i.title, tags: getTags(db, i.id), @@ -1472,7 +1474,6 @@ inner join images i on i.id = ixc.image_id ${where.sql}; return images.map(i => ({ id: i.id, filename: i.filename, - file: `${UPLOAD_DIR}/${i.filename}`, url: `${UPLOAD_URL}/${encodeURIComponent(i.filename)}`, title: i.title, tags: getTags(db, i.id), @@ -1490,7 +1491,6 @@ const allImagesFromDisk = (tags, sort) => { .map(f => ({ id: 0, filename: f, - file: `${UPLOAD_DIR}/${f}`, url: `${UPLOAD_URL}/${encodeURIComponent(f)}`, title: f.replace(/\.[a-z]+$/, ''), tags: [], @@ -1501,12 +1501,12 @@ const allImagesFromDisk = (tags, sort) => { switch (sort) { case 'alpha_asc': images = images.sort((a, b) => { - return a.file > b.file ? 1 : -1; + return a.filename > b.filename ? 1 : -1; }); break; case 'alpha_desc': images = images.sort((a, b) => { - return a.file < b.file ? 1 : -1; + return a.filename < b.filename ? 1 : -1; }); break; case 'date_asc': @@ -1552,7 +1552,7 @@ var Images = { // final resized version of the puzzle image const TILE_SIZE = 64; async function createPuzzle(rng, targetTiles, image, ts, shapeMode) { - const imagePath = image.file; + const imagePath = `${UPLOAD_DIR}/${image.filename}`; const imageUrl = image.url; // determine puzzle information from the image dimensions const dim = await Images.getDimensions(imagePath); @@ -1651,6 +1651,7 @@ async function createPuzzle(rng, targetTiles, image, ts, shapeMode) { // information that was used to create the puzzle targetTiles: targetTiles, imageUrl, + image: image, width: info.width, height: info.height, tileSize: info.tileSize, @@ -2114,7 +2115,8 @@ app.get('/api/replay-data', async (req, res) => { let game = null; if (offset === 0) { // also need the game - game = await Game.createGameObject(gameId, log[0][2], log[0][3], log[0][4], log[0][5], log[0][6], log[0][7]); + game = await Game.createGameObject(gameId, log[0][2], log[0][3], // must be ImageInfo + log[0][4], log[0][5], log[0][6], log[0][7]); } res.send({ log, game: game ? Util.encodeGame(game) : null }); }); diff --git a/scripts/fix_games_image_info.ts b/scripts/fix_games_image_info.ts new file mode 100644 index 0000000..559b512 --- /dev/null +++ b/scripts/fix_games_image_info.ts @@ -0,0 +1,90 @@ +import GameCommon from '../src/common/GameCommon' +import GameLog from '../src/server/GameLog' +import { Game } from '../src/common/Types' +import { logger } from '../src/common/Util' +import { DB_FILE, DB_PATCHES_DIR, UPLOAD_DIR } from '../src/server/Dirs' +import Db from '../src/server/Db' +import GameStorage from '../src/server/GameStorage' +import fs from 'fs' + +const log = logger('fix_games_image_info.ts') + +import Images from '../src/server/Images' + +console.log(DB_FILE) + +const db = new Db(DB_FILE, DB_PATCHES_DIR) +db.patch(true) + +// ;(async () => { +// let images = db.getMany('images') +// for (let image of images) { +// console.log(image.filename) +// let dim = await Images.getDimensions(`${UPLOAD_DIR}/${image.filename}`) +// console.log(await Images.getDimensions(`${UPLOAD_DIR}/${image.filename}`)) +// image.width = dim.w +// image.height = dim.h +// db.upsert('images', image, { id: image.id }) +// } +// })() + +function fixOne(gameId: string) { + let g = GameCommon.get(gameId) + if (!g) { + return + } + + if (!g.puzzle.info.image && g.puzzle.info.imageUrl) { + log.log('game id: ', gameId) + const parts = g.puzzle.info.imageUrl.split('/') + const fileName = parts[parts.length - 1] + const imageRow = db.get('images', {filename: fileName}) + if (!imageRow) { + return + } + + g.puzzle.info.image = Images.imageFromDb(db, imageRow.id) + + log.log(g.puzzle.info.image.title, imageRow.id) + + GameStorage.persistGame(gameId) + } else if (g.puzzle.info.image?.id) { + const imageId = g.puzzle.info.image.id + + g.puzzle.info.image = Images.imageFromDb(db, imageId) + + log.log(g.puzzle.info.image.title, imageId) + + GameStorage.persistGame(gameId) + } + + // fix log + const file = GameLog.filename(gameId, 0) + if (!fs.existsSync(file)) { + return + } + + const lines = fs.readFileSync(file, 'utf-8').split("\n") + const l = lines.filter(line => !!line).map(line => { + return JSON.parse(`[${line}]`) + }) + if (l && l[0] && !l[0][3].id) { + log.log(l[0][3]) + l[0][3] = g.puzzle.info.image + const newlines = l.map(ll => { + return JSON.stringify(ll).slice(1, -1) + }).join("\n") + "\n" + console.log(g.puzzle.info.image) + // process.exit(0) + fs.writeFileSync(file, newlines) + } +} + +function fix() { + GameStorage.loadGames() + GameCommon.getAllGames().forEach((game: Game) => { + fixOne(game.id) + }) +} + +fix() diff --git a/scripts/fix_image.ts b/scripts/fix_image.ts deleted file mode 100644 index 366ed1f..0000000 --- a/scripts/fix_image.ts +++ /dev/null @@ -1,23 +0,0 @@ -import GameCommon from '../src/common/GameCommon' -import { logger } from '../src/common/Util' -import GameStorage from '../src/server/GameStorage' - -const log = logger('fix_image.js') - -function fix(gameId) { - GameStorage.loadGame(gameId) - let changed = false - - let imgUrl = GameCommon.getImageUrl(gameId) - if (imgUrl.match(/^\/example-images\//)) { - log.log(`found bad imgUrl: ${imgUrl}`) - imgUrl = imgUrl.replace(/^\/example-images\//, '/uploads/') - GameCommon.setImageUrl(gameId, imgUrl) - changed = true - } - if (changed) { - GameStorage.persistGame(gameId) - } -} - -fix(process.argv[2]) diff --git a/src/common/GameCommon.ts b/src/common/GameCommon.ts index e2f18eb..7ce4599 100644 --- a/src/common/GameCommon.ts +++ b/src/common/GameCommon.ts @@ -162,11 +162,12 @@ function getPieceCount(gameId: string): number { } function getImageUrl(gameId: string): string { - return GAMES[gameId].puzzle.info.imageUrl -} - -function setImageUrl(gameId: string, imageUrl: string): void { - GAMES[gameId].puzzle.info.imageUrl = imageUrl + const imageUrl = GAMES[gameId].puzzle.info.image?.url + || GAMES[gameId].puzzle.info.imageUrl + if (!imageUrl) { + throw new Error('[2021-07-11] no image url set') + } + return imageUrl } function getScoreMode(gameId: string): ScoreMode { @@ -895,7 +896,6 @@ export default { getFinishedPiecesCount, getPieceCount, getImageUrl, - setImageUrl, get, getAllGames, getPlayerBgColor, diff --git a/src/common/Types.ts b/src/common/Types.ts index 261e210..71840bf 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -93,7 +93,7 @@ export interface Image { export interface GameSettings { tiles: number - image: Image + image: ImageInfo scoreMode: ScoreMode shapeMode: ShapeMode snapMode: SnapMode @@ -152,11 +152,23 @@ export interface PieceChange { group?: number } +export interface ImageInfo +{ + id: number + filename: string + url: string + title: string + tags: Tag[] + created: Timestamp + width: number + height: number +} + export interface PuzzleInfo { table: PuzzleTable targetTiles: number - imageUrl: string - imageTitle: string + imageUrl?: string // deprecated, use image.url instead + image?: ImageInfo width: number height: number diff --git a/src/frontend/components/InfoOverlay.vue b/src/frontend/components/InfoOverlay.vue index 7f76d2d..efc11fe 100644 --- a/src/frontend/components/InfoOverlay.vue +++ b/src/frontend/components/InfoOverlay.vue @@ -6,19 +6,19 @@ Image Title: - {{game.puzzle.info.imageTitle}} + {{game.puzzle.info.image.title}} Snap Mode: - {{scoreMode[0]}} + {{scoreMode[0]}} Shape Mode: - {{shapeMode[0]}} + {{shapeMode[0]}} Score Mode: - {{snapMode[0]}} + {{snapMode[0]}}
diff --git a/src/server/Game.ts b/src/server/Game.ts index 2c4c123..0aef26f 100644 --- a/src/server/Game.ts +++ b/src/server/Game.ts @@ -1,9 +1,9 @@ import GameCommon from './../common/GameCommon' -import { Change, Game, Input, ScoreMode, ShapeMode, SnapMode, Timestamp } from './../common/Types' +import { Change, Game, Input, ScoreMode, ShapeMode, SnapMode,ImageInfo, Timestamp } from './../common/Types' import Util, { logger } from './../common/Util' import { Rng } from './../common/Rng' import GameLog from './GameLog' -import { createPuzzle, PuzzleCreationImageInfo } from './Puzzle' +import { createPuzzle } from './Puzzle' import Protocol from './../common/Protocol' import GameStorage from './GameStorage' @@ -12,7 +12,7 @@ const log = logger('Game.ts') async function createGameObject( gameId: string, targetTiles: number, - image: PuzzleCreationImageInfo, + image: ImageInfo, ts: Timestamp, scoreMode: ScoreMode, shapeMode: ShapeMode, @@ -35,7 +35,7 @@ async function createGameObject( async function createGame( gameId: string, targetTiles: number, - image: PuzzleCreationImageInfo, + image: ImageInfo, ts: Timestamp, scoreMode: ScoreMode, shapeMode: ShapeMode, diff --git a/src/server/GameLog.ts b/src/server/GameLog.ts index c6a14ad..a8b2050 100644 --- a/src/server/GameLog.ts +++ b/src/server/GameLog.ts @@ -100,4 +100,6 @@ export default { exists, log: _log, get, + filename, + idxname, } diff --git a/src/server/Images.ts b/src/server/Images.ts index 8cda19a..2a2c095 100644 --- a/src/server/Images.ts +++ b/src/server/Images.ts @@ -7,30 +7,10 @@ import {UPLOAD_DIR, UPLOAD_URL} from './Dirs' import Db, { OrderBy, WhereRaw } from './Db' import { Dim } from '../common/Geometry' import { logger } from '../common/Util' -import { Timestamp } from '../common/Types' +import { Tag, ImageInfo } from '../common/Types' const log = logger('Images.ts') -interface Tag -{ - id: number - slug: string - title: string -} - -interface ImageInfo -{ - id: number - filename: string - file: string - url: string - title: string - tags: Tag[] - created: Timestamp - width: number - height: number -} - const resizeImage = async (filename: string): Promise => { if (!filename.toLowerCase().match(/\.(jpe?g|webp|png)$/)) { return @@ -106,7 +86,6 @@ const imageFromDb = (db: Db, imageId: number): ImageInfo => { return { id: i.id, filename: i.filename, - file: `${UPLOAD_DIR}/${i.filename}`, url: `${UPLOAD_URL}/${encodeURIComponent(i.filename)}`, title: i.title, tags: getTags(db, i.id), @@ -152,7 +131,6 @@ inner join images i on i.id = ixc.image_id ${where.sql}; return images.map(i => ({ id: i.id as number, filename: i.filename, - file: `${UPLOAD_DIR}/${i.filename}`, url: `${UPLOAD_URL}/${encodeURIComponent(i.filename)}`, title: i.title, tags: getTags(db, i.id), @@ -174,7 +152,6 @@ const allImagesFromDisk = ( .map(f => ({ id: 0, filename: f, - file: `${UPLOAD_DIR}/${f}`, url: `${UPLOAD_URL}/${encodeURIComponent(f)}`, title: f.replace(/\.[a-z]+$/, ''), tags: [] as Tag[], @@ -186,13 +163,13 @@ const allImagesFromDisk = ( switch (sort) { case 'alpha_asc': images = images.sort((a, b) => { - return a.file > b.file ? 1 : -1 + return a.filename > b.filename ? 1 : -1 }) break; case 'alpha_desc': images = images.sort((a, b) => { - return a.file < b.file ? 1 : -1 + return a.filename < b.filename ? 1 : -1 }) break; diff --git a/src/server/Puzzle.ts b/src/server/Puzzle.ts index bd0ad98..581578a 100644 --- a/src/server/Puzzle.ts +++ b/src/server/Puzzle.ts @@ -1,14 +1,9 @@ import Util from './../common/Util' import { Rng } from './../common/Rng' import Images from './Images' -import { EncodedPiece, EncodedPieceShape, PieceShape, Puzzle, ShapeMode } from '../common/Types' +import { EncodedPiece, EncodedPieceShape, PieceShape, Puzzle, ShapeMode, ImageInfo } from '../common/Types' import { Dim, Point } from '../common/Geometry' - -export interface PuzzleCreationImageInfo { - file: string - url: string - title: string -} +import { UPLOAD_DIR } from './Dirs' export interface PuzzleCreationInfo { width: number @@ -28,11 +23,11 @@ const TILE_SIZE = 64 async function createPuzzle( rng: Rng, targetTiles: number, - image: PuzzleCreationImageInfo, + image: ImageInfo, ts: number, shapeMode: ShapeMode ): Promise { - const imagePath = image.file + const imagePath = `${UPLOAD_DIR}/${image.filename}` const imageUrl = image.url // determine puzzle information from the image dimensions @@ -140,8 +135,8 @@ async function createPuzzle( }, // information that was used to create the puzzle targetTiles: targetTiles, - imageUrl, - imageTitle: image.title || '', + imageUrl, // todo: remove + image: image, width: info.width, // actual puzzle width (same as bitmap.width) height: info.height, // actual puzzle height (same as bitmap.height) diff --git a/src/server/main.ts b/src/server/main.ts index 119bf9a..cbed199 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -19,7 +19,7 @@ import { UPLOAD_DIR, } from './Dirs' import GameCommon from '../common/GameCommon' -import { ServerEvent, Game as GameType, GameSettings, ScoreMode, ShapeMode, SnapMode } from '../common/Types' +import { ServerEvent, Game as GameType, GameSettings } from '../common/Types' import GameStorage from './GameStorage' import Db from './Db' @@ -87,7 +87,7 @@ app.get('/api/replay-data', async (req, res): Promise => { game = await Game.createGameObject( gameId, log[0][2], - log[0][3], + log[0][3], // must be ImageInfo log[0][4], log[0][5], log[0][6], From bbcfd4200878f72402734b66e5dd8298374cfb13 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sun, 11 Jul 2021 17:08:18 +0200 Subject: [PATCH 32/45] extract event adapter to own file --- build/public/assets/index.90925b20.js | 1 + build/public/assets/index.93936dee.js | 1 - build/public/index.html | 2 +- build/server/main.js | 4 + src/common/Protocol.ts | 6 + src/frontend/EventAdapter.ts | 177 ++++++++++++++++++++++++ src/frontend/game.ts | 188 ++------------------------ 7 files changed, 202 insertions(+), 177 deletions(-) create mode 100644 build/public/assets/index.90925b20.js delete mode 100644 build/public/assets/index.93936dee.js create mode 100644 src/frontend/EventAdapter.ts diff --git a/build/public/assets/index.90925b20.js b/build/public/assets/index.90925b20.js new file mode 100644 index 0000000..940cbdd --- /dev/null +++ b/build/public/assets/index.90925b20.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as C,s as x,u as k,x as P,y as A}from"./vendor.684f7bc8.js";var S=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const z={id:"app"},T={key:0,class:"nav"},I=i("Games overview"),M=i("New game");S.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",z,[e.showNav?(s(),t("ul",T,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[M])),_:1})])])):l("",!0),n(g)])};const E=864e5,D=e=>{const t=Math.floor(e/E);e%=E;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 N=1,_=1e3,V=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>D(t-e),B=D,U=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||V();return`${n} ${O(o,l)}`}}});const R={class:"game-info-text"},$=n("br",null,null,-1),G=n("br",null,null,-1),L=n("br",null,null,-1),F=i(" ↪️ Watch replay ");U.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),$,i(" 👥 "+r(e.game.players),1),G,i(" "+r(e.time(e.game.started,e.game.finished)),1),L])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[F])),_:1},8,["to"])):l("",!0)],4)};var j=e({components:{GameTeaser:U},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 W=n("h1",null,"Running games",-1),K=n("h1",null,"Finished games",-1);j.render=function(e,o,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(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)),K,(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 H=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});H.render=function(e,o,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y=e({name:"image-library",components:{ImageTeaser:H},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});Y.render=function(e,n,o,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])};class q{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new q(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const Q=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},Z=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=Q(o.getHours(),"00"),a=Q(o.getMinutes(),"00"),s=Q(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var X={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",q.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode,e.shapeMode,e.snapMode]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:q.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const J={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};J.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var ee=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const te=m();y("data-v-a4fa5e7e");const ne={key:0,class:"autocomplete"};f();const oe=te(((e,o,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ne,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));ee.render=oe,ee.__scopeId="data-v-a4fa5e7e";const le=Z("NewImageDialog.vue");var ae=e({name:"new-image-dialog",components:{ResponsiveImage:J,TagsInput:ee},props:{autocompleteTags:{type:Function},uploadProgress:{type:Number},uploading:{type:String}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{uploadProgressPercent(){return this.uploadProgress?Math.round(100*this.uploadProgress):0},canPostToGallery(){return!this.uploading&&!(!this.previewUrl||!this.file)},canSetupGameClick(){return!this.uploading&&!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){le.info("onDragleave"),this.droppable=!1}}});const se=n("div",{class:"drop-target"},null,-1),ie={key:0,class:"has-image"},re={key:1},de={class:"upload"},ce=n("span",{class:"btn"},"Upload File",-1),ue={class:"area-settings"},pe=n("td",null,[n("label",null,"Title")],-1),ge=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),he=n("td",null,[n("label",null,"Tags")],-1),me={class:"area-buttons"},ye=i("🖼️ Post to gallery"),fe=i("🧩 Post to gallery "),ve=n("br",null,null,-1),we=i(" + set up game");ae.render=function(e,o,l,c,h,m){const y=a("responsive-image"),f=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[se,e.previewUrl?(s(),t("div",ie,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(y,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",re,[n("label",de,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ce])]))],34),n("div",ue,[n("table",null,[n("tr",null,[pe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),ge,n("tr",null,[he,n("td",null,[n(f,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",me,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},["postToGallery"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[ye],64))],8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},["setupGame"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[fe,ve,we],64))],8,["disabled"])])])])};var be=e({name:"edit-image-dialog",components:{ResponsiveImage:J,TagsInput:ee},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Ce={class:"area-image"},xe={class:"has-image"},ke={class:"area-settings"},Pe=n("td",null,[n("label",null,"Title")],-1),Ae=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Se=n("td",null,[n("label",null,"Tags")],-1),ze={class:"area-buttons"};var Te,Ie,Me,Ee,De,Ne,_e,Ve;be.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Ce,[n("div",xe,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ke,[n("table",null,[n("tr",null,[Pe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ae,n("tr",null,[Se,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",ze,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])},(Ie=Te||(Te={}))[Ie.Flat=0]="Flat",Ie[Ie.Out=1]="Out",Ie[Ie.In=-1]="In",(Ee=Me||(Me={}))[Ee.FINAL=0]="FINAL",Ee[Ee.ANY=1]="ANY",(Ne=De||(De={}))[Ne.NORMAL=0]="NORMAL",Ne[Ne.ANY=1]="ANY",Ne[Ne.FLAT=2]="FLAT",(Ve=_e||(_e={}))[Ve.NORMAL=0]="NORMAL",Ve[Ve.REAL=1]="REAL";var Oe=e({name:"new-game-dialog",components:{ResponsiveImage:J},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Me.ANY,shapeMode:De.NORMAL,snapMode:_e.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Be={class:"area-image"},Ue={class:"has-image"},Re={key:0,class:"image-title"},$e={key:0,class:"image-title-title"},Ge={key:1,class:"image-title-dim"},Le={class:"area-settings"},Fe=n("td",null,[n("label",null,"Pieces")],-1),je=n("td",null,[n("label",null,"Scoring: ")],-1),We=i(" Any (Score when pieces are connected to each other or on final location)"),Ke=n("br",null,null,-1),He=i(" Final (Score when pieces are put to their final location)"),Ye=n("td",null,[n("label",null,"Shapes: ")],-1),qe=i(" Normal"),Qe=n("br",null,null,-1),Ze=i(" Any (flat pieces can occur anywhere)"),Xe=n("br",null,null,-1),Je=i(" Flat (all pieces flat on all sides)"),et=n("td",null,[n("label",null,"Snapping: ")],-1),tt=i(" Normal (pieces snap to final destination and to each other)"),nt=n("br",null,null,-1),ot=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),lt={class:"area-buttons"};Oe.render=function(e,o,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",Be,[n("div",Ue,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title||e.image.width||e.image.height?(s(),t("div",Re,[e.image.title?(s(),t("span",$e,'"'+r(e.image.title)+'"',1)):l("",!0),e.image.width||e.image.height?(s(),t("span",Ge,"("+r(e.image.width)+" ✕ "+r(e.image.height)+")",1)):l("",!0)])):l("",!0)]),n("div",Le,[n("table",null,[n("tr",null,[Fe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[je,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),We]),Ke,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),He])])]),n("tr",null,[Ye,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),qe]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ze]),Xe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),Je])])]),n("tr",null,[et,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),tt]),nt,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),ot])])])])]),n("div",lt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};const at=async(e,t,n)=>new Promise(((o,l)=>{const a=new window.XMLHttpRequest;a.open(e,t,!0),a.withCredentials=!0;for(const e in n.headers||{})a.setRequestHeader(e,n.headers[e]);a.addEventListener("load",(function(e){o({status:this.status,text:this.responseText,json:async()=>JSON.parse(this.responseText)})})),a.addEventListener("error",(function(e){l(new Error("xhr error"))})),a.upload&&n.onUploadProgress&&a.upload.addEventListener("progress",(function(e){n.onUploadProgress&&n.onUploadProgress(e)})),a.send(n.body)}));var st=(e,t)=>at("post",e,t),it=e({components:{ImageLibrary:Y,NewImageDialog:ae,EditImageDialog:be,NewGameDialog:Oe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:"",uploading:"",uploadProgress:0}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${X.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){this.uploadProgress=0;const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await st("/api/upload",{body:t,onUploadProgress:e=>{this.uploadProgress=e.loaded/e.total}});return this.uploadProgress=1,await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){this.uploading="postToGallery",await this.uploadImage(e),this.uploading="",this.dialog="",await this.loadImages()},async setupGameClick(e){this.uploading="setupGame";const t=await this.uploadImage(e);this.uploading="",this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const rt={class:"upload-image-teaser"},dt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),ct={key:0},ut=i(" Tags: "),pt=i(" Sort by: "),gt=n("option",{value:"date_desc"},"Newest first",-1),ht=n("option",{value:"date_asc"},"Oldest first",-1),mt=n("option",{value:"alpha_asc"},"A-Z",-1),yt=n("option",{value:"alpha_desc"},"Z-A",-1);it.render=function(e,o,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[n("div",rt,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),dt]),n("div",null,[e.tags.length>0?(s(),t("label",ct,[ut,(s(!0),t(d,null,c(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[pt,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[gt,ht,mt,yt],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),uploadProgress:e.uploadProgress,uploading:e.uploading,onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","uploadProgress","uploading","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var ft=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"},wt=n("div",null,"Scores",-1),bt=n("td",null,"⚡",-1),Ct=n("td",null,"💤",-1);ft.render=function(e,o,l,a,i,u){return s(),t("div",vt,[wt,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}},[Ct,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var xt=e({name:"puzzle-status",props:{finished:{type:Boolean,required:!0},duration:{type:Number,required:!0},piecesDone:{type:Number,required:!0},piecesTotal:{type:Number,required:!0}},computed:{icon(){return this.finished?"🏁":"⏳"},durationStr(){return B(this.duration)}}});const kt={class:"timer"};xt.render=function(e,o,l,a,i,d){return s(),t("div",kt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var Pt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const At=m();y("data-v-4d56fc17");const St=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),It=n("td",null,[n("label",null,"Sounds: ")],-1),Mt=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Et={class:"sound-volume"},Dt=n("td",null,[n("label",null,"Show player names: ")],-1);f();const Nt=At(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[10]||(o[10]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[9]||(o[9]=u((()=>{}),["stop"]))},[n("tr",null,[St,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[zt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[Tt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[It,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[C,e.modelValue.soundsEnabled]])])]),n("tr",null,[Mt,n("td",Et,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])]),n("tr",null,[Dt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[8]||(o[8]=t=>e.modelValue.showPlayerNames=t)},null,512),[[C,e.modelValue.showPlayerNames]])])])])]))));Pt.render=Nt,Pt.__scopeId="data-v-4d56fc17";var _t=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const Vt={class:"preview"};_t.render=function(e,o,l,a,i,r){return s(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",Vt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Ot=e({name:"help-overlay",emits:{bgclick:null},props:{game:{type:Object,required:!0}},computed:{scoreMode(){switch(this.game.scoreMode){case Me.ANY:return["Any","Score when pieces are connected to each other or on final location"];case Me.FINAL:default:return["Final","Score when pieces are put to their final location"]}},shapeMode(){switch(this.game.shapeMode){case De.FLAT:return["Flat","all pieces flat on all sides"];case De.ANY:return["Any","flat pieces can occur anywhere"];case De.NORMAL:default:return["Normal",""]}},snapMode(){switch(this.game.snapMode){case _e.REAL:return["Real","pieces snap only to corners, already snapped pieces and to each other"];case _e.NORMAL:default:return["Normal","pieces snap to final destination and to each other"]}}}});const Bt=n("tr",null,[n("td",{colspan:"2"},"Info about this puzzle")],-1),Ut=n("td",null,"Image Title: ",-1),Rt=n("td",null,"Snap Mode: ",-1),$t=n("td",null,"Shape Mode: ",-1),Gt=n("td",null,"Score Mode: ",-1);Ot.render=function(e,o,l,a,i,d){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]=u((()=>{}),["stop"]))},[Bt,n("tr",null,[Ut,n("td",null,r(e.game.puzzle.info.image.title),1)]),n("tr",null,[Rt,n("td",null,[n("span",{title:e.snapMode[1]},r(e.scoreMode[0]),9,["title"])])]),n("tr",null,[$t,n("td",null,[n("span",{title:e.snapMode[1]},r(e.shapeMode[0]),9,["title"])])]),n("tr",null,[Gt,n("td",null,[n("span",{title:e.snapMode[1]},r(e.snapMode[0]),9,["title"])])])])])};var Lt=1,Ft=4,jt=2,Wt=3,Kt=2,Ht=4,Yt=3,qt=9,Qt=1,Zt=2,Xt=3,Jt=4,en=5,tn=6,nn=7,on=8,ln=10,an=11,sn=12,rn=13,dn=14,cn=15,un=16,pn=17,gn=18,hn=1,mn=2,yn=3;const fn=Z("Communication.js");let vn,wn=[],bn=e=>{wn.push(e)},Cn=[],xn=e=>{Cn.push(e)};let kn=0;const Pn=e=>{kn!==e&&(kn=e,xn(e))};function An(e){if(2===kn)try{vn.send(JSON.stringify(e))}catch(t){fn.info("unable to send message.. maybe because ws is invalid?")}}let Sn,zn;var Tn={connect:function(e,t,n){return Sn=0,zn={},Pn(3),new Promise((o=>{vn=new WebSocket(e,n+"|"+t),vn.onopen=()=>{Pn(2),An([Wt])},vn.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===Ft){const e=t[1];o(e)}else{if(l!==Lt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&zn[o])return void delete zn[o];bn(t)}}},vn.onerror=()=>{throw Pn(1),"[ 2021-05-15 onerror ]"},vn.onclose=e=>{4e3===e.code||1001===e.code?Pn(4):Pn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await fetch(`/api/replay-data${X.asQueryArgs(n)}`);return await o.json()},disconnect:function(){vn&&vn.close(4e3),Sn=0,zn={}},sendClientEvent:function(e){Sn++,zn[Sn]=e,An([jt,Sn,zn[Sn]])},onServerChange:function(e){bn=e;for(const t of wn)bn(t);wn=[]},onConnectionStateChange:function(e){xn=e;for(const t of Cn)xn(t);Cn=[]},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},In=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Tn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Tn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const Mn={key:0,class:"overlay connection-lost"},En={key:0,class:"overlay-content"},Dn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Nn={key:1,class:"overlay-content"},_n=n("div",null,"Connecting...",-1);In.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",Mn,[e.lostConnection?(s(),t("div",En,[Dn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",Nn,[_n])):l("",!0)])):l("",!0)};var Vn=e({name:"help-overlay",emits:{bgclick:null}});const On=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),Bn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),Un=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),Rn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),$n=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Gn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Ln=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),Fn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),jn=n("tr",null,[n("td",null,"🎯 Center puzzle in screen:"),n("td",null,[n("div",null,[n("kbd",null,"C")])])],-1),Wn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Kn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Hn=n("tr",null,[n("td",null,"👤 Toggle player names:"),n("td",null,[n("div",null,[n("kbd",null,"N")])])],-1),Yn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),qn=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),Qn=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),Zn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);Vn.render=function(e,o,l,a,i,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]=u((()=>{}),["stop"]))},[On,Bn,Un,Rn,$n,Gn,Ln,Fn,jn,Wn,Kn,Hn,Yn,qn,Qn,Zn])])};var Xn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Jn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),eo=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),to=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),no=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function oo(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var lo={createCanvas:oo,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=oo(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=oo(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(),o}};const ao=Z("Debug.js");let so=0,io=0;var ro=e=>{so=performance.now(),io=e},co=e=>{const t=performance.now(),n=t-so;n>io&&ao.log(e+": "+n),so=t};function uo(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function po(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var go={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:uo,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:po,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 uo(po(e),po(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const ho=Z("PuzzleGraphics.js");function mo(e,t){const n=X.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var yo={loadPuzzleBitmaps:async function(e){const t=await lo.loadImageToBitmap(e.info.imageUrl),n=await lo.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){ho.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=go.pointAdd(a,{x:o,y:0}),c=go.pointAdd(r,{x:0,y:o}),u=go.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;oX.decodePiece(fo[e].puzzle.tiles[t]),Do=(e,t)=>Eo(e,t).group,No=(e,t)=>{const n=fo[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},_o=(e,t)=>{const n=fo[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=fo[e].puzzle.info,o=X.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return go.pointAdd(o,l)},Vo=(e,t)=>Eo(e,t).pos,Oo=e=>{const t=Jo(e),n=el(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}},Bo=(e,t)=>{const n=Go(e),o=Eo(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Uo=(e,t)=>Eo(e,t).z,Ro=(e,t)=>{for(const n of fo[e].puzzle.tiles){const e=X.decodePiece(n);if(e.owner===t)return e.idx}return-1},$o=e=>fo[e].puzzle.info.tileDrawSize,Go=e=>fo[e].puzzle.info.tileSize,Lo=e=>fo[e].puzzle.data.maxGroup,Fo=e=>fo[e].puzzle.data.maxZ;function jo(e,t){const n=fo[e].puzzle.info,o=X.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Wo=(e,t,n)=>{for(const o of t)Mo(e,o,{z:n})},Ko=(e,t,n)=>{const o=Vo(e,t);Mo(e,t,{pos:go.pointAdd(o,n)})},Ho=(e,t,n)=>{const o=$o(e),l=Oo(e),a=n;for(const s of t){const t=Eo(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)Ko(e,s,a)},Yo=(e,t)=>Eo(e,t).owner,qo=(e,t)=>{for(const n of t)Mo(e,n,{owner:-1,z:1})},Qo=(e,t,n)=>{for(const o of t)Mo(e,o,{owner:n})};function Zo(e,t){const n=fo[e].puzzle.tiles,o=X.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=X.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const Xo=(e,t)=>{const n=wo(e,t);return n?n.points:0},Jo=e=>fo[e].puzzle.info.table.width,el=e=>fo[e].puzzle.info.table.height;var tl={setGame:function(e,t){fo[e]=t},exists:function(e){return!!fo[e]||!1},playerExists:Co,getActivePlayers:function(e,t){const n=t-30*_;return xo(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return xo(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Co(e,t)?To(e,t,{ts:n}):bo(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:zo,getPieceCount:ko,getImageUrl:function(e){var t;const n=(null==(t=fo[e].puzzle.info.image)?void 0:t.url)||fo[e].puzzle.info.imageUrl;if(!n)throw new Error("[2021-07-11] no image url set");return n},get:function(e){return fo[e]||null},getAllGames:function(){return Object.values(fo).sort(((e,t)=>So(e.id)===So(t.id)?t.puzzle.data.started-e.puzzle.data.started:So(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=wo(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=wo(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=wo(e,t);return n?n.name:null},getPlayerIndexById:vo,getPlayerIdByIndex:function(e,t){return fo[e].players.length>t?X.decodePlayer(fo[e].players[t]).id:null},changePlayer:To,setPlayer:bo,setPiece:function(e,t,n){fo[e].puzzle.tiles[t]=X.encodePiece(n)},setPuzzleData:function(e,t){fo[e].puzzle.data=t},getTableWidth:Jo,getTableHeight:el,getPuzzle:e=>fo[e].puzzle,getRng:e=>fo[e].rng.obj,getPuzzleWidth:e=>fo[e].puzzle.info.width,getPuzzleHeight:e=>fo[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return fo[e].puzzle.tiles.map(X.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Ro(e,t);return n<0?null:fo[e].puzzle.tiles[n]},getPieceDrawOffset:e=>fo[e].puzzle.info.tileDrawOffset,getPieceDrawSize:$o,getFinalPiecePos:_o,getStartTs:e=>fo[e].puzzle.data.started,getFinishTs:e=>fo[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=fo[e].puzzle,s=function(e,t){return t in fo[e].evtInfos?fo[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([hn,a.data])},d=t=>{i.push([mn,X.encodePiece(Eo(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=wo(e,t);n&&i.push([yn,X.encodePlayer(n)])},p=n[0];if(p===tn){const l=n[1];To(e,t,{bgcolor:l,ts:o}),u()}else if(p===nn){const l=n[1];To(e,t,{color:l,ts:o}),u()}else if(p===on){const l=`${n[1]}`.substr(0,16);To(e,t,{name:l,ts:o}),u()}else if(p===qt){const l=n[1],a=n[2],s=wo(e,t);if(s){const n=s.x-l,i=s.y-a;To(e,t,{ts:o,x:n,y:i}),u()}}else if(p===Qt){const l={x:n[1],y:n[2]};To(e,t,{d:1,ts:o}),u(),s._last_mouse_down=l;const a=((e,t)=>{const n=fo[e].puzzle.info,o=fo[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=Fo(e)+1;Io(e,{maxZ:n}),r();const o=Zo(e,a);Wo(e,o,Fo(e)),Qo(e,o,t),c(o)}s._last_mouse=l}else if(p===Xt){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)To(e,t,{x:l,y:a,ts:o}),u();else{const n=Ro(e,t);if(n>=0){To(e,t,{x:l,y:a,ts:o}),u();const r=Zo(e,n);let d=go.pointInBounds(i,Oo(e))&&go.pointInBounds(s._last_mouse_down,Oo(e));for(const t of r){const n=Bo(e,t);if(go.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Ho(e,r,{x:t,y:n}),c(r)}}else To(e,t,{ts:o}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===Zt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Ro(e,t);if(g>=0){const n=Zo(e,g);Qo(e,n,0),c(n);const s=Vo(e,g),i=_o(e,g);let h=!1;if(Ao(e)===_e.REAL){for(const t of n)if(No(e,t)){h=!0;break}}else h=!0;if(h&&go.pointDistance(i,s){const l=fo[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=Do(e,t),l=Do(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=Vo(e,t),s=go.pointAdd(Vo(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(go.pointDistance(a,s){const o=fo[e].puzzle.tiles,l=Do(e,t),a=Do(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(Io(e,{maxGroup:Lo(e)+1}),r(),s=Lo(e));if(Mo(e,t,{group:s}),d(t),Mo(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=X.decodePiece(r);i.includes(t.group)&&(Mo(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=Zo(e,t),((e,t)=>-1===Yo(e,t))(e,n))qo(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=Uo(e,o);t>n&&(n=t)}return n})(e,l);Wo(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of Zo(e,g)){const o=jo(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])){a=!0;break}}if(a&&Po(e)===Me.ANY){const n=Xo(e,t)+1;To(e,t,{d:p,ts:o,points:n}),u()}else To(e,t,{d:p,ts:o}),u();a&&Ao(e)===_e.REAL&&zo(e)===ko(e)&&(Io(e,{finished:o}),r()),a&&l&&l(t)}}else To(e,t,{d:p,ts:o}),u();s._last_mouse=i}else if(p===Jt){const l=n[1],a=n[2];To(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else if(p===en){const l=n[1],a=n[2];To(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else To(e,t,{ts:o}),u();return function(e,t,n){fo[e].evtInfos[t]=n}(e,t,s),i}};let nl=-10,ol=20,ll=2,al=15;class sl{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=nl+Math.random()*ol,this.vy=-1*(ll+Math.random()*al),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;ll=t/2,al=t-ll;const n=1/4*this.canvas.width/(t/2);nl=-n,ol=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new sl(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new sl(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{localStorage.setItem(e,t)},yl=e=>localStorage.getItem(e);var fl=(e,t)=>{ml(e,`${t}`)},vl=(e,t)=>{const n=yl(e);if(null===n)return t;const o=parseInt(n,10);return isNaN(o)?t:o},wl=(e,t)=>{ml(e,t?"1":"0")},bl=(e,t)=>{const n=yl(e);return null===n?t:"1"===n},Cl=(e,t)=>{ml(e,t)},xl=(e,t)=>{const n=yl(e);return null===n?t:n};const kl={"./grab.png":Jn,"./grab_mask.png":eo,"./hand.png":to,"./hand_mask.png":no},Pl={"./click.mp3":Xn},Al="replay";let Sl=!0,zl=!0;let Tl=!0;async function Il(e,t,n,o,l,a){void 0===window.DEBUG&&(window.DEBUG=!1);const s=Pl["./click.mp3"].default,i=new Audio(s),r=await lo.loadImageToBitmap(kl["./grab.png"].default),d=await lo.loadImageToBitmap(kl["./hand.png"].default),c=await lo.loadImageToBitmap(kl["./grab_mask.png"].default),u=await lo.loadImageToBitmap(kl["./hand_mask.png"].default),p=r.width,g=Math.round(p/2),h=r.height,m=Math.round(h/2),y={},f=async e=>{const t=e.color+" "+e.d;if(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(lo.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Tl=!0})),t}(l,lo.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};Tn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await Tn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let C=()=>0;const x=async()=>{if("play"===o){const o=await Tn.connect(n,e,t),l=X.decodeGame(o);tl.setGame(l.id,l),C=()=>V()}else{if(o!==Al)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=X.decodeGame(t.game);tl.setGame(n.id,n),w.lastRealTs=V(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,C=()=>w.lastGameTs}}Tl=!0};await x();const k=tl.getPieceDrawOffset(e),P=tl.getPieceDrawSize(e),A=tl.getPuzzleWidth(e),S=tl.getPuzzleHeight(e),z=tl.getTableWidth(e),T=tl.getTableHeight(e),I={x:(z-A)/2,y:(T-S)/2},M={w:A,h:S},E={w:P,h:P},D=await yo.loadPuzzleBitmaps(tl.getPuzzle(e)),_=new rl(v,tl.getRng(e));_.init();const O=v.getContext("2d");v.classList.add("loaded"),a.setPuzzleCut();const B=function(){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)},a=(e,t)=>{if(n==e)return!1;const l=1-n/e;return o(-t.x*l,-t.y*l),n=e,!0},s=o=>({x:o.x/n-e,y:o.y/n-t}),i=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),r=e=>({w:e.w*n,h:e.h*n}),d=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,reset:()=>{e=0,t=0,n=1},move:o,canZoom:e=>n!=l(e),zoom:(e,t)=>a(l(e),t),setZoom:a,worldToViewport:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:r,viewportToWorld:e=>{const{x:t,y:n}=s(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:s,viewportDimToWorld:e=>{const{w:t,h:n}=d(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:d}}(),U=()=>{B.reset(),B.move(-(z-v.width)/2,-(T-v.height)/2);const e=B.worldDimToViewport(M),t=v.width-40,n=v.height-40;if(e.w>t||e.h>n||e.w{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Qt,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Zt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([Xt,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Jt:en;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([ln]),o===Al&&("KeyI"===e.code&&v([rn]),"KeyO"===e.code&&v([dn]),"KeyP"===e.code&&v([sn])),"KeyF"===e.code&&v([pn]),"KeyG"===e.code&&v([gn]),"KeyM"===e.code&&v([an]),"KeyN"===e.code&&v([cn]),"KeyC"===e.code&&v([un]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});v([qt,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Jt,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([en,...e])}},setHotkeys:e=>{a=e}}}(v,window,B,o),$=tl.getImageUrl(e),G=()=>{const t=tl.getStartTs(e),n=tl.getFinishTs(e),o=C();a.setFinished(!!n),a.setDuration((n||o)-t)};G(),a.setPiecesDone(tl.getFinishedPiecesCount(e)),a.setPiecesTotal(tl.getPieceCount(e));const L=C();a.setActivePlayers(tl.getActivePlayers(e,L)),a.setIdlePlayers(tl.getIdlePlayers(e,L));const F=!!tl.getFinishTs(e);let j=F;const W=()=>j&&!F,K=()=>vl(dl,100),H=()=>bl(cl,!1),Y=()=>bl(hl,!0),q=()=>{const e=K();i.volume=e/100,i.play()},Q=()=>o===Al?xl(ul,"#222222"):tl.getPlayerBgColor(e,t)||xl(ul,"#222222"),Z=()=>o===Al?xl(pl,"#ffffff"):tl.getPlayerColor(e,t)||xl(pl,"#ffffff");let J="",ee="",te=!1;const ne=e=>{te=e;const[t,n]=e?[J,"grab"]:[ee,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},oe=e=>{J=lo.colorizedCanvas(r,c,e).toDataURL(),ee=lo.colorizedCanvas(d,u,e).toDataURL(),ne(te)};oe(Z());const le=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ae=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,le())},ie=()=>{w.paused=!w.paused,le()},re=[];let de;let ce;if("play"===o?re.push(setInterval((()=>{G()}),1e3)):o===Al&&le(),"play"===o)Tn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case yn:{const n=X.decodePlayer(a);n.id!==t&&(tl.setPlayer(e,n.id,n),Tl=!0)}break;case mn:{const t=X.decodePiece(a);tl.setPiece(e,t.idx,t),Tl=!0}break;case hn:tl.setPuzzleData(e,a),Tl=!0}j=!!tl.getFinishTs(e)}));else if(o===Al){const t=(t,n)=>{const o=t;if(o[0]===Kt){const t=o[1];return tl.addPlayer(e,t,n),!0}if(o[0]===Ht){const t=tl.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return tl.addPlayer(e,t,n),!0}if(o[0]===Yt){const t=tl.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return tl.handleInput(e,t,l,n),!0}return!1};let n=w.lastGameTs;const o=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=V();if(w.paused)return w.lastRealTs=l,void(de=setTimeout(o,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const o=w.log[w.logPointer],l=n+o[o.length-1],a=w.log[e],i=a[a.length-1],r=l+i;if(r>s){s+500*N{let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/o),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{R.createKeyEvents();for(const n of R.consumeAll())if("play"===o){const o=n[0];if(o===qt){const e=n[1],t=n[2],o=B.worldDimToViewport({w:e,h:t});Tl=!0,B.move(o.w,o.h)}else if(o===Xt){if(ue&&!tl.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-ue.x),l=Math.round(t.y-ue.y);Tl=!0,B.move(o,l),ue=t}}else if(o===nn)oe(n[1]);else if(o===Qt){const e={x:n[1],y:n[2]};ue=B.worldToViewport(e),ne(!0)}else if(o===Zt)ue=null,ne(!1);else if(o===Jt){const e={x:n[1],y:n[2]};Tl=!0,B.zoom("in",B.worldToViewport(e))}else if(o===en){const e={x:n[1],y:n[2]};Tl=!0,B.zoom("out",B.worldToViewport(e))}else o===ln?a.togglePreview():o===an?a.toggleSoundsEnabled():o===cn?a.togglePlayerNames():o===un?U():o===pn?(Sl=!Sl,Tl=!0):o===gn&&(zl=!zl,Tl=!0);const l=C();tl.handleInput(e,t,n,l,(e=>{H()&&q()})).length>0&&(Tl=!0),Tn.sendClientEvent(n)}else if(o===Al){const e=n[0];if(e===sn)ie();else if(e===dn)se();else if(e===rn)ae();else if(e===qt){const e=n[1],t=n[2];Tl=!0,B.move(e,t)}else if(e===Xt){if(ue){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),o=Math.round(t.x-ue.x),l=Math.round(t.y-ue.y);Tl=!0,B.move(o,l),ue=t}}else if(e===nn)oe(n[1]);else if(e===Qt){const e={x:n[1],y:n[2]};ue=B.worldToViewport(e),ne(!0)}else if(e===Zt)ue=null,ne(!1);else if(e===Jt){const e={x:n[1],y:n[2]};Tl=!0,B.zoom("in",B.worldToViewport(e))}else if(e===en){const e={x:n[1],y:n[2]};Tl=!0,B.zoom("out",B.worldToViewport(e))}else e===ln?a.togglePreview():e===an?a.toggleSoundsEnabled():e===cn?a.togglePlayerNames():e===un?U():e===pn?(Sl=!Sl,Tl=!0):e===gn&&(zl=!zl,Tl=!0)}j=!!tl.getFinishTs(e),W()&&(_.update(),Tl=!0)},render:async()=>{if(!Tl)return;const n=C();let l,s,i;window.DEBUG&&ro(0),O.fillStyle=Q(),O.fillRect(0,0,v.width,v.height),window.DEBUG&&co("clear done"),l=B.worldToViewportRaw(I),s=B.worldDimToViewportRaw(M),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&co("board done");const r=tl.getPiecesSortedByZIndex(e);window.DEBUG&&co("get tiles done"),s=B.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?Sl:zl)&&(i=D[e.idx],l=B.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&co("tiles done");const d=[];for(const a of tl.getActivePlayers(e,n))c=a,(o===Al||c.id!==t)&&(i=await f(a),l=B.worldToViewport(a),O.drawImage(i,l.x-g,l.y-m),Y()&&d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,o]of d)O.fillText(e,t,o);window.DEBUG&&co("players done"),a.setActivePlayers(tl.getActivePlayers(e,n)),a.setIdlePlayers(tl.getIdlePlayers(e,n)),a.setPiecesDone(tl.getFinishedPiecesCount(e)),window.DEBUG&&co("HUD done"),W()&&_.render(),Tl=!1}}),{setHotkeys:e=>{R.setHotkeys(e)},onBgChange:e=>{Cl(ul,e),R.addEvent([tn,e])},onColorChange:e=>{Cl(pl,e),R.addEvent([nn,e])},onNameChange:e=>{Cl(gl,e),R.addEvent([on,e])},onSoundsEnabledChange:e=>{wl(cl,e)},onSoundsVolumeChange:e=>{fl(dl,e),q()},onShowPlayerNamesChange:e=>{wl(hl,e)},replayOnSpeedUp:ae,replayOnSpeedDown:se,replayOnPauseToggle:ie,previewImageUrl:$,player:{background:Q(),color:Z(),name:o===Al?xl(gl,"anon"):tl.getPlayerName(e,t)||xl(gl,"anon"),soundsEnabled:H(),soundsVolume:K(),showPlayerNames:Y()},game:tl.get(e),disconnect:Tn.disconnect,connect:x,unload:()=>{re.forEach((e=>{clearInterval(e)})),de&&clearTimeout(de),ce&&ce.stop()}}}var Ml=e({name:"game",components:{PuzzleStatus:xt,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,InfoOverlay:Ot,ConnectionOverlay:In,HelpOverlay:Vn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,cuttingPuzzle:!0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},game:null,previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await Il(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setPuzzleCut:()=>{this.cuttingPuzzle=!1},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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 El={id:"game"},Dl={key:1,class:"overlay"},Nl=n("div",{class:"overlay-content"},[n("div",null,"⏳ Cutting puzzle, please wait... ⏳")],-1),_l={class:"menu"},Vl={class:"tabs"},Ol=i("🧩 Puzzles");Ml.render=function(e,i,r,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("info-overlay"),y=a("help-overlay"),f=a("connection-overlay"),v=a("puzzle-status"),w=a("router-link"),b=a("scores");return s(),t("div",El,[p(n(g,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),e.g.game?p((s(),t(m,{key:0,onBgclick:i[4]||(i[4]=t=>e.toggle("info",!0)),game:e.g.game},null,8,["game"])),[[x,"info"===e.overlay]]):l("",!0),p(n(y,{onBgclick:i[5]||(i[5]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),e.cuttingPuzzle?(s(),t("div",Dl,[Nl])):l("",!0),n(f,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(v,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",_l,[n("div",Vl,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Ol])),_:1}),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("info",!0))},"ℹ️ Info"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("help",!0))},"⌨️ Hotkeys")])]),n(b,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var Bl=e({name:"replay",components:{PuzzleStatus:xt,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,InfoOverlay:Ot,HelpOverlay:Vn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,cuttingPuzzle:!0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},game:null,previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await Il(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,Al,this.$el,{setPuzzleCut:()=>{this.cuttingPuzzle=!1},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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 Ul={id:"replay"},Rl={key:1,class:"overlay"},$l=n("div",{class:"overlay-content"},[n("div",null,"⏳ Cutting puzzle, please wait... ⏳")],-1),Gl={class:"menu"},Ll={class:"tabs"},Fl=i("🧩 Puzzles");Bl.render=function(e,i,d,c,u,g){const h=a("settings-overlay"),m=a("preview-overlay"),y=a("info-overlay"),f=a("help-overlay"),v=a("puzzle-status"),w=a("router-link"),b=a("scores");return s(),t("div",Ul,[p(n(h,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(m,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),e.g.game?p((s(),t(y,{key:0,onBgclick:i[4]||(i[4]=t=>e.toggle("info",!0)),game:e.g.game},null,8,["game"])),[[x,"info"===e.overlay]]):l("",!0),p(n(f,{onBgclick:i[5]||(i[5]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),e.cuttingPuzzle?(s(),t("div",Rl,[$l])):l("",!0),n(v,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[8]||(i[8]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",Gl,[n("div",Ll,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Fl])),_:1}),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[11]||(i[11]=t=>e.toggle("info",!0))},"ℹ️ Info"),n("div",{class:"opener",onClick:i[12]||(i[12]=t=>e.toggle("help",!0))},"⌨️ Hotkeys")])]),n(b,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=k({history:P(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:it},{name:"game",path:"/g/:id",component:Ml},{name:"replay",path:"/replay/:id",component:Bl}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=A(S);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=X.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); diff --git a/build/public/assets/index.93936dee.js b/build/public/assets/index.93936dee.js deleted file mode 100644 index e6ab743..0000000 --- a/build/public/assets/index.93936dee.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as n,w as l,b as o,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as C,s as x,u as k,x as P,y as A}from"./vendor.684f7bc8.js";var S=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const z={id:"app"},T={key:0,class:"nav"},I=i("Games overview"),M=i("New game");S.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",z,[e.showNav?(s(),t("ul",T,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:l((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:l((()=>[M])),_:1})])])):o("",!0),n(g)])};const E=864e5,D=e=>{const t=Math.floor(e/E);e%=E;const n=Math.floor(e/36e5);e%=36e5;const l=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${l}m ${Math.floor(e/1e3)}s`};var N=1,_=1e3,V=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},O=(e,t)=>D(t-e),B=D,U=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",l=e,o=t||V();return`${n} ${O(l,o)}`}}});const R={class:"game-info-text"},$=n("br",null,null,-1),G=n("br",null,null,-1),L=n("br",null,null,-1),F=i(" ↪️ Watch replay ");U.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:l((()=>[n("span",R,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),$,i(" 👥 "+r(e.game.players),1),G,i(" "+r(e.time(e.game.started,e.game.finished)),1),L])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:l((()=>[F])),_:1},8,["to"])):o("",!0)],4)};var j=e({components:{GameTeaser:U},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 W=n("h1",null,"Running games",-1),K=n("h1",null,"Finished games",-1);j.render=function(e,l,o,i,r,u){const p=a("game-teaser");return s(),t("div",null,[W,(s(!0),t(d,null,c(e.gamesRunning,((e,l)=>(s(),t("div",{class:"game-teaser-wrap",key:l},[n(p,{game:e},null,8,["game"])])))),128)),K,(s(!0),t(d,null,c(e.gamesFinished,((e,l)=>(s(),t("div",{class:"game-teaser-wrap",key:l},[n(p,{game:e},null,8,["game"])])))),128))])};var H=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});H.render=function(e,l,o,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:l[2]||(l[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:l[1]||(l[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var Y=e({name:"image-library",components:{ImageTeaser:H},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});Y.render=function(e,n,l,o,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,l)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:l},null,8,["image","onClick","onEditClick"])))),128))])};class q{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),l=t[n];t[n]=t[e],t[e]=l}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new q(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const Q=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},Z=(...e)=>{const t=t=>(...n)=>{const l=new Date,o=Q(l.getHours(),"00"),a=Q(l.getMinutes(),"00"),s=Q(l.getSeconds(),"00");console[t](`${o}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var X={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",q.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode,e.shapeMode,e.snapMode]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:q.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const l=[n,e[n]].map(encodeURIComponent);t.push(l.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const J={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};J.render=function(e,n,l,o,a,i){return s(),t("div",{style:i.style,title:l.title},null,12,["title"])};var ee=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const te=m();y("data-v-a4fa5e7e");const ne={key:0,class:"autocomplete"};f();const le=te(((e,l,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.input=t),placeholder:"Plants, People",onChange:l[2]||(l[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:l[3]||(l[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:l[4]||(l[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",ne,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,l)=>(s(),t("li",{key:l,class:{active:l===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):o("",!0),(s(!0),t(d,null,c(e.values,((n,l)=>(s(),t("span",{key:l,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));ee.render=le,ee.__scopeId="data-v-a4fa5e7e";const oe=Z("NewImageDialog.vue");var ae=e({name:"new-image-dialog",components:{ResponsiveImage:J,TagsInput:ee},props:{autocompleteTags:{type:Function},uploadProgress:{type:Number},uploading:{type:String}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{uploadProgressPercent(){return this.uploadProgress?Math.round(100*this.uploadProgress):0},canPostToGallery(){return!this.uploading&&!(!this.previewUrl||!this.file)},canSetupGameClick(){return!this.uploading&&!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const l=n[0];return l.type.startsWith("image/")?l:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){oe.info("onDragleave"),this.droppable=!1}}});const se=n("div",{class:"drop-target"},null,-1),ie={key:0,class:"has-image"},re={key:1},de={class:"upload"},ce=n("span",{class:"btn"},"Upload File",-1),ue={class:"area-settings"},pe=n("td",null,[n("label",null,"Title")],-1),ge=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),he=n("td",null,[n("label",null,"Tags")],-1),me={class:"area-buttons"},ye=i("🖼️ Post to gallery"),fe=i("🧩 Post to gallery "),ve=n("br",null,null,-1),we=i(" + set up game");ae.render=function(e,l,o,c,h,m){const y=a("responsive-image"),f=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:l[11]||(l[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[10]||(l[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:l[3]||(l[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:l[4]||(l[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:l[5]||(l[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[se,e.previewUrl?(s(),t("div",ie,[n("span",{class:"remove btn",onClick:l[1]||(l[1]=t=>e.previewUrl="")},"X"),n(y,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",re,[n("label",de,[n("input",{type:"file",style:{display:"none"},onChange:l[2]||(l[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),ce])]))],34),n("div",ue,[n("table",null,[n("tr",null,[pe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":l[6]||(l[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),ge,n("tr",null,[he,n("td",null,[n(f,{modelValue:e.tags,"onUpdate:modelValue":l[7]||(l[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",me,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:l[8]||(l[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},["postToGallery"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[ye],64))],8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:l[9]||(l[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},["setupGame"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[fe,ve,we],64))],8,["disabled"])])])])};var be=e({name:"edit-image-dialog",components:{ResponsiveImage:J,TagsInput:ee},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Ce={class:"area-image"},xe={class:"has-image"},ke={class:"area-settings"},Pe=n("td",null,[n("label",null,"Title")],-1),Ae=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Se=n("td",null,[n("label",null,"Tags")],-1),ze={class:"area-buttons"};var Te,Ie,Me,Ee,De,Ne,_e,Ve;be.render=function(e,l,o,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("div",Ce,[n("div",xe,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ke,[n("table",null,[n("tr",null,[Pe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),Ae,n("tr",null,[Se,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[2]||(l[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",ze,[n("button",{class:"btn",onClick:l[3]||(l[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])},(Ie=Te||(Te={}))[Ie.Flat=0]="Flat",Ie[Ie.Out=1]="Out",Ie[Ie.In=-1]="In",(Ee=Me||(Me={}))[Ee.FINAL=0]="FINAL",Ee[Ee.ANY=1]="ANY",(Ne=De||(De={}))[Ne.NORMAL=0]="NORMAL",Ne[Ne.ANY=1]="ANY",Ne[Ne.FLAT=2]="FLAT",(Ve=_e||(_e={}))[Ve.NORMAL=0]="NORMAL",Ve[Ve.REAL=1]="REAL";var Oe=e({name:"new-game-dialog",components:{ResponsiveImage:J},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Me.ANY,shapeMode:De.NORMAL,snapMode:_e.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Be={class:"area-image"},Ue={class:"has-image"},Re={key:0,class:"image-title"},$e={key:0,class:"image-title-title"},Ge={key:1,class:"image-title-dim"},Le={class:"area-settings"},Fe=n("td",null,[n("label",null,"Pieces")],-1),je=n("td",null,[n("label",null,"Scoring: ")],-1),We=i(" Any (Score when pieces are connected to each other or on final location)"),Ke=n("br",null,null,-1),He=i(" Final (Score when pieces are put to their final location)"),Ye=n("td",null,[n("label",null,"Shapes: ")],-1),qe=i(" Normal"),Qe=n("br",null,null,-1),Ze=i(" Any (flat pieces can occur anywhere)"),Xe=n("br",null,null,-1),Je=i(" Flat (all pieces flat on all sides)"),et=n("td",null,[n("label",null,"Snapping: ")],-1),tt=i(" Normal (pieces snap to final destination and to each other)"),nt=n("br",null,null,-1),lt=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),ot={class:"area-buttons"};Oe.render=function(e,l,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:l[11]||(l[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[10]||(l[10]=u((()=>{}),["stop"]))},[n("div",Be,[n("div",Ue,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title||e.image.width||e.image.height?(s(),t("div",Re,[e.image.title?(s(),t("span",$e,'"'+r(e.image.title)+'"',1)):o("",!0),e.image.width||e.image.height?(s(),t("span",Ge,"("+r(e.image.width)+" ✕ "+r(e.image.height)+")",1)):o("",!0)])):o("",!0)]),n("div",Le,[n("table",null,[n("tr",null,[Fe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[je,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[2]||(l[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),We]),Ke,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[3]||(l[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),He])])]),n("tr",null,[Ye,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[4]||(l[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),qe]),Qe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[5]||(l[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),Ze]),Xe,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[6]||(l[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),Je])])]),n("tr",null,[et,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[7]||(l[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),tt]),nt,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[8]||(l[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),lt])])])])]),n("div",ot,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:l[9]||(l[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};const at=async(e,t,n)=>new Promise(((l,o)=>{const a=new window.XMLHttpRequest;a.open(e,t,!0),a.withCredentials=!0;for(const e in n.headers||{})a.setRequestHeader(e,n.headers[e]);a.addEventListener("load",(function(e){l({status:this.status,text:this.responseText,json:async()=>JSON.parse(this.responseText)})})),a.addEventListener("error",(function(e){o(new Error("xhr error"))})),a.upload&&n.onUploadProgress&&a.upload.addEventListener("progress",(function(e){n.onUploadProgress&&n.onUploadProgress(e)})),a.send(n.body)}));var st=(e,t)=>at("post",e,t),it=e({components:{ImageLibrary:Y,NewImageDialog:ae,EditImageDialog:be,NewGameDialog:Oe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:"",uploading:"",uploadProgress:0}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${X.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){this.uploadProgress=0;const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await st("/api/upload",{body:t,onUploadProgress:e=>{this.uploadProgress=e.loaded/e.total}});return this.uploadProgress=1,await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){this.uploading="postToGallery",await this.uploadImage(e),this.uploading="",this.dialog="",await this.loadImages()},async setupGameClick(e){this.uploading="setupGame";const t=await this.uploadImage(e);this.uploading="",this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/api/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const rt={class:"upload-image-teaser"},dt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),ct={key:0},ut=i(" Tags: "),pt=i(" Sort by: "),gt=n("option",{value:"date_desc"},"Newest first",-1),ht=n("option",{value:"date_asc"},"Oldest first",-1),mt=n("option",{value:"alpha_asc"},"A-Z",-1),yt=n("option",{value:"alpha_desc"},"Z-A",-1);it.render=function(e,l,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[n("div",rt,[n("div",{class:"btn btn-big",onClick:l[1]||(l[1]=t=>e.dialog="new-image")},"Upload your image"),dt]),n("div",null,[e.tags.length>0?(s(),t("label",ct,[ut,(s(!0),t(d,null,c(e.relevantTags,((n,l)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:l,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):o("",!0),n("label",null,[pt,p(n("select",{"onUpdate:modelValue":l[2]||(l[2]=t=>e.filters.sort=t),onChange:l[3]||(l[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[gt,ht,mt,yt],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:l[4]||(l[4]=t=>e.dialog=""),uploadProgress:e.uploadProgress,uploading:e.uploading,onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","uploadProgress","uploading","onPostToGalleryClick","onSetupGameClick"])):o("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:l[5]||(l[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):o("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:l[6]||(l[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):o("",!0)])};var ft=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"},wt=n("div",null,"Scores",-1),bt=n("td",null,"⚡",-1),Ct=n("td",null,"💤",-1);ft.render=function(e,l,o,a,i,u){return s(),t("div",vt,[wt,n("table",null,[(s(!0),t(d,null,c(e.actives,((e,l)=>(s(),t("tr",{key:l,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,l)=>(s(),t("tr",{key:l,style:{color:e.color}},[Ct,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var xt=e({name:"puzzle-status",props:{finished:{type:Boolean,required:!0},duration:{type:Number,required:!0},piecesDone:{type:Number,required:!0},piecesTotal:{type:Number,required:!0}},computed:{icon(){return this.finished?"🏁":"⏳"},durationStr(){return B(this.duration)}}});const kt={class:"timer"};xt.render=function(e,l,o,a,i,d){return s(),t("div",kt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),b(e.$slots,"default")])};var Pt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const At=m();y("data-v-4d56fc17");const St=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),It=n("td",null,[n("label",null,"Sounds: ")],-1),Mt=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Et={class:"sound-volume"},Dt=n("td",null,[n("label",null,"Show player names: ")],-1);f();const Nt=At(((e,l,o,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:l[10]||(l[10]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:l[9]||(l[9]=u((()=>{}),["stop"]))},[n("tr",null,[St,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":l[1]||(l[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[zt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":l[2]||(l[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[Tt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":l[3]||(l[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[It,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":l[4]||(l[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[C,e.modelValue.soundsEnabled]])])]),n("tr",null,[Mt,n("td",Et,[n("span",{onClick:l[5]||(l[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:l[6]||(l[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:l[7]||(l[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])]),n("tr",null,[Dt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":l[8]||(l[8]=t=>e.modelValue.showPlayerNames=t)},null,512),[[C,e.modelValue.showPlayerNames]])])])])]))));Pt.render=Nt,Pt.__scopeId="data-v-4d56fc17";var _t=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const Vt={class:"preview"};_t.render=function(e,l,o,a,i,r){return s(),t("div",{class:"overlay",onClick:l[1]||(l[1]=t=>e.$emit("bgclick"))},[n("div",Vt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Ot=e({name:"help-overlay",emits:{bgclick:null},props:{game:{type:Object,required:!0}},computed:{scoreMode(){switch(this.game.scoreMode){case Me.ANY:return["Any","Score when pieces are connected to each other or on final location"];case Me.FINAL:default:return["Final","Score when pieces are put to their final location"]}},shapeMode(){switch(this.game.shapeMode){case De.FLAT:return["Flat","all pieces flat on all sides"];case De.ANY:return["Any","flat pieces can occur anywhere"];case De.NORMAL:default:return["Normal",""]}},snapMode(){switch(this.game.snapMode){case _e.REAL:return["Real","pieces snap only to corners, already snapped pieces and to each other"];case _e.NORMAL:default:return["Normal","pieces snap to final destination and to each other"]}}}});const Bt=n("tr",null,[n("td",{colspan:"2"},"Info about this puzzle")],-1),Ut=n("td",null,"Image Title: ",-1),Rt=n("td",null,"Snap Mode: ",-1),$t=n("td",null,"Shape Mode: ",-1),Gt=n("td",null,"Score Mode: ",-1);Ot.render=function(e,l,o,a,i,d){return s(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[Bt,n("tr",null,[Ut,n("td",null,r(e.game.puzzle.info.image.title),1)]),n("tr",null,[Rt,n("td",null,[n("span",{title:e.snapMode[1]},r(e.scoreMode[0]),9,["title"])])]),n("tr",null,[$t,n("td",null,[n("span",{title:e.snapMode[1]},r(e.shapeMode[0]),9,["title"])])]),n("tr",null,[Gt,n("td",null,[n("span",{title:e.snapMode[1]},r(e.snapMode[0]),9,["title"])])])])])};var Lt=1,Ft=4,jt=2,Wt=3,Kt=2,Ht=4,Yt=3,qt=9,Qt=1,Zt=2,Xt=3,Jt=4,en=5,tn=6,nn=7,ln=8,on=10,an=11,sn=12,rn=13,dn=14,cn=15,un=16,pn=1,gn=2,hn=3;const mn=Z("Communication.js");let yn,fn=[],vn=e=>{fn.push(e)},wn=[],bn=e=>{wn.push(e)};let Cn=0;const xn=e=>{Cn!==e&&(Cn=e,bn(e))};function kn(e){if(2===Cn)try{yn.send(JSON.stringify(e))}catch(t){mn.info("unable to send message.. maybe because ws is invalid?")}}let Pn,An;var Sn={connect:function(e,t,n){return Pn=0,An={},xn(3),new Promise((l=>{yn=new WebSocket(e,n+"|"+t),yn.onopen=()=>{xn(2),kn([Wt])},yn.onmessage=e=>{const t=JSON.parse(e.data),o=t[0];if(o===Ft){const e=t[1];l(e)}else{if(o!==Lt)throw`[ 2021-05-09 invalid connect msgType ${o} ]`;{const e=t[1],l=t[2];if(e===n&&An[l])return void delete An[l];vn(t)}}},yn.onerror=()=>{throw xn(1),"[ 2021-05-15 onerror ]"},yn.onclose=e=>{4e3===e.code||1001===e.code?xn(4):xn(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},l=await fetch(`/api/replay-data${X.asQueryArgs(n)}`);return await l.json()},disconnect:function(){yn&&yn.close(4e3),Pn=0,An={}},sendClientEvent:function(e){Pn++,An[Pn]=e,kn([jt,Pn,An[Pn]])},onServerChange:function(e){vn=e;for(const t of fn)vn(t);fn=[]},onConnectionStateChange:function(e){bn=e;for(const t of wn)bn(t);wn=[]},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},zn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Sn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Sn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const Tn={key:0,class:"overlay connection-lost"},In={key:0,class:"overlay-content"},Mn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),En={key:1,class:"overlay-content"},Dn=n("div",null,"Connecting...",-1);zn.render=function(e,l,a,i,r,d){return e.show?(s(),t("div",Tn,[e.lostConnection?(s(),t("div",In,[Mn,n("span",{class:"btn",onClick:l[1]||(l[1]=t=>e.$emit("reconnect"))},"Reconnect")])):o("",!0),e.connecting?(s(),t("div",En,[Dn])):o("",!0)])):o("",!0)};var Nn=e({name:"help-overlay",emits:{bgclick:null}});const _n=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),Vn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),On=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),Bn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),Un=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Rn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),$n=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),Gn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Ln=n("tr",null,[n("td",null,"🎯 Center puzzle in screen:"),n("td",null,[n("div",null,[n("kbd",null,"C")])])],-1),Fn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),jn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Wn=n("tr",null,[n("td",null,"👤 Toggle player names:"),n("td",null,[n("div",null,[n("kbd",null,"N")])])],-1),Kn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),Hn=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),Yn=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),qn=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);Nn.render=function(e,l,o,a,i,r){return s(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[_n,Vn,On,Bn,Un,Rn,$n,Gn,Ln,Fn,jn,Wn,Kn,Hn,Yn,qn])])};var Qn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),Zn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Xn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Jn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),el=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function tl(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var nl={createCanvas:tl,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const l=tl(t,n);return l.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(l)},colorizedCanvas:function(e,t,n){const l=tl(e.width,e.height),o=l.getContext("2d");return o.save(),o.drawImage(t,0,0),o.fillStyle=n,o.globalCompositeOperation="source-in",o.fillRect(0,0,t.width,t.height),o.restore(),o.save(),o.globalCompositeOperation="destination-over",o.drawImage(e,0,0),o.restore(),l}};const ll=Z("Debug.js");let ol=0,al=0;var sl=e=>{ol=performance.now(),al=e},il=e=>{const t=performance.now(),n=t-ol;n>al&&ll.log(e+": "+n),ol=t};function rl(e,t){const n=e.x-t.x,l=e.y-t.y;return Math.sqrt(n*n+l*l)}function dl(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var cl={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:rl,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:dl,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 rl(dl(e),dl(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const ul=Z("PuzzleGraphics.js");function pl(e,t){const n=X.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var gl={loadPuzzleBitmaps:async function(e){const t=await nl.loadImageToBitmap(e.info.imageUrl),n=await nl.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){ul.log("start createPuzzleTileBitmaps");const l=n.tileSize,o=n.tileMarginWidth,a=n.tileDrawSize,s=l/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:o,y:o},r=cl.pointAdd(a,{x:l,y:0}),c=cl.pointAdd(r,{x:0,y:l}),u=cl.pointSub(c,{x:l,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let l=0;lX.decodePiece(hl[e].puzzle.tiles[t]),Il=(e,t)=>Tl(e,t).group,Ml=(e,t)=>{const n=hl[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},El=(e,t)=>{const n=hl[e].puzzle.info,l={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},o=function(e,t){const n=hl[e].puzzle.info,l=X.coordByPieceIdx(n,t),o=l.x*n.tileSize,a=l.y*n.tileSize;return{x:o,y:a}}(e,t);return cl.pointAdd(l,o)},Dl=(e,t)=>Tl(e,t).pos,Nl=e=>{const t=Ql(e),n=Zl(e),l=Math.round(t/4),o=Math.round(n/4);return{x:0-l,y:0-o,w:t+2*l,h:n+2*o}},_l=(e,t)=>{const n=Ul(e),l=Tl(e,t);return{x:l.pos.x,y:l.pos.y,w:n,h:n}},Vl=(e,t)=>Tl(e,t).z,Ol=(e,t)=>{for(const n of hl[e].puzzle.tiles){const e=X.decodePiece(n);if(e.owner===t)return e.idx}return-1},Bl=e=>hl[e].puzzle.info.tileDrawSize,Ul=e=>hl[e].puzzle.info.tileSize,Rl=e=>hl[e].puzzle.data.maxGroup,$l=e=>hl[e].puzzle.data.maxZ;function Gl(e,t){const n=hl[e].puzzle.info,l=X.coordByPieceIdx(n,t);return[l.y>0?t-n.tilesX:-1,l.x0?t-1:-1]}const Ll=(e,t,n)=>{for(const l of t)zl(e,l,{z:n})},Fl=(e,t,n)=>{const l=Dl(e,t);zl(e,t,{pos:cl.pointAdd(l,n)})},jl=(e,t,n)=>{const l=Bl(e),o=Nl(e),a=n;for(const s of t){const t=Tl(e,s);t.pos.x+n.xo.x+o.w&&(a.x=Math.min(o.x+o.w-t.pos.x+l,a.x)),t.pos.y+n.yo.y+o.h&&(a.y=Math.min(o.y+o.h-t.pos.y+l,a.y))}for(const s of t)Fl(e,s,a)},Wl=(e,t)=>Tl(e,t).owner,Kl=(e,t)=>{for(const n of t)zl(e,n,{owner:-1,z:1})},Hl=(e,t,n)=>{for(const l of t)zl(e,l,{owner:n})};function Yl(e,t){const n=hl[e].puzzle.tiles,l=X.decodePiece(n[t]),o=[];if(l.group)for(const a of n){const e=X.decodePiece(a);e.group===l.group&&o.push(e.idx)}else o.push(l.idx);return o}const ql=(e,t)=>{const n=yl(e,t);return n?n.points:0},Ql=e=>hl[e].puzzle.info.table.width,Zl=e=>hl[e].puzzle.info.table.height;var Xl={setGame:function(e,t){hl[e]=t},exists:function(e){return!!hl[e]||!1},playerExists:vl,getActivePlayers:function(e,t){const n=t-30*_;return wl(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return wl(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){vl(e,t)?Al(e,t,{ts:n}):fl(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:Pl,getPieceCount:bl,getImageUrl:function(e){var t;const n=(null==(t=hl[e].puzzle.info.image)?void 0:t.url)||hl[e].puzzle.info.imageUrl;if(!n)throw new Error("[2021-07-11] no image url set");return n},get:function(e){return hl[e]||null},getAllGames:function(){return Object.values(hl).sort(((e,t)=>kl(e.id)===kl(t.id)?t.puzzle.data.started-e.puzzle.data.started:kl(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=yl(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=yl(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=yl(e,t);return n?n.name:null},getPlayerIndexById:ml,getPlayerIdByIndex:function(e,t){return hl[e].players.length>t?X.decodePlayer(hl[e].players[t]).id:null},changePlayer:Al,setPlayer:fl,setPiece:function(e,t,n){hl[e].puzzle.tiles[t]=X.encodePiece(n)},setPuzzleData:function(e,t){hl[e].puzzle.data=t},getTableWidth:Ql,getTableHeight:Zl,getPuzzle:e=>hl[e].puzzle,getRng:e=>hl[e].rng.obj,getPuzzleWidth:e=>hl[e].puzzle.info.width,getPuzzleHeight:e=>hl[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return hl[e].puzzle.tiles.map(X.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Ol(e,t);return n<0?null:hl[e].puzzle.tiles[n]},getPieceDrawOffset:e=>hl[e].puzzle.info.tileDrawOffset,getPieceDrawSize:Bl,getFinalPiecePos:El,getStartTs:e=>hl[e].puzzle.data.started,getFinishTs:e=>hl[e].puzzle.data.finished,handleInput:function(e,t,n,l,o){const a=hl[e].puzzle,s=function(e,t){return t in hl[e].evtInfos?hl[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([pn,a.data])},d=t=>{i.push([gn,X.encodePiece(Tl(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=yl(e,t);n&&i.push([hn,X.encodePlayer(n)])},p=n[0];if(p===tn){const o=n[1];Al(e,t,{bgcolor:o,ts:l}),u()}else if(p===nn){const o=n[1];Al(e,t,{color:o,ts:l}),u()}else if(p===ln){const o=`${n[1]}`.substr(0,16);Al(e,t,{name:o,ts:l}),u()}else if(p===qt){const o=n[1],a=n[2],s=yl(e,t);if(s){const n=s.x-o,i=s.y-a;Al(e,t,{ts:l,x:n,y:i}),u()}}else if(p===Qt){const o={x:n[1],y:n[2]};Al(e,t,{d:1,ts:l}),u(),s._last_mouse_down=o;const a=((e,t)=>{const n=hl[e].puzzle.info,l=hl[e].puzzle.tiles;let o=-1,a=-1;for(let s=0;so)&&(o=e.z,a=s)}return a})(e,o);if(a>=0){const n=$l(e)+1;Sl(e,{maxZ:n}),r();const l=Yl(e,a);Ll(e,l,$l(e)),Hl(e,l,t),c(l)}s._last_mouse=o}else if(p===Xt){const o=n[1],a=n[2],i={x:o,y:a};if(null===s._last_mouse_down)Al(e,t,{x:o,y:a,ts:l}),u();else{const n=Ol(e,t);if(n>=0){Al(e,t,{x:o,y:a,ts:l}),u();const r=Yl(e,n);let d=cl.pointInBounds(i,Nl(e))&&cl.pointInBounds(s._last_mouse_down,Nl(e));for(const t of r){const n=_l(e,t);if(cl.pointInBounds(i,n)){d=!0;break}}if(d){const t=o-s._last_mouse_down.x,n=a-s._last_mouse_down.y;jl(e,r,{x:t,y:n}),c(r)}}else Al(e,t,{ts:l}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===Zt){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Ol(e,t);if(g>=0){const n=Yl(e,g);Hl(e,n,0),c(n);const s=Dl(e,g),i=El(e,g);let h=!1;if(xl(e)===_e.REAL){for(const t of n)if(Ml(e,t)){h=!0;break}}else h=!0;if(h&&cl.pointDistance(i,s){const o=hl[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const l=Il(e,t),o=Il(e,n);return!(!l||l!==o)})(e,t,n))return!1;const a=Dl(e,t),s=cl.pointAdd(Dl(e,n),{x:l[0]*o.tileSize,y:l[1]*o.tileSize});if(cl.pointDistance(a,s){const l=hl[e].puzzle.tiles,o=Il(e,t),a=Il(e,n);let s;const i=[];o&&i.push(o),a&&i.push(a),o?s=o:a?s=a:(Sl(e,{maxGroup:Rl(e)+1}),r(),s=Rl(e));if(zl(e,t,{group:s}),d(t),zl(e,n,{group:s}),d(n),i.length>0)for(const r of l){const t=X.decodePiece(r);i.includes(t.group)&&(zl(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),o=Yl(e,t),((e,t)=>-1===Wl(e,t))(e,n))Kl(e,o);else{const t=((e,t)=>{let n=0;for(const l of t){const t=Vl(e,l);t>n&&(n=t)}return n})(e,o);Ll(e,o,t)}return c(o),!0}return!1};let a=!1;for(const t of Yl(e,g)){const l=Gl(e,t);if(n(e,t,l[0],[0,1])||n(e,t,l[1],[-1,0])||n(e,t,l[2],[0,-1])||n(e,t,l[3],[1,0])){a=!0;break}}if(a&&Cl(e)===Me.ANY){const n=ql(e,t)+1;Al(e,t,{d:p,ts:l,points:n}),u()}else Al(e,t,{d:p,ts:l}),u();a&&xl(e)===_e.REAL&&Pl(e)===bl(e)&&(Sl(e,{finished:l}),r()),a&&o&&o(t)}}else Al(e,t,{d:p,ts:l}),u();s._last_mouse=i}else if(p===Jt){const o=n[1],a=n[2];Al(e,t,{x:o,y:a,ts:l}),u(),s._last_mouse={x:o,y:a}}else if(p===en){const o=n[1],a=n[2];Al(e,t,{x:o,y:a,ts:l}),u(),s._last_mouse={x:o,y:a}}else Al(e,t,{ts:l}),u();return function(e,t,n){hl[e].evtInfos[t]=n}(e,t,s),i}};let Jl=-10,eo=20,to=2,no=15;class lo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Jl+Math.random()*eo,this.vy=-1*(to+Math.random()*no),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;to=t/2,no=t-to;const n=1/4*this.canvas.width/(t/2);Jl=-n,eo=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new lo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new lo(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{localStorage.setItem(e,t)},ho=e=>localStorage.getItem(e);var mo=(e,t)=>{go(e,`${t}`)},yo=(e,t)=>{const n=ho(e);if(null===n)return t;const l=parseInt(n,10);return isNaN(l)?t:l},fo=(e,t)=>{go(e,t?"1":"0")},vo=(e,t)=>{const n=ho(e);return null===n?t:"1"===n},wo=(e,t)=>{go(e,t)},bo=(e,t)=>{const n=ho(e);return null===n?t:n};const Co={"./grab.png":Zn,"./grab_mask.png":Xn,"./hand.png":Jn,"./hand_mask.png":el},xo={"./click.mp3":Qn};let ko=!0,Po=!0;let Ao=!0;async function So(e,t,n,l,o,a){void 0===window.DEBUG&&(window.DEBUG=!1);const s=xo["./click.mp3"].default,i=new Audio(s),r=await nl.loadImageToBitmap(Co["./grab.png"].default),d=await nl.loadImageToBitmap(Co["./hand.png"].default),c=await nl.loadImageToBitmap(Co["./grab_mask.png"].default),u=await nl.loadImageToBitmap(Co["./hand_mask.png"].default),p=r.width,g=Math.round(p/2),h=r.height,m=Math.round(h/2),y={},f=async e=>{const t=e.color+" "+e.d;if(!y[t]){const n=e.d?r:d;if(e.color){const l=e.d?c:u;y[t]=await createImageBitmap(nl.colorizedCanvas(n,l,e.color))}else y[t]=n}return y[t]},v=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,Ao=!0})),t}(o,nl.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};Sn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await Sn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let C=()=>0;const x=async()=>{if("play"===l){const l=await Sn.connect(n,e,t),o=X.decodeGame(l);Xl.setGame(o.id,o),C=()=>V()}else{if("replay"!==l)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=X.decodeGame(t.game);Xl.setGame(n.id,n),w.lastRealTs=V(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,C=()=>w.lastGameTs}}Ao=!0};await x();const k=Xl.getPieceDrawOffset(e),P=Xl.getPieceDrawSize(e),A=Xl.getPuzzleWidth(e),S=Xl.getPuzzleHeight(e),z=Xl.getTableWidth(e),T=Xl.getTableHeight(e),I={x:(z-A)/2,y:(T-S)/2},M={w:A,h:S},E={w:P,h:P},D=await gl.loadPuzzleBitmaps(Xl.getPuzzle(e)),_=new ao(v,Xl.getRng(e));_.init();const O=v.getContext("2d");v.classList.add("loaded"),a.setPuzzleCut();const B=function(){let e=0,t=0,n=1;const l=(l,o)=>{e+=l/n,t+=o/n},o=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},a=(e,t)=>{if(n==e)return!1;const o=1-n/e;return l(-t.x*o,-t.y*o),n=e,!0},s=l=>({x:l.x/n-e,y:l.y/n-t}),i=l=>({x:(l.x+e)*n,y:(l.y+t)*n}),r=e=>({w:e.w*n,h:e.h*n}),d=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,reset:()=>{e=0,t=0,n=1},move:l,canZoom:e=>n!=o(e),zoom:(e,t)=>a(o(e),t),setZoom:a,worldToViewport:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:r,viewportToWorld:e=>{const{x:t,y:n}=s(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:s,viewportDimToWorld:e=>{const{w:t,h:n}=d(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:d}}(),U=()=>{B.reset(),B.move(-(z-v.width)/2,-(T-v.height)/2);const e=B.worldDimToViewport(M),t=v.width-40,n=v.height-40;if(e.w>t||e.h>n||e.w{const l=n.viewportToWorld({x:e,y:t});return[l.x,l.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([Qt,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([Zt,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([Xt,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Jt:en;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([on]),"replay"===l&&("KeyI"===e.code&&v([rn]),"KeyO"===e.code&&v([dn]),"KeyP"===e.code&&v([sn])),"KeyF"===e.code&&(ko=!ko,Ao=!0),"KeyG"===e.code&&(Po=!Po,Ao=!0),"KeyM"===e.code&&v([an]),"KeyN"===e.code&&v([cn]),"KeyC"===e.code&&v([un]))}));const v=e=>{o.push(e)};return{addEvent:v,consumeAll:()=>{if(0===o.length)return[];const e=o.slice();return o=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const l=(p?24:12)*Math.sqrt(n.getCurrentZoom()),o=n.viewportDimToWorld({w:e*l,h:t*l});v([qt,o.w,o.h]),f&&(f[0]-=o.w,f[1]-=o.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([Jt,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([en,...e])}},setHotkeys:e=>{a=e}}}(v,window,B,l),$=Xl.getImageUrl(e),G=()=>{const t=Xl.getStartTs(e),n=Xl.getFinishTs(e),l=C();a.setFinished(!!n),a.setDuration((n||l)-t)};G(),a.setPiecesDone(Xl.getFinishedPiecesCount(e)),a.setPiecesTotal(Xl.getPieceCount(e));const L=C();a.setActivePlayers(Xl.getActivePlayers(e,L)),a.setIdlePlayers(Xl.getIdlePlayers(e,L));const F=!!Xl.getFinishTs(e);let j=F;const W=()=>j&&!F,K=()=>yo(so,100),H=()=>vo(io,!1),Y=()=>vo(po,!0),q=()=>{const e=K();i.volume=e/100,i.play()},Q=()=>"replay"===l?bo(ro,"#222222"):Xl.getPlayerBgColor(e,t)||bo(ro,"#222222"),Z=()=>"replay"===l?bo(co,"#ffffff"):Xl.getPlayerColor(e,t)||bo(co,"#ffffff");let J="",ee="",te=!1;const ne=e=>{te=e;const[t,n]=e?[J,"grab"]:[ee,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},le=e=>{J=nl.colorizedCanvas(r,c,e).toDataURL(),ee=nl.colorizedCanvas(d,u,e).toDataURL(),ne(te)};le(Z());const oe=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},ae=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,oe())},ie=()=>{w.paused=!w.paused,oe()},re=[];let de;let ce;if("play"===l?re.push(setInterval((()=>{G()}),1e3)):"replay"===l&&oe(),"play"===l)Sn.onServerChange((n=>{n[0],n[1],n[2];const l=n[3];for(const[o,a]of l)switch(o){case hn:{const n=X.decodePlayer(a);n.id!==t&&(Xl.setPlayer(e,n.id,n),Ao=!0)}break;case gn:{const t=X.decodePiece(a);Xl.setPiece(e,t.idx,t),Ao=!0}break;case pn:Xl.setPuzzleData(e,a),Ao=!0}j=!!Xl.getFinishTs(e)}));else if("replay"===l){const t=(t,n)=>{const l=t;if(l[0]===Kt){const t=l[1];return Xl.addPlayer(e,t,n),!0}if(l[0]===Ht){const t=Xl.getPlayerIdByIndex(e,l[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return Xl.addPlayer(e,t,n),!0}if(l[0]===Yt){const t=Xl.getPlayerIdByIndex(e,l[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const o=l[2];return Xl.handleInput(e,t,o,n),!0}return!1};let n=w.lastGameTs;const l=async()=>{w.logPointer+1>=w.log.length&&await b(e);const o=V();if(w.paused)return w.lastRealTs=o,void(de=setTimeout(l,50));const a=(o-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const l=w.log[w.logPointer],o=n+l[l.length-1],a=w.log[e],i=a[a.length-1],r=o+i;if(r>s){s+500*N{let t=!1;const n=e.fps||60,l=e.slow||1,o=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=l*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,o(i);a(c/l),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{R.createKeyEvents();for(const n of R.consumeAll())if("play"===l){const l=n[0];if(l===qt){const e=n[1],t=n[2],l=B.worldDimToViewport({w:e,h:t});Ao=!0,B.move(l.w,l.h)}else if(l===Xt){if(ue&&!Xl.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),l=Math.round(t.x-ue.x),o=Math.round(t.y-ue.y);Ao=!0,B.move(l,o),ue=t}}else if(l===nn)le(n[1]);else if(l===Qt){const e={x:n[1],y:n[2]};ue=B.worldToViewport(e),ne(!0)}else if(l===Zt)ue=null,ne(!1);else if(l===Jt){const e={x:n[1],y:n[2]};Ao=!0,B.zoom("in",B.worldToViewport(e))}else if(l===en){const e={x:n[1],y:n[2]};Ao=!0,B.zoom("out",B.worldToViewport(e))}else l===on?a.togglePreview():l===an?a.toggleSoundsEnabled():l===cn?a.togglePlayerNames():l===un&&U();const o=C();Xl.handleInput(e,t,n,o,(e=>{H()&&q()})).length>0&&(Ao=!0),Sn.sendClientEvent(n)}else if("replay"===l){const e=n[0];if(e===sn)ie();else if(e===dn)se();else if(e===rn)ae();else if(e===qt){const e=n[1],t=n[2];Ao=!0,B.move(e,t)}else if(e===Xt){if(ue){const e={x:n[1],y:n[2]},t=B.worldToViewport(e),l=Math.round(t.x-ue.x),o=Math.round(t.y-ue.y);Ao=!0,B.move(l,o),ue=t}}else if(e===nn)le(n[1]);else if(e===Qt){const e={x:n[1],y:n[2]};ue=B.worldToViewport(e),ne(!0)}else if(e===Zt)ue=null,ne(!1);else if(e===Jt){const e={x:n[1],y:n[2]};Ao=!0,B.zoom("in",B.worldToViewport(e))}else if(e===en){const e={x:n[1],y:n[2]};Ao=!0,B.zoom("out",B.worldToViewport(e))}else e===on?a.togglePreview():e===an?a.toggleSoundsEnabled():e===cn?a.togglePlayerNames():e===un&&U()}j=!!Xl.getFinishTs(e),W()&&(_.update(),Ao=!0)},render:async()=>{if(!Ao)return;const n=C();let o,s,i;window.DEBUG&&sl(0),O.fillStyle=Q(),O.fillRect(0,0,v.width,v.height),window.DEBUG&&il("clear done"),o=B.worldToViewportRaw(I),s=B.worldDimToViewportRaw(M),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(o.x,o.y,s.w,s.h),window.DEBUG&&il("board done");const r=Xl.getPiecesSortedByZIndex(e);window.DEBUG&&il("get tiles done"),s=B.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?ko:Po)&&(i=D[e.idx],o=B.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(i,0,0,i.width,i.height,o.x,o.y,s.w,s.h));window.DEBUG&&il("tiles done");const d=[];for(const a of Xl.getActivePlayers(e,n))c=a,("replay"===l||c.id!==t)&&(i=await f(a),o=B.worldToViewport(a),O.drawImage(i,o.x-g,o.y-m),Y()&&d.push([`${a.name} (${a.points})`,o.x,o.y+h]));var c;O.fillStyle="white",O.textAlign="center";for(const[e,t,l]of d)O.fillText(e,t,l);window.DEBUG&&il("players done"),a.setActivePlayers(Xl.getActivePlayers(e,n)),a.setIdlePlayers(Xl.getIdlePlayers(e,n)),a.setPiecesDone(Xl.getFinishedPiecesCount(e)),window.DEBUG&&il("HUD done"),W()&&_.render(),Ao=!1}}),{setHotkeys:e=>{R.setHotkeys(e)},onBgChange:e=>{wo(ro,e),R.addEvent([tn,e])},onColorChange:e=>{wo(co,e),R.addEvent([nn,e])},onNameChange:e=>{wo(uo,e),R.addEvent([ln,e])},onSoundsEnabledChange:e=>{fo(io,e)},onSoundsVolumeChange:e=>{mo(so,e),q()},onShowPlayerNamesChange:e=>{fo(po,e)},replayOnSpeedUp:ae,replayOnSpeedDown:se,replayOnPauseToggle:ie,previewImageUrl:$,player:{background:Q(),color:Z(),name:"replay"===l?bo(uo,"anon"):Xl.getPlayerName(e,t)||bo(uo,"anon"),soundsEnabled:H(),soundsVolume:K(),showPlayerNames:Y()},game:Xl.get(e),disconnect:Sn.disconnect,connect:x,unload:()=>{re.forEach((e=>{clearInterval(e)})),de&&clearTimeout(de),ce&&ce.stop()}}}var zo=e({name:"game",components:{PuzzleStatus:xt,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,InfoOverlay:Ot,ConnectionOverlay:zn,HelpOverlay:Nn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,cuttingPuzzle:!0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},game:null,previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await So(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setPuzzleCut:()=>{this.cuttingPuzzle=!1},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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 To={id:"game"},Io={key:1,class:"overlay"},Mo=n("div",{class:"overlay-content"},[n("div",null,"⏳ Cutting puzzle, please wait... ⏳")],-1),Eo={class:"menu"},Do={class:"tabs"},No=i("🧩 Puzzles");zo.render=function(e,i,r,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("info-overlay"),y=a("help-overlay"),f=a("connection-overlay"),v=a("puzzle-status"),w=a("router-link"),b=a("scores");return s(),t("div",To,[p(n(g,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),e.g.game?p((s(),t(m,{key:0,onBgclick:i[4]||(i[4]=t=>e.toggle("info",!0)),game:e.g.game},null,8,["game"])),[[x,"info"===e.overlay]]):o("",!0),p(n(y,{onBgclick:i[5]||(i[5]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),e.cuttingPuzzle?(s(),t("div",Io,[Mo])):o("",!0),n(f,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(v,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",Eo,[n("div",Do,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[No])),_:1}),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("info",!0))},"ℹ️ Info"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("help",!0))},"⌨️ Hotkeys")])]),n(b,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var _o=e({name:"replay",components:{PuzzleStatus:xt,Scores:ft,SettingsOverlay:Pt,PreviewOverlay:_t,InfoOverlay:Ot,HelpOverlay:Nn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,cuttingPuzzle:!0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},game:null,previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await So(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"replay",this.$el,{setPuzzleCut:()=>{this.cuttingPuzzle=!1},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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 Vo={id:"replay"},Oo={key:1,class:"overlay"},Bo=n("div",{class:"overlay-content"},[n("div",null,"⏳ Cutting puzzle, please wait... ⏳")],-1),Uo={class:"menu"},Ro={class:"tabs"},$o=i("🧩 Puzzles");_o.render=function(e,i,d,c,u,g){const h=a("settings-overlay"),m=a("preview-overlay"),y=a("info-overlay"),f=a("help-overlay"),v=a("puzzle-status"),w=a("router-link"),b=a("scores");return s(),t("div",Vo,[p(n(h,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(m,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),e.g.game?p((s(),t(y,{key:0,onBgclick:i[4]||(i[4]=t=>e.toggle("info",!0)),game:e.g.game},null,8,["game"])),[[x,"info"===e.overlay]]):o("",!0),p(n(f,{onBgclick:i[5]||(i[5]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),e.cuttingPuzzle?(s(),t("div",Oo,[Bo])):o("",!0),n(v,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:l((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[8]||(i[8]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",Uo,[n("div",Ro,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[$o])),_:1}),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[11]||(i[11]=t=>e.toggle("info",!0))},"ℹ️ Info"),n("div",{class:"opener",onClick:i[12]||(i[12]=t=>e.toggle("help",!0))},"⌨️ Hotkeys")])]),n(b,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=k({history:P(),routes:[{name:"index",path:"/",component:j},{name:"new-game",path:"/new-game",component:it},{name:"game",path:"/g/:id",component:zo},{name:"replay",path:"/replay/:id",component:_o}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const l=A(S);l.config.globalProperties.$config=t,l.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=X.uniqId(),localStorage.setItem("ID",e)),e}(),l.use(n),l.mount("#app")})(); diff --git a/build/public/index.html b/build/public/index.html index 3df4956..3fc9389 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,7 +4,7 @@ 🧩 jigsaw.hyottoko.club - + diff --git a/build/server/main.js b/build/server/main.js index 82e7590..3f60a65 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -340,6 +340,8 @@ const INPUT_EV_REPLAY_SPEED_UP = 13; const INPUT_EV_REPLAY_SPEED_DOWN = 14; const INPUT_EV_TOGGLE_PLAYER_NAMES = 15; const INPUT_EV_CENTER_FIT_PUZZLE = 16; +const INPUT_EV_TOGGLE_FIXED_PIECES = 17; +const INPUT_EV_TOGGLE_LOOSE_PIECES = 18; const CHANGE_DATA = 1; const CHANGE_TILE = 2; const CHANGE_PLAYER = 3; @@ -368,6 +370,8 @@ var Protocol = { INPUT_EV_REPLAY_SPEED_DOWN, INPUT_EV_TOGGLE_PLAYER_NAMES, INPUT_EV_CENTER_FIT_PUZZLE, + INPUT_EV_TOGGLE_FIXED_PIECES, + INPUT_EV_TOGGLE_LOOSE_PIECES, CHANGE_DATA, CHANGE_TILE, CHANGE_PLAYER, diff --git a/src/common/Protocol.ts b/src/common/Protocol.ts index 0370c1d..1f868c1 100644 --- a/src/common/Protocol.ts +++ b/src/common/Protocol.ts @@ -67,6 +67,9 @@ const INPUT_EV_REPLAY_SPEED_DOWN = 14 const INPUT_EV_TOGGLE_PLAYER_NAMES = 15 const INPUT_EV_CENTER_FIT_PUZZLE = 16 +const INPUT_EV_TOGGLE_FIXED_PIECES = 17 +const INPUT_EV_TOGGLE_LOOSE_PIECES = 18 + const CHANGE_DATA = 1 const CHANGE_TILE = 2 const CHANGE_PLAYER = 3 @@ -104,6 +107,9 @@ export default { INPUT_EV_TOGGLE_PLAYER_NAMES, INPUT_EV_CENTER_FIT_PUZZLE, + INPUT_EV_TOGGLE_FIXED_PIECES, + INPUT_EV_TOGGLE_LOOSE_PIECES, + CHANGE_DATA, CHANGE_TILE, CHANGE_PLAYER, diff --git a/src/frontend/EventAdapter.ts b/src/frontend/EventAdapter.ts new file mode 100644 index 0000000..d0166fe --- /dev/null +++ b/src/frontend/EventAdapter.ts @@ -0,0 +1,177 @@ +import Protocol from "../common/Protocol" +import { GameEvent } from "../common/Types" +import { MODE_REPLAY } from "./game" + +function EventAdapter ( + canvas: HTMLCanvasElement, + window: any, + viewport: any, + MODE: string +) { + let events: Array = [] + + let KEYS_ON = true + + let LEFT = false + let RIGHT = false + let UP = false + let DOWN = false + let ZOOM_IN = false + let ZOOM_OUT = false + let SHIFT = false + + const toWorldPoint = (x: number, y: number): [number, number] => { + const pos = viewport.viewportToWorld({x, y}) + return [pos.x, pos.y] + } + + const mousePos = (ev: MouseEvent) => toWorldPoint(ev.offsetX, ev.offsetY) + const canvasCenter = () => toWorldPoint(canvas.width / 2, canvas.height / 2) + + const key = (state: boolean, ev: KeyboardEvent) => { + if (!KEYS_ON) { + return + } + + if (ev.code === 'ShiftLeft' || ev.code === 'ShiftRight') { + SHIFT = state + } else if (ev.code === 'ArrowUp' || ev.code === 'KeyW') { + UP = state + } else if (ev.code === 'ArrowDown' || ev.code === 'KeyS') { + DOWN = state + } else if (ev.code === 'ArrowLeft' || ev.code === 'KeyA') { + LEFT = state + } else if (ev.code === 'ArrowRight' || ev.code === 'KeyD') { + RIGHT = state + } else if (ev.code === 'KeyQ') { + ZOOM_OUT = state + } else if (ev.code === 'KeyE') { + ZOOM_IN = state + } + } + + let lastMouse: [number, number]|null = null + canvas.addEventListener('mousedown', (ev) => { + lastMouse = mousePos(ev) + if (ev.button === 0) { + addEvent([Protocol.INPUT_EV_MOUSE_DOWN, ...lastMouse]) + } + }) + + canvas.addEventListener('mouseup', (ev) => { + lastMouse = mousePos(ev) + if (ev.button === 0) { + addEvent([Protocol.INPUT_EV_MOUSE_UP, ...lastMouse]) + } + }) + + canvas.addEventListener('mousemove', (ev) => { + lastMouse = mousePos(ev) + addEvent([Protocol.INPUT_EV_MOUSE_MOVE, ...lastMouse]) + }) + + canvas.addEventListener('wheel', (ev) => { + lastMouse = mousePos(ev) + if (viewport.canZoom(ev.deltaY < 0 ? 'in' : 'out')) { + const evt = ev.deltaY < 0 + ? Protocol.INPUT_EV_ZOOM_IN + : Protocol.INPUT_EV_ZOOM_OUT + addEvent([evt, ...lastMouse]) + } + }) + + window.addEventListener('keydown', (ev: KeyboardEvent) => key(true, ev)) + window.addEventListener('keyup', (ev: KeyboardEvent) => key(false, ev)) + + window.addEventListener('keypress', (ev: KeyboardEvent) => { + if (!KEYS_ON) { + return + } + if (ev.code === 'Space') { + addEvent([Protocol.INPUT_EV_TOGGLE_PREVIEW]) + } + + if (MODE === MODE_REPLAY) { + if (ev.code === 'KeyI') { + addEvent([Protocol.INPUT_EV_REPLAY_SPEED_UP]) + } + + if (ev.code === 'KeyO') { + addEvent([Protocol.INPUT_EV_REPLAY_SPEED_DOWN]) + } + + if (ev.code === 'KeyP') { + addEvent([Protocol.INPUT_EV_REPLAY_TOGGLE_PAUSE]) + } + } + if (ev.code === 'KeyF') { + addEvent([Protocol.INPUT_EV_TOGGLE_FIXED_PIECES]) + } + if (ev.code === 'KeyG') { + addEvent([Protocol.INPUT_EV_TOGGLE_LOOSE_PIECES]) + } + if (ev.code === 'KeyM') { + addEvent([Protocol.INPUT_EV_TOGGLE_SOUNDS]) + } + if (ev.code === 'KeyN') { + addEvent([Protocol.INPUT_EV_TOGGLE_PLAYER_NAMES]) + } + if (ev.code === 'KeyC') { + addEvent([Protocol.INPUT_EV_CENTER_FIT_PUZZLE]) + } + }) + + const addEvent = (event: GameEvent) => { + events.push(event) + } + + const consumeAll = (): GameEvent[] => { + if (events.length === 0) { + return [] + } + const all = events.slice() + events = [] + return all + } + + const createKeyEvents = (): void => { + const w = (LEFT ? 1 : 0) - (RIGHT ? 1 : 0) + const h = (UP ? 1 : 0) - (DOWN ? 1 : 0) + if (w !== 0 || h !== 0) { + const amount = (SHIFT ? 24 : 12) * Math.sqrt(viewport.getCurrentZoom()) + const pos = viewport.viewportDimToWorld({w: w * amount, h: h * amount}) + addEvent([Protocol.INPUT_EV_MOVE, pos.w, pos.h]) + if (lastMouse) { + lastMouse[0] -= pos.w + lastMouse[1] -= pos.h + } + } + + if (ZOOM_IN && ZOOM_OUT) { + // cancel each other out + } else if (ZOOM_IN) { + if (viewport.canZoom('in')) { + const target = lastMouse || canvasCenter() + addEvent([Protocol.INPUT_EV_ZOOM_IN, ...target]) + } + } else if (ZOOM_OUT) { + if (viewport.canZoom('out')) { + const target = lastMouse || canvasCenter() + addEvent([Protocol.INPUT_EV_ZOOM_OUT, ...target]) + } + } + } + + const setHotkeys = (state: boolean) => { + KEYS_ON = state + } + + return { + addEvent, + consumeAll, + createKeyEvents, + setHotkeys, + } +} + +export default EventAdapter diff --git a/src/frontend/game.ts b/src/frontend/game.ts index ea457d1..9d7cb47 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -22,9 +22,9 @@ import { EncodedGame, ReplayData, Timestamp, - GameEvent, ServerEvent, } from '../common/Types' +import EventAdapter from './EventAdapter' declare global { interface Window { DEBUG?: boolean @@ -96,180 +96,6 @@ function addCanvasToDom(TARGET_EL: HTMLElement, canvas: HTMLCanvasElement) { return canvas } -function EventAdapter ( - canvas: HTMLCanvasElement, - window: any, - viewport: any, - MODE: string -) { - let events: Array = [] - - let KEYS_ON = true - - let LEFT = false - let RIGHT = false - let UP = false - let DOWN = false - let ZOOM_IN = false - let ZOOM_OUT = false - let SHIFT = false - - const toWorldPoint = (x: number, y: number): [number, number] => { - const pos = viewport.viewportToWorld({x, y}) - return [pos.x, pos.y] - } - - const mousePos = (ev: MouseEvent) => toWorldPoint(ev.offsetX, ev.offsetY) - const canvasCenter = () => toWorldPoint(canvas.width / 2, canvas.height / 2) - - const key = (state: boolean, ev: KeyboardEvent) => { - if (!KEYS_ON) { - return - } - - if (ev.code === 'ShiftLeft' || ev.code === 'ShiftRight') { - SHIFT = state - } else if (ev.code === 'ArrowUp' || ev.code === 'KeyW') { - UP = state - } else if (ev.code === 'ArrowDown' || ev.code === 'KeyS') { - DOWN = state - } else if (ev.code === 'ArrowLeft' || ev.code === 'KeyA') { - LEFT = state - } else if (ev.code === 'ArrowRight' || ev.code === 'KeyD') { - RIGHT = state - } else if (ev.code === 'KeyQ') { - ZOOM_OUT = state - } else if (ev.code === 'KeyE') { - ZOOM_IN = state - } - } - - let lastMouse: [number, number]|null = null - canvas.addEventListener('mousedown', (ev) => { - lastMouse = mousePos(ev) - if (ev.button === 0) { - addEvent([Protocol.INPUT_EV_MOUSE_DOWN, ...lastMouse]) - } - }) - - canvas.addEventListener('mouseup', (ev) => { - lastMouse = mousePos(ev) - if (ev.button === 0) { - addEvent([Protocol.INPUT_EV_MOUSE_UP, ...lastMouse]) - } - }) - - canvas.addEventListener('mousemove', (ev) => { - lastMouse = mousePos(ev) - addEvent([Protocol.INPUT_EV_MOUSE_MOVE, ...lastMouse]) - }) - - canvas.addEventListener('wheel', (ev) => { - lastMouse = mousePos(ev) - if (viewport.canZoom(ev.deltaY < 0 ? 'in' : 'out')) { - const evt = ev.deltaY < 0 - ? Protocol.INPUT_EV_ZOOM_IN - : Protocol.INPUT_EV_ZOOM_OUT - addEvent([evt, ...lastMouse]) - } - }) - - window.addEventListener('keydown', (ev: KeyboardEvent) => key(true, ev)) - window.addEventListener('keyup', (ev: KeyboardEvent) => key(false, ev)) - - window.addEventListener('keypress', (ev: KeyboardEvent) => { - if (!KEYS_ON) { - return - } - if (ev.code === 'Space') { - addEvent([Protocol.INPUT_EV_TOGGLE_PREVIEW]) - } - - if (MODE === MODE_REPLAY) { - if (ev.code === 'KeyI') { - addEvent([Protocol.INPUT_EV_REPLAY_SPEED_UP]) - } - - if (ev.code === 'KeyO') { - addEvent([Protocol.INPUT_EV_REPLAY_SPEED_DOWN]) - } - - if (ev.code === 'KeyP') { - addEvent([Protocol.INPUT_EV_REPLAY_TOGGLE_PAUSE]) - } - } - if (ev.code === 'KeyF') { - PIECE_VIEW_FIXED = !PIECE_VIEW_FIXED - RERENDER = true - } - if (ev.code === 'KeyG') { - PIECE_VIEW_LOOSE = !PIECE_VIEW_LOOSE - RERENDER = true - } - if (ev.code === 'KeyM') { - addEvent([Protocol.INPUT_EV_TOGGLE_SOUNDS]) - } - if (ev.code === 'KeyN') { - addEvent([Protocol.INPUT_EV_TOGGLE_PLAYER_NAMES]) - } - if (ev.code === 'KeyC') { - addEvent([Protocol.INPUT_EV_CENTER_FIT_PUZZLE]) - } - }) - - const addEvent = (event: GameEvent) => { - events.push(event) - } - - const consumeAll = (): GameEvent[] => { - if (events.length === 0) { - return [] - } - const all = events.slice() - events = [] - return all - } - - const createKeyEvents = (): void => { - const w = (LEFT ? 1 : 0) - (RIGHT ? 1 : 0) - const h = (UP ? 1 : 0) - (DOWN ? 1 : 0) - if (w !== 0 || h !== 0) { - const amount = (SHIFT ? 24 : 12) * Math.sqrt(viewport.getCurrentZoom()) - const pos = viewport.viewportDimToWorld({w: w * amount, h: h * amount}) - addEvent([Protocol.INPUT_EV_MOVE, pos.w, pos.h]) - if (lastMouse) { - lastMouse[0] -= pos.w - lastMouse[1] -= pos.h - } - } - - if (ZOOM_IN && ZOOM_OUT) { - // cancel each other out - } else if (ZOOM_IN) { - if (viewport.canZoom('in')) { - const target = lastMouse || canvasCenter() - addEvent([Protocol.INPUT_EV_ZOOM_IN, ...target]) - } - } else if (ZOOM_OUT) { - if (viewport.canZoom('out')) { - const target = lastMouse || canvasCenter() - addEvent([Protocol.INPUT_EV_ZOOM_OUT, ...target]) - } - } - } - - const setHotkeys = (state: boolean) => { - KEYS_ON = state - } - - return { - addEvent, - consumeAll, - createKeyEvents, - setHotkeys, - } -} - export async function main( gameId: string, clientId: string, @@ -746,6 +572,12 @@ export async function main( HUD.togglePlayerNames() } else if (type === Protocol.INPUT_EV_CENTER_FIT_PUZZLE) { centerPuzzle() + } else if (type === Protocol.INPUT_EV_TOGGLE_FIXED_PIECES) { + PIECE_VIEW_FIXED = !PIECE_VIEW_FIXED + RERENDER = true + } else if (type === Protocol.INPUT_EV_TOGGLE_LOOSE_PIECES) { + PIECE_VIEW_LOOSE = !PIECE_VIEW_LOOSE + RERENDER = true } // LOCAL + SERVER CHANGES @@ -818,6 +650,12 @@ export async function main( HUD.togglePlayerNames() } else if (type === Protocol.INPUT_EV_CENTER_FIT_PUZZLE) { centerPuzzle() + } else if (type === Protocol.INPUT_EV_TOGGLE_FIXED_PIECES) { + PIECE_VIEW_FIXED = !PIECE_VIEW_FIXED + RERENDER = true + } else if (type === Protocol.INPUT_EV_TOGGLE_LOOSE_PIECES) { + PIECE_VIEW_LOOSE = !PIECE_VIEW_LOOSE + RERENDER = true } } } From e7628895c91c9b3eafa1b2fc3b2f7b7a0a3131f6 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sun, 11 Jul 2021 17:21:41 +0200 Subject: [PATCH 33/45] sort finished games by finish date --- src/common/GameCommon.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/common/GameCommon.ts b/src/common/GameCommon.ts index 7ce4599..8b7fde7 100644 --- a/src/common/GameCommon.ts +++ b/src/common/GameCommon.ts @@ -138,12 +138,16 @@ function setEvtInfo( function getAllGames(): Array { return Object.values(GAMES).sort((a: Game, b: Game) => { + const finished = isFinished(a.id) // when both have same finished state, sort by started - if (isFinished(a.id) === isFinished(b.id)) { + if (finished === isFinished(b.id)) { + if (finished) { + return b.puzzle.data.finished - a.puzzle.data.finished + } return b.puzzle.data.started - a.puzzle.data.started } // otherwise, sort: unfinished, finished - return isFinished(a.id) ? 1 : -1 + return finished ? 1 : -1 }) } From 8f31a669d5f81b87b5c40d5e1f55d6b0c2c1661d Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sun, 11 Jul 2021 17:48:49 +0200 Subject: [PATCH 34/45] send client id header with every request initiated from frontend to backend --- src/frontend/Communication.ts | 3 ++- src/frontend/components/Upload.vue | 4 ++-- src/frontend/main.ts | 18 +++++++++++------- src/frontend/views/Index.vue | 3 ++- src/frontend/views/NewGame.vue | 8 +++----- src/frontend/xhr.ts | 9 ++++++++- 6 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/frontend/Communication.ts b/src/frontend/Communication.ts index 70a61c8..899a269 100644 --- a/src/frontend/Communication.ts +++ b/src/frontend/Communication.ts @@ -3,6 +3,7 @@ import { ClientEvent, EncodedGame, GameEvent, ReplayData, ServerEvent } from '../common/Types' import Util, { logger } from '../common/Util' import Protocol from './../common/Protocol' +import xhr from './xhr' const log = logger('Communication.js') @@ -117,7 +118,7 @@ async function requestReplayData( offset: number ): Promise { const args = { gameId, offset } - const res = await fetch(`/api/replay-data${Util.asQueryArgs(args)}`) + const res = await xhr.get(`/api/replay-data${Util.asQueryArgs(args)}`, {}) const json: ReplayData = await res.json() return json } diff --git a/src/frontend/components/Upload.vue b/src/frontend/components/Upload.vue index a5a6949..ec030c6 100644 --- a/src/frontend/components/Upload.vue +++ b/src/frontend/components/Upload.vue @@ -6,6 +6,7 @@ + diff --git a/build/server/main.js b/build/server/main.js index 3f60a65..5e3f14e 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -610,12 +610,16 @@ function setEvtInfo(gameId, playerId, evtInfo) { } function getAllGames() { return Object.values(GAMES).sort((a, b) => { + const finished = isFinished(a.id); // when both have same finished state, sort by started - if (isFinished(a.id) === isFinished(b.id)) { + if (finished === isFinished(b.id)) { + if (finished) { + return b.puzzle.data.finished - a.puzzle.data.finished; + } return b.puzzle.data.started - a.puzzle.data.started; } // otherwise, sort: unfinished, finished - return isFinished(a.id) ? 1 : -1; + return finished ? 1 : -1; }); } function getAllPlayers(gameId) { @@ -1439,6 +1443,7 @@ const imageFromDb = (db, imageId) => { const i = db.get('images', { id: imageId }); return { id: i.id, + uploaderUserId: i.uploader_user_id, filename: i.filename, url: `${UPLOAD_URL}/${encodeURIComponent(i.filename)}`, title: i.title, @@ -1477,6 +1482,7 @@ inner join images i on i.id = ixc.image_id ${where.sql}; const images = db.getMany('images', wheresRaw, orderByMap[orderBy]); return images.map(i => ({ id: i.id, + uploaderUserId: i.uploader_user_id, filename: i.filename, url: `${UPLOAD_URL}/${encodeURIComponent(i.filename)}`, title: i.title, @@ -1494,6 +1500,7 @@ const allImagesFromDisk = (tags, sort) => { .filter(f => f.toLowerCase().match(/\.(jpe?g|webp|png)$/)) .map(f => ({ id: 0, + uploaderUserId: null, filename: f, url: `${UPLOAD_URL}/${encodeURIComponent(f)}`, title: f.replace(/\.[a-z]+$/, ''), @@ -2093,6 +2100,16 @@ const storage = multer.diskStorage({ } }); const upload = multer({ storage }).single('file'); +app.get('/api/me', (req, res) => { + let user = db.get('users', { + 'client_id': req.headers['client-id'], + 'client_secret': req.headers['client-secret'], + }); + res.send({ + id: user ? user.id : null, + created: user ? user.created : null, + }); +}); app.get('/api/conf', (req, res) => { res.send({ WS_ADDRESS: config.ws.connectstring, @@ -2164,7 +2181,24 @@ const setImageTags = (db, imageId, tags) => { }); }; app.post('/api/save-image', express.json(), (req, res) => { + let user = db.get('users', { + 'client_id': req.headers['client-id'], + 'client_secret': req.headers['client-secret'], + }); + let userId = null; + if (user) { + userId = parseInt(user.id, 10); + } + else { + res.status(403).send({ ok: false, error: 'forbidden' }); + return; + } const data = req.body; + let image = db.get('images', { id: data.id }); + if (parseInt(image.uploader_user_id, 10) !== userId) { + res.status(403).send({ ok: false, error: 'forbidden' }); + return; + } db.update('images', { title: data.title, }, { @@ -2189,8 +2223,24 @@ app.post('/api/upload', (req, res) => { log.log(err); res.status(400).send("Something went wrong!"); } + let user = db.get('users', { + 'client_id': req.headers['client-id'], + 'client_secret': req.headers['client-secret'], + }); + let userId = null; + if (user) { + userId = user.id; + } + else { + userId = db.insert('users', { + 'client_id': req.headers['client-id'], + 'client_secret': req.headers['client-secret'], + 'created': Time.timestamp(), + }); + } const dim = await Images.getDimensions(`${UPLOAD_DIR}/${req.file.filename}`); const imageId = db.insert('images', { + uploader_user_id: userId, filename: req.file.filename, filename_original: req.file.originalname, title: req.body.title || '', From c11229a5e568a5684bbc0f72be51d88d3738a854 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sun, 11 Jul 2021 21:11:13 +0200 Subject: [PATCH 38/45] fix info overlay --- src/frontend/components/InfoOverlay.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/frontend/components/InfoOverlay.vue b/src/frontend/components/InfoOverlay.vue index efc11fe..1bf6220 100644 --- a/src/frontend/components/InfoOverlay.vue +++ b/src/frontend/components/InfoOverlay.vue @@ -9,15 +9,15 @@ {{game.puzzle.info.image.title}} - Snap Mode: + Scoring: {{scoreMode[0]}} - Shape Mode: + Shapes: {{shapeMode[0]}} - Score Mode: + Snapping: {{snapMode[0]}} From e5fb49ecb1d9ee68f52931a3b869eaba0266aad4 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sun, 11 Jul 2021 21:14:25 +0200 Subject: [PATCH 39/45] build --- build/public/assets/index.97691b3e.js | 1 + build/public/assets/index.cbfa449f.js | 1 - build/public/index.html | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 build/public/assets/index.97691b3e.js delete mode 100644 build/public/assets/index.cbfa449f.js diff --git a/build/public/assets/index.97691b3e.js b/build/public/assets/index.97691b3e.js new file mode 100644 index 0000000..11c7d04 --- /dev/null +++ b/build/public/assets/index.97691b3e.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as l,b as o,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as C,s as x,u as k,x as P,y as A}from"./vendor.684f7bc8.js";var S=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const z={id:"app"},T={key:0,class:"nav"},I=i("Games overview"),E=i("New game");S.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",z,[e.showNav?(s(),t("ul",T,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:l((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:l((()=>[E])),_:1})])])):o("",!0),n(g)])};let M="",D="";const N=async(e,t,n)=>new Promise(((l,o)=>{const a=new window.XMLHttpRequest;a.open(e,t,!0),a.withCredentials=!0;for(const e in n.headers||{})a.setRequestHeader(e,n.headers[e]);a.setRequestHeader("Client-Id",M),a.setRequestHeader("Client-Secret",D),a.addEventListener("load",(function(e){l({status:this.status,text:this.responseText,json:async()=>JSON.parse(this.responseText)})})),a.addEventListener("error",(function(e){o(new Error("xhr error"))})),a.upload&&n.onUploadProgress&&a.upload.addEventListener("progress",(function(e){n.onUploadProgress&&n.onUploadProgress(e)})),a.send(n.body||null)}));var _=(e,t)=>N("get",e,t),V=(e,t)=>N("post",e,t),O=e=>{M=e},B=e=>{D=e};const U=864e5,R=e=>{const t=Math.floor(e/U);e%=U;const n=Math.floor(e/36e5);e%=36e5;const l=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${l}m ${Math.floor(e/1e3)}s`};var $=1,G=1e3,L=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},F=(e,t)=>R(t-e),j=R,W=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",l=e,o=t||L();return`${n} ${F(l,o)}`}}});const H={class:"game-info-text"},K=n("br",null,null,-1),q=n("br",null,null,-1),Y=n("br",null,null,-1),Q=i(" ↪️ Watch replay ");W.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:l((()=>[n("span",H,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),K,i(" 👥 "+r(e.game.players),1),q,i(" "+r(e.time(e.game.started,e.game.finished)),1),Y])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:l((()=>[Q])),_:1},8,["to"])):o("",!0)],4)};var Z=e({components:{GameTeaser:W},data:()=>({gamesRunning:[],gamesFinished:[]}),async created(){const e=await _("/api/index-data",{}),t=await e.json();this.gamesRunning=t.gamesRunning,this.gamesFinished=t.gamesFinished}});const X=n("h1",null,"Running games",-1),J=n("h1",null,"Finished games",-1);Z.render=function(e,l,o,i,r,u){const p=a("game-teaser");return s(),t("div",null,[X,(s(!0),t(d,null,c(e.gamesRunning,((e,l)=>(s(),t("div",{class:"game-teaser-wrap",key:l},[n(p,{game:e},null,8,["game"])])))),128)),J,(s(!0),t(d,null,c(e.gamesFinished,((e,l)=>(s(),t("div",{class:"game-teaser-wrap",key:l},[n(p,{game:e},null,8,["game"])])))),128))])};var ee=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}},canEdit(){return!!this.$me.id&&this.$me.id===this.image.uploaderUserId}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});ee.render=function(e,n,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:n[2]||(n[2]=(...t)=>e.onClick&&e.onClick(...t))},[e.canEdit?(s(),t("div",{key:0,class:"btn edit",onClick:n[1]||(n[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")):o("",!0)],4)};var te=e({name:"image-library",components:{ImageTeaser:ee},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});te.render=function(e,n,l,o,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,l)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:l},null,8,["image","onClick","onEditClick"])))),128))])};class ne{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),l=t[n];t[n]=t[e],t[e]=l}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new ne(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},oe=(...e)=>{const t=t=>(...n)=>{const l=new Date,o=le(l.getHours(),"00"),a=le(l.getMinutes(),"00"),s=le(l.getSeconds(),"00");console[t](`${o}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var ae={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",ne.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode,e.shapeMode,e.snapMode]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:ne.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const l=[n,e[n]].map(encodeURIComponent);t.push(l.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const se={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};se.render=function(e,n,l,o,a,i){return s(),t("div",{style:i.style,title:l.title},null,12,["title"])};var ie=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const re=m();y("data-v-a4fa5e7e");const de={key:0,class:"autocomplete"};f();const ce=re(((e,l,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.input=t),placeholder:"Plants, People",onChange:l[2]||(l[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:l[3]||(l[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:l[4]||(l[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",de,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,l)=>(s(),t("li",{key:l,class:{active:l===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):o("",!0),(s(!0),t(d,null,c(e.values,((n,l)=>(s(),t("span",{key:l,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));ie.render=ce,ie.__scopeId="data-v-a4fa5e7e";const ue=oe("NewImageDialog.vue");var pe=e({name:"new-image-dialog",components:{ResponsiveImage:se,TagsInput:ie},props:{autocompleteTags:{type:Function},uploadProgress:{type:Number},uploading:{type:String}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{uploadProgressPercent(){return this.uploadProgress?Math.round(100*this.uploadProgress):0},canPostToGallery(){return!this.uploading&&!(!this.previewUrl||!this.file)},canSetupGameClick(){return!this.uploading&&!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const l=n[0];return l.type.startsWith("image/")?l:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){ue.info("onDragleave"),this.droppable=!1}}});const ge=n("div",{class:"drop-target"},null,-1),he={key:0,class:"has-image"},me={key:1},ye={class:"upload"},fe=n("span",{class:"btn"},"Upload File",-1),ve={class:"area-settings"},we=n("td",null,[n("label",null,"Title")],-1),be=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ce=n("td",null,[n("label",null,"Tags")],-1),xe={class:"area-buttons"},ke=i("🖼️ Post to gallery"),Pe=i("🧩 Post to gallery "),Ae=n("br",null,null,-1),Se=i(" + set up game");pe.render=function(e,l,o,c,h,m){const y=a("responsive-image"),f=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:l[11]||(l[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[10]||(l[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:l[3]||(l[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:l[4]||(l[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:l[5]||(l[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[ge,e.previewUrl?(s(),t("div",he,[n("span",{class:"remove btn",onClick:l[1]||(l[1]=t=>e.previewUrl="")},"X"),n(y,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",me,[n("label",ye,[n("input",{type:"file",style:{display:"none"},onChange:l[2]||(l[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),fe])]))],34),n("div",ve,[n("table",null,[n("tr",null,[we,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":l[6]||(l[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),be,n("tr",null,[Ce,n("td",null,[n(f,{modelValue:e.tags,"onUpdate:modelValue":l[7]||(l[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",xe,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:l[8]||(l[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},["postToGallery"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[ke],64))],8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:l[9]||(l[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},["setupGame"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Pe,Ae,Se],64))],8,["disabled"])])])])};var ze=e({name:"edit-image-dialog",components:{ResponsiveImage:se,TagsInput:ie},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Te={class:"area-image"},Ie={class:"has-image"},Ee={class:"area-settings"},Me=n("td",null,[n("label",null,"Title")],-1),De=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ne=n("td",null,[n("label",null,"Tags")],-1),_e={class:"area-buttons"};var Ve,Oe,Be,Ue,Re,$e,Ge,Le;ze.render=function(e,l,o,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("div",Te,[n("div",Ie,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ee,[n("table",null,[n("tr",null,[Me,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),De,n("tr",null,[Ne,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[2]||(l[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",_e,[n("button",{class:"btn",onClick:l[3]||(l[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])},(Oe=Ve||(Ve={}))[Oe.Flat=0]="Flat",Oe[Oe.Out=1]="Out",Oe[Oe.In=-1]="In",(Ue=Be||(Be={}))[Ue.FINAL=0]="FINAL",Ue[Ue.ANY=1]="ANY",($e=Re||(Re={}))[$e.NORMAL=0]="NORMAL",$e[$e.ANY=1]="ANY",$e[$e.FLAT=2]="FLAT",(Le=Ge||(Ge={}))[Le.NORMAL=0]="NORMAL",Le[Le.REAL=1]="REAL";var Fe=e({name:"new-game-dialog",components:{ResponsiveImage:se},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Be.ANY,shapeMode:Re.NORMAL,snapMode:Ge.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const je={class:"area-image"},We={class:"has-image"},He={key:0,class:"image-title"},Ke={key:0,class:"image-title-title"},qe={key:1,class:"image-title-dim"},Ye={class:"area-settings"},Qe=n("td",null,[n("label",null,"Pieces")],-1),Ze=n("td",null,[n("label",null,"Scoring: ")],-1),Xe=i(" Any (Score when pieces are connected to each other or on final location)"),Je=n("br",null,null,-1),et=i(" Final (Score when pieces are put to their final location)"),tt=n("td",null,[n("label",null,"Shapes: ")],-1),nt=i(" Normal"),lt=n("br",null,null,-1),ot=i(" Any (flat pieces can occur anywhere)"),at=n("br",null,null,-1),st=i(" Flat (all pieces flat on all sides)"),it=n("td",null,[n("label",null,"Snapping: ")],-1),rt=i(" Normal (pieces snap to final destination and to each other)"),dt=n("br",null,null,-1),ct=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),ut={class:"area-buttons"};Fe.render=function(e,l,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:l[11]||(l[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[10]||(l[10]=u((()=>{}),["stop"]))},[n("div",je,[n("div",We,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title||e.image.width||e.image.height?(s(),t("div",He,[e.image.title?(s(),t("span",Ke,'"'+r(e.image.title)+'"',1)):o("",!0),e.image.width||e.image.height?(s(),t("span",qe,"("+r(e.image.width)+" ✕ "+r(e.image.height)+")",1)):o("",!0)])):o("",!0)]),n("div",Ye,[n("table",null,[n("tr",null,[Qe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[2]||(l[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Xe]),Je,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[3]||(l[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),et])])]),n("tr",null,[tt,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[4]||(l[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),nt]),lt,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[5]||(l[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),ot]),at,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[6]||(l[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),st])])]),n("tr",null,[it,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[7]||(l[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),rt]),dt,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[8]||(l[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),ct])])])])]),n("div",ut,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:l[9]||(l[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var pt=e({components:{ImageLibrary:te,NewImageDialog:pe,EditImageDialog:ze,NewGameDialog:Fe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:"",uploading:"",uploadProgress:0}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await _(`/api/newgame-data${ae.asQueryArgs(this.filters)}`,{}),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){this.uploadProgress=0;const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await V("/api/upload",{body:t,onUploadProgress:e=>{this.uploadProgress=e.loaded/e.total}});return this.uploadProgress=1,await n.json()},async saveImage(e){const t=await V("/api/save-image",{headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){const t=await this.saveImage(e);t.ok?(this.dialog="",await this.loadImages()):alert(t.error)},async postToGalleryClick(e){this.uploading="postToGallery",await this.uploadImage(e),this.uploading="",this.dialog="",await this.loadImages()},async setupGameClick(e){this.uploading="setupGame";const t=await this.uploadImage(e);this.uploading="",this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await V("/api/newgame",{headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const gt={class:"upload-image-teaser"},ht=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),mt={key:0},yt=i(" Tags: "),ft=i(" Sort by: "),vt=n("option",{value:"date_desc"},"Newest first",-1),wt=n("option",{value:"date_asc"},"Oldest first",-1),bt=n("option",{value:"alpha_asc"},"A-Z",-1),Ct=n("option",{value:"alpha_desc"},"Z-A",-1);pt.render=function(e,l,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[n("div",gt,[n("div",{class:"btn btn-big",onClick:l[1]||(l[1]=t=>e.dialog="new-image")},"Upload your image"),ht]),n("div",null,[e.tags.length>0?(s(),t("label",mt,[yt,(s(!0),t(d,null,c(e.relevantTags,((n,l)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:l,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):o("",!0),n("label",null,[ft,p(n("select",{"onUpdate:modelValue":l[2]||(l[2]=t=>e.filters.sort=t),onChange:l[3]||(l[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[vt,wt,bt,Ct],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:l[4]||(l[4]=t=>e.dialog=""),uploadProgress:e.uploadProgress,uploading:e.uploading,onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","uploadProgress","uploading","onPostToGalleryClick","onSetupGameClick"])):o("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:l[5]||(l[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):o("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:l[6]||(l[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):o("",!0)])};var xt=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 kt={class:"scores"},Pt=n("div",null,"Scores",-1),At=n("td",null,"⚡",-1),St=n("td",null,"💤",-1);xt.render=function(e,l,o,a,i,u){return s(),t("div",kt,[Pt,n("table",null,[(s(!0),t(d,null,c(e.actives,((e,l)=>(s(),t("tr",{key:l,style:{color:e.color}},[At,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,l)=>(s(),t("tr",{key:l,style:{color:e.color}},[St,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 j(this.duration)}}});const Tt={class:"timer"};zt.render=function(e,l,o,a,i,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),b(e.$slots,"default")])};var It=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const Et=m();y("data-v-4d56fc17");const Mt=n("td",null,[n("label",null,"Background: ")],-1),Dt=n("td",null,[n("label",null,"Color: ")],-1),Nt=n("td",null,[n("label",null,"Name: ")],-1),_t=n("td",null,[n("label",null,"Sounds: ")],-1),Vt=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Ot={class:"sound-volume"},Bt=n("td",null,[n("label",null,"Show player names: ")],-1);f();const Ut=Et(((e,l,o,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:l[10]||(l[10]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:l[9]||(l[9]=u((()=>{}),["stop"]))},[n("tr",null,[Mt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":l[1]||(l[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[Dt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":l[2]||(l[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[Nt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":l[3]||(l[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[_t,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":l[4]||(l[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[C,e.modelValue.soundsEnabled]])])]),n("tr",null,[Vt,n("td",Ot,[n("span",{onClick:l[5]||(l[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:l[6]||(l[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:l[7]||(l[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])]),n("tr",null,[Bt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":l[8]||(l[8]=t=>e.modelValue.showPlayerNames=t)},null,512),[[C,e.modelValue.showPlayerNames]])])])])]))));It.render=Ut,It.__scopeId="data-v-4d56fc17";var Rt=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const $t={class:"preview"};Rt.render=function(e,l,o,a,i,r){return s(),t("div",{class:"overlay",onClick:l[1]||(l[1]=t=>e.$emit("bgclick"))},[n("div",$t,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Gt=e({name:"help-overlay",emits:{bgclick:null},props:{game:{type:Object,required:!0}},computed:{scoreMode(){switch(this.game.scoreMode){case Be.ANY:return["Any","Score when pieces are connected to each other or on final location"];case Be.FINAL:default:return["Final","Score when pieces are put to their final location"]}},shapeMode(){switch(this.game.shapeMode){case Re.FLAT:return["Flat","all pieces flat on all sides"];case Re.ANY:return["Any","flat pieces can occur anywhere"];case Re.NORMAL:default:return["Normal",""]}},snapMode(){switch(this.game.snapMode){case Ge.REAL:return["Real","pieces snap only to corners, already snapped pieces and to each other"];case Ge.NORMAL:default:return["Normal","pieces snap to final destination and to each other"]}}}});const Lt=n("tr",null,[n("td",{colspan:"2"},"Info about this puzzle")],-1),Ft=n("td",null,"Image Title: ",-1),jt=n("td",null,"Scoring: ",-1),Wt=n("td",null,"Shapes: ",-1),Ht=n("td",null,"Snapping: ",-1);Gt.render=function(e,l,o,a,i,d){return s(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[Lt,n("tr",null,[Ft,n("td",null,r(e.game.puzzle.info.image.title),1)]),n("tr",null,[jt,n("td",null,[n("span",{title:e.snapMode[1]},r(e.scoreMode[0]),9,["title"])])]),n("tr",null,[Wt,n("td",null,[n("span",{title:e.snapMode[1]},r(e.shapeMode[0]),9,["title"])])]),n("tr",null,[Ht,n("td",null,[n("span",{title:e.snapMode[1]},r(e.snapMode[0]),9,["title"])])])])])};var Kt=1,qt=4,Yt=2,Qt=3,Zt=2,Xt=4,Jt=3,en=9,tn=1,nn=2,ln=3,on=4,an=5,sn=6,rn=7,dn=8,cn=10,un=11,pn=12,gn=13,hn=14,mn=15,yn=16,fn=17,vn=18,wn=1,bn=2,Cn=3;const xn=oe("Communication.js");let kn,Pn=[],An=e=>{Pn.push(e)},Sn=[],zn=e=>{Sn.push(e)};let Tn=0;const In=e=>{Tn!==e&&(Tn=e,zn(e))};function En(e){if(2===Tn)try{kn.send(JSON.stringify(e))}catch(t){xn.info("unable to send message.. maybe because ws is invalid?")}}let Mn,Dn;var Nn={connect:function(e,t,n){return Mn=0,Dn={},In(3),new Promise((l=>{kn=new WebSocket(e,n+"|"+t),kn.onopen=()=>{In(2),En([Qt])},kn.onmessage=e=>{const t=JSON.parse(e.data),o=t[0];if(o===qt){const e=t[1];l(e)}else{if(o!==Kt)throw`[ 2021-05-09 invalid connect msgType ${o} ]`;{const e=t[1],l=t[2];if(e===n&&Dn[l])return void delete Dn[l];An(t)}}},kn.onerror=()=>{throw In(1),"[ 2021-05-15 onerror ]"},kn.onclose=e=>{4e3===e.code||1001===e.code?In(4):In(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},l=await _(`/api/replay-data${ae.asQueryArgs(n)}`,{});return await l.json()},disconnect:function(){kn&&kn.close(4e3),Mn=0,Dn={}},sendClientEvent:function(e){Mn++,Dn[Mn]=e,En([Yt,Mn,Dn[Mn]])},onServerChange:function(e){An=e;for(const t of Pn)An(t);Pn=[]},onConnectionStateChange:function(e){zn=e;for(const t of Sn)zn(t);Sn=[]},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},_n=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Nn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Nn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const Vn={key:0,class:"overlay connection-lost"},On={key:0,class:"overlay-content"},Bn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Un={key:1,class:"overlay-content"},Rn=n("div",null,"Connecting...",-1);_n.render=function(e,l,a,i,r,d){return e.show?(s(),t("div",Vn,[e.lostConnection?(s(),t("div",On,[Bn,n("span",{class:"btn",onClick:l[1]||(l[1]=t=>e.$emit("reconnect"))},"Reconnect")])):o("",!0),e.connecting?(s(),t("div",Un,[Rn])):o("",!0)])):o("",!0)};var $n=e({name:"help-overlay",emits:{bgclick:null}});const Gn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),Ln=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),Fn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),jn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),Wn=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Hn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Kn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),qn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Yn=n("tr",null,[n("td",null,"🎯 Center puzzle in screen:"),n("td",null,[n("div",null,[n("kbd",null,"C")])])],-1),Qn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Zn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Xn=n("tr",null,[n("td",null,"👤 Toggle player names:"),n("td",null,[n("div",null,[n("kbd",null,"N")])])],-1),Jn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),el=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),tl=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),nl=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);$n.render=function(e,l,o,a,i,r){return s(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[Gn,Ln,Fn,jn,Wn,Hn,Kn,qn,Yn,Qn,Zn,Xn,Jn,el,tl,nl])])};var ll=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),ol=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),al=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),sl=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),il=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function rl(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var dl={createCanvas:rl,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const l=rl(t,n);return l.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(l)},colorizedCanvas:function(e,t,n){const l=rl(e.width,e.height),o=l.getContext("2d");return o.save(),o.drawImage(t,0,0),o.fillStyle=n,o.globalCompositeOperation="source-in",o.fillRect(0,0,t.width,t.height),o.restore(),o.save(),o.globalCompositeOperation="destination-over",o.drawImage(e,0,0),o.restore(),l}};const cl=oe("Debug.js");let ul=0,pl=0;var gl=e=>{ul=performance.now(),pl=e},hl=e=>{const t=performance.now(),n=t-ul;n>pl&&cl.log(e+": "+n),ul=t};function ml(e,t){const n=e.x-t.x,l=e.y-t.y;return Math.sqrt(n*n+l*l)}function yl(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var fl={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:ml,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:yl,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 ml(yl(e),yl(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const vl=oe("PuzzleGraphics.js");function wl(e,t){const n=ae.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var bl={loadPuzzleBitmaps:async function(e){const t=await dl.loadImageToBitmap(e.info.imageUrl),n=await dl.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){vl.log("start createPuzzleTileBitmaps");const l=n.tileSize,o=n.tileMarginWidth,a=n.tileDrawSize,s=l/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:o,y:o},r=fl.pointAdd(a,{x:l,y:0}),c=fl.pointAdd(r,{x:0,y:l}),u=fl.pointSub(c,{x:l,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let l=0;lae.decodePiece(Cl[e].puzzle.tiles[t]),Ol=(e,t)=>Vl(e,t).group,Bl=(e,t)=>{const n=Cl[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},Ul=(e,t)=>{const n=Cl[e].puzzle.info,l={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},o=function(e,t){const n=Cl[e].puzzle.info,l=ae.coordByPieceIdx(n,t),o=l.x*n.tileSize,a=l.y*n.tileSize;return{x:o,y:a}}(e,t);return fl.pointAdd(l,o)},Rl=(e,t)=>Vl(e,t).pos,$l=e=>{const t=lo(e),n=oo(e),l=Math.round(t/4),o=Math.round(n/4);return{x:0-l,y:0-o,w:t+2*l,h:n+2*o}},Gl=(e,t)=>{const n=Wl(e),l=Vl(e,t);return{x:l.pos.x,y:l.pos.y,w:n,h:n}},Ll=(e,t)=>Vl(e,t).z,Fl=(e,t)=>{for(const n of Cl[e].puzzle.tiles){const e=ae.decodePiece(n);if(e.owner===t)return e.idx}return-1},jl=e=>Cl[e].puzzle.info.tileDrawSize,Wl=e=>Cl[e].puzzle.info.tileSize,Hl=e=>Cl[e].puzzle.data.maxGroup,Kl=e=>Cl[e].puzzle.data.maxZ;function ql(e,t){const n=Cl[e].puzzle.info,l=ae.coordByPieceIdx(n,t);return[l.y>0?t-n.tilesX:-1,l.x0?t-1:-1]}const Yl=(e,t,n)=>{for(const l of t)_l(e,l,{z:n})},Ql=(e,t,n)=>{const l=Rl(e,t);_l(e,t,{pos:fl.pointAdd(l,n)})},Zl=(e,t,n)=>{const l=jl(e),o=$l(e),a=n;for(const s of t){const t=Vl(e,s);t.pos.x+n.xo.x+o.w&&(a.x=Math.min(o.x+o.w-t.pos.x+l,a.x)),t.pos.y+n.yo.y+o.h&&(a.y=Math.min(o.y+o.h-t.pos.y+l,a.y))}for(const s of t)Ql(e,s,a)},Xl=(e,t)=>Vl(e,t).owner,Jl=(e,t)=>{for(const n of t)_l(e,n,{owner:-1,z:1})},eo=(e,t,n)=>{for(const l of t)_l(e,l,{owner:n})};function to(e,t){const n=Cl[e].puzzle.tiles,l=ae.decodePiece(n[t]),o=[];if(l.group)for(const a of n){const e=ae.decodePiece(a);e.group===l.group&&o.push(e.idx)}else o.push(l.idx);return o}const no=(e,t)=>{const n=kl(e,t);return n?n.points:0},lo=e=>Cl[e].puzzle.info.table.width,oo=e=>Cl[e].puzzle.info.table.height;var ao={setGame:function(e,t){Cl[e]=t},exists:function(e){return!!Cl[e]||!1},playerExists:Al,getActivePlayers:function(e,t){const n=t-30*G;return Sl(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*G;return Sl(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Al(e,t)?Dl(e,t,{ts:n}):Pl(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:Ml,getPieceCount:zl,getImageUrl:function(e){var t;const n=(null==(t=Cl[e].puzzle.info.image)?void 0:t.url)||Cl[e].puzzle.info.imageUrl;if(!n)throw new Error("[2021-07-11] no image url set");return n},get:function(e){return Cl[e]||null},getAllGames:function(){return Object.values(Cl).sort(((e,t)=>{const n=El(e.id);return n===El(t.id)?n?t.puzzle.data.finished-e.puzzle.data.finished:t.puzzle.data.started-e.puzzle.data.started:n?1:-1}))},getPlayerBgColor:(e,t)=>{const n=kl(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=kl(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=kl(e,t);return n?n.name:null},getPlayerIndexById:xl,getPlayerIdByIndex:function(e,t){return Cl[e].players.length>t?ae.decodePlayer(Cl[e].players[t]).id:null},changePlayer:Dl,setPlayer:Pl,setPiece:function(e,t,n){Cl[e].puzzle.tiles[t]=ae.encodePiece(n)},setPuzzleData:function(e,t){Cl[e].puzzle.data=t},getTableWidth:lo,getTableHeight:oo,getPuzzle:e=>Cl[e].puzzle,getRng:e=>Cl[e].rng.obj,getPuzzleWidth:e=>Cl[e].puzzle.info.width,getPuzzleHeight:e=>Cl[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Cl[e].puzzle.tiles.map(ae.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Fl(e,t);return n<0?null:Cl[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Cl[e].puzzle.info.tileDrawOffset,getPieceDrawSize:jl,getFinalPiecePos:Ul,getStartTs:e=>Cl[e].puzzle.data.started,getFinishTs:e=>Cl[e].puzzle.data.finished,handleInput:function(e,t,n,l,o){const a=Cl[e].puzzle,s=function(e,t){return t in Cl[e].evtInfos?Cl[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([wn,a.data])},d=t=>{i.push([bn,ae.encodePiece(Vl(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=kl(e,t);n&&i.push([Cn,ae.encodePlayer(n)])},p=n[0];if(p===sn){const o=n[1];Dl(e,t,{bgcolor:o,ts:l}),u()}else if(p===rn){const o=n[1];Dl(e,t,{color:o,ts:l}),u()}else if(p===dn){const o=`${n[1]}`.substr(0,16);Dl(e,t,{name:o,ts:l}),u()}else if(p===en){const o=n[1],a=n[2],s=kl(e,t);if(s){const n=s.x-o,i=s.y-a;Dl(e,t,{ts:l,x:n,y:i}),u()}}else if(p===tn){const o={x:n[1],y:n[2]};Dl(e,t,{d:1,ts:l}),u(),s._last_mouse_down=o;const a=((e,t)=>{const n=Cl[e].puzzle.info,l=Cl[e].puzzle.tiles;let o=-1,a=-1;for(let s=0;so)&&(o=e.z,a=s)}return a})(e,o);if(a>=0){const n=Kl(e)+1;Nl(e,{maxZ:n}),r();const l=to(e,a);Yl(e,l,Kl(e)),eo(e,l,t),c(l)}s._last_mouse=o}else if(p===ln){const o=n[1],a=n[2],i={x:o,y:a};if(null===s._last_mouse_down)Dl(e,t,{x:o,y:a,ts:l}),u();else{const n=Fl(e,t);if(n>=0){Dl(e,t,{x:o,y:a,ts:l}),u();const r=to(e,n);let d=fl.pointInBounds(i,$l(e))&&fl.pointInBounds(s._last_mouse_down,$l(e));for(const t of r){const n=Gl(e,t);if(fl.pointInBounds(i,n)){d=!0;break}}if(d){const t=o-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Zl(e,r,{x:t,y:n}),c(r)}}else Dl(e,t,{ts:l}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===nn){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Fl(e,t);if(g>=0){const n=to(e,g);eo(e,n,0),c(n);const s=Rl(e,g),i=Ul(e,g);let h=!1;if(Il(e)===Ge.REAL){for(const t of n)if(Bl(e,t)){h=!0;break}}else h=!0;if(h&&fl.pointDistance(i,s){const o=Cl[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const l=Ol(e,t),o=Ol(e,n);return!(!l||l!==o)})(e,t,n))return!1;const a=Rl(e,t),s=fl.pointAdd(Rl(e,n),{x:l[0]*o.tileSize,y:l[1]*o.tileSize});if(fl.pointDistance(a,s){const l=Cl[e].puzzle.tiles,o=Ol(e,t),a=Ol(e,n);let s;const i=[];o&&i.push(o),a&&i.push(a),o?s=o:a?s=a:(Nl(e,{maxGroup:Hl(e)+1}),r(),s=Hl(e));if(_l(e,t,{group:s}),d(t),_l(e,n,{group:s}),d(n),i.length>0)for(const r of l){const t=ae.decodePiece(r);i.includes(t.group)&&(_l(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),o=to(e,t),((e,t)=>-1===Xl(e,t))(e,n))Jl(e,o);else{const t=((e,t)=>{let n=0;for(const l of t){const t=Ll(e,l);t>n&&(n=t)}return n})(e,o);Yl(e,o,t)}return c(o),!0}return!1};let a=!1;for(const t of to(e,g)){const l=ql(e,t);if(n(e,t,l[0],[0,1])||n(e,t,l[1],[-1,0])||n(e,t,l[2],[0,-1])||n(e,t,l[3],[1,0])){a=!0;break}}if(a&&Tl(e)===Be.ANY){const n=no(e,t)+1;Dl(e,t,{d:p,ts:l,points:n}),u()}else Dl(e,t,{d:p,ts:l}),u();a&&Il(e)===Ge.REAL&&Ml(e)===zl(e)&&(Nl(e,{finished:l}),r()),a&&o&&o(t)}}else Dl(e,t,{d:p,ts:l}),u();s._last_mouse=i}else if(p===on){const o=n[1],a=n[2];Dl(e,t,{x:o,y:a,ts:l}),u(),s._last_mouse={x:o,y:a}}else if(p===an){const o=n[1],a=n[2];Dl(e,t,{x:o,y:a,ts:l}),u(),s._last_mouse={x:o,y:a}}else Dl(e,t,{ts:l}),u();return function(e,t,n){Cl[e].evtInfos[t]=n}(e,t,s),i}};let so=-10,io=20,ro=2,co=15;class uo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=so+Math.random()*io,this.vy=-1*(ro+Math.random()*co),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;ro=t/2,co=t-ro;const n=1/4*this.canvas.width/(t/2);so=-n,io=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new uo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new uo(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{localStorage.setItem(e,t)},Co=e=>localStorage.getItem(e);var xo=(e,t)=>{bo(e,`${t}`)},ko=(e,t)=>{const n=Co(e);if(null===n)return t;const l=parseInt(n,10);return isNaN(l)?t:l},Po=(e,t)=>{bo(e,t?"1":"0")},Ao=(e,t)=>{const n=Co(e);return null===n?t:"1"===n},So=(e,t)=>{bo(e,t)},zo=(e,t)=>{const n=Co(e);return null===n?t:n};const To={"./grab.png":ol,"./grab_mask.png":al,"./hand.png":sl,"./hand_mask.png":il},Io={"./click.mp3":ll},Eo="replay";let Mo=!0,Do=!0;let No=!0;async function _o(e,t,n,l,o,a){void 0===window.DEBUG&&(window.DEBUG=!1);const s=Io["./click.mp3"].default,i=new Audio(s),r=await dl.loadImageToBitmap(To["./grab.png"].default),d=await dl.loadImageToBitmap(To["./hand.png"].default),c=await dl.loadImageToBitmap(To["./grab_mask.png"].default),u=await dl.loadImageToBitmap(To["./hand_mask.png"].default),p=r.width,g=Math.round(p/2),h=r.height,m=Math.round(h/2),y={},f=async e=>{const t=e.color+" "+e.d;if(!y[t]){const n=e.d?r:d;if(e.color){const l=e.d?c:u;y[t]=await createImageBitmap(dl.colorizedCanvas(n,l,e.color))}else y[t]=n}return y[t]},v=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,No=!0})),t}(o,dl.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};Nn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await Nn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let C=()=>0;const x=async()=>{if("play"===l){const l=await Nn.connect(n,e,t),o=ae.decodeGame(l);ao.setGame(o.id,o),C=()=>L()}else{if(l!==Eo)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=ae.decodeGame(t.game);ao.setGame(n.id,n),w.lastRealTs=L(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,C=()=>w.lastGameTs}}No=!0};await x();const k=ao.getPieceDrawOffset(e),P=ao.getPieceDrawSize(e),A=ao.getPuzzleWidth(e),S=ao.getPuzzleHeight(e),z=ao.getTableWidth(e),T=ao.getTableHeight(e),I={x:(z-A)/2,y:(T-S)/2},E={w:A,h:S},M={w:P,h:P},D=await bl.loadPuzzleBitmaps(ao.getPuzzle(e)),N=new go(v,ao.getRng(e));N.init();const _=v.getContext("2d");v.classList.add("loaded"),a.setPuzzleCut();const V=function(){let e=0,t=0,n=1;const l=(l,o)=>{e+=l/n,t+=o/n},o=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},a=(e,t)=>{if(n==e)return!1;const o=1-n/e;return l(-t.x*o,-t.y*o),n=e,!0},s=l=>({x:l.x/n-e,y:l.y/n-t}),i=l=>({x:(l.x+e)*n,y:(l.y+t)*n}),r=e=>({w:e.w*n,h:e.h*n}),d=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,reset:()=>{e=0,t=0,n=1},move:l,canZoom:e=>n!=o(e),zoom:(e,t)=>a(o(e),t),setZoom:a,worldToViewport:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:r,viewportToWorld:e=>{const{x:t,y:n}=s(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:s,viewportDimToWorld:e=>{const{w:t,h:n}=d(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:d}}(),O=()=>{V.reset(),V.move(-(z-v.width)/2,-(T-v.height)/2);const e=V.worldDimToViewport(E),t=v.width-40,n=v.height-40;if(e.w>t||e.h>n||e.w{const l=n.viewportToWorld({x:e,y:t});return[l.x,l.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([tn,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([nn,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([ln,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?on:an;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([cn]),l===Eo&&("KeyI"===e.code&&v([gn]),"KeyO"===e.code&&v([hn]),"KeyP"===e.code&&v([pn])),"KeyF"===e.code&&v([fn]),"KeyG"===e.code&&v([vn]),"KeyM"===e.code&&v([un]),"KeyN"===e.code&&v([mn]),"KeyC"===e.code&&v([yn]))}));const v=e=>{o.push(e)};return{addEvent:v,consumeAll:()=>{if(0===o.length)return[];const e=o.slice();return o=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const l=(p?24:12)*Math.sqrt(n.getCurrentZoom()),o=n.viewportDimToWorld({w:e*l,h:t*l});v([en,o.w,o.h]),f&&(f[0]-=o.w,f[1]-=o.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([on,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([an,...e])}},setHotkeys:e=>{a=e}}}(v,window,V,l),U=ao.getImageUrl(e),R=()=>{const t=ao.getStartTs(e),n=ao.getFinishTs(e),l=C();a.setFinished(!!n),a.setDuration((n||l)-t)};R(),a.setPiecesDone(ao.getFinishedPiecesCount(e)),a.setPiecesTotal(ao.getPieceCount(e));const G=C();a.setActivePlayers(ao.getActivePlayers(e,G)),a.setIdlePlayers(ao.getIdlePlayers(e,G));const F=!!ao.getFinishTs(e);let j=F;const W=()=>j&&!F,H=()=>ko(ho,100),K=()=>Ao(mo,!1),q=()=>Ao(wo,!0),Y=()=>{const e=H();i.volume=e/100,i.play()},Q=()=>l===Eo?zo(yo,"#222222"):ao.getPlayerBgColor(e,t)||zo(yo,"#222222"),Z=()=>l===Eo?zo(fo,"#ffffff"):ao.getPlayerColor(e,t)||zo(fo,"#ffffff");let X="",J="",ee=!1;const te=e=>{ee=e;const[t,n]=e?[X,"grab"]:[J,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},ne=e=>{X=dl.colorizedCanvas(r,c,e).toDataURL(),J=dl.colorizedCanvas(d,u,e).toDataURL(),te(ee)};ne(Z());const le=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},oe=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,le())},ie=()=>{w.paused=!w.paused,le()},re=[];let de;let ce;if("play"===l?re.push(setInterval((()=>{R()}),1e3)):l===Eo&&le(),"play"===l)Nn.onServerChange((n=>{n[0],n[1],n[2];const l=n[3];for(const[o,a]of l)switch(o){case Cn:{const n=ae.decodePlayer(a);n.id!==t&&(ao.setPlayer(e,n.id,n),No=!0)}break;case bn:{const t=ae.decodePiece(a);ao.setPiece(e,t.idx,t),No=!0}break;case wn:ao.setPuzzleData(e,a),No=!0}j=!!ao.getFinishTs(e)}));else if(l===Eo){const t=(t,n)=>{const l=t;if(l[0]===Zt){const t=l[1];return ao.addPlayer(e,t,n),!0}if(l[0]===Xt){const t=ao.getPlayerIdByIndex(e,l[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return ao.addPlayer(e,t,n),!0}if(l[0]===Jt){const t=ao.getPlayerIdByIndex(e,l[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const o=l[2];return ao.handleInput(e,t,o,n),!0}return!1};let n=w.lastGameTs;const l=async()=>{w.logPointer+1>=w.log.length&&await b(e);const o=L();if(w.paused)return w.lastRealTs=o,void(de=setTimeout(l,50));const a=(o-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const l=w.log[w.logPointer],o=n+l[l.length-1],a=w.log[e],i=a[a.length-1],r=o+i;if(r>s){s+500*${let t=!1;const n=e.fps||60,l=e.slow||1,o=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=l*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,o(i);a(c/l),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{B.createKeyEvents();for(const n of B.consumeAll())if("play"===l){const l=n[0];if(l===en){const e=n[1],t=n[2],l=V.worldDimToViewport({w:e,h:t});No=!0,V.move(l.w,l.h)}else if(l===ln){if(ue&&!ao.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=V.worldToViewport(e),l=Math.round(t.x-ue.x),o=Math.round(t.y-ue.y);No=!0,V.move(l,o),ue=t}}else if(l===rn)ne(n[1]);else if(l===tn){const e={x:n[1],y:n[2]};ue=V.worldToViewport(e),te(!0)}else if(l===nn)ue=null,te(!1);else if(l===on){const e={x:n[1],y:n[2]};No=!0,V.zoom("in",V.worldToViewport(e))}else if(l===an){const e={x:n[1],y:n[2]};No=!0,V.zoom("out",V.worldToViewport(e))}else l===cn?a.togglePreview():l===un?a.toggleSoundsEnabled():l===mn?a.togglePlayerNames():l===yn?O():l===fn?(Mo=!Mo,No=!0):l===vn&&(Do=!Do,No=!0);const o=C();ao.handleInput(e,t,n,o,(e=>{K()&&Y()})).length>0&&(No=!0),Nn.sendClientEvent(n)}else if(l===Eo){const e=n[0];if(e===pn)ie();else if(e===hn)se();else if(e===gn)oe();else if(e===en){const e=n[1],t=n[2];No=!0,V.move(e,t)}else if(e===ln){if(ue){const e={x:n[1],y:n[2]},t=V.worldToViewport(e),l=Math.round(t.x-ue.x),o=Math.round(t.y-ue.y);No=!0,V.move(l,o),ue=t}}else if(e===rn)ne(n[1]);else if(e===tn){const e={x:n[1],y:n[2]};ue=V.worldToViewport(e),te(!0)}else if(e===nn)ue=null,te(!1);else if(e===on){const e={x:n[1],y:n[2]};No=!0,V.zoom("in",V.worldToViewport(e))}else if(e===an){const e={x:n[1],y:n[2]};No=!0,V.zoom("out",V.worldToViewport(e))}else e===cn?a.togglePreview():e===un?a.toggleSoundsEnabled():e===mn?a.togglePlayerNames():e===yn?O():e===fn?(Mo=!Mo,No=!0):e===vn&&(Do=!Do,No=!0)}j=!!ao.getFinishTs(e),W()&&(N.update(),No=!0)},render:async()=>{if(!No)return;const n=C();let o,s,i;window.DEBUG&&gl(0),_.fillStyle=Q(),_.fillRect(0,0,v.width,v.height),window.DEBUG&&hl("clear done"),o=V.worldToViewportRaw(I),s=V.worldDimToViewportRaw(E),_.fillStyle="rgba(255, 255, 255, .3)",_.fillRect(o.x,o.y,s.w,s.h),window.DEBUG&&hl("board done");const r=ao.getPiecesSortedByZIndex(e);window.DEBUG&&hl("get tiles done"),s=V.worldDimToViewportRaw(M);for(const e of r)(-1===e.owner?Mo:Do)&&(i=D[e.idx],o=V.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),_.drawImage(i,0,0,i.width,i.height,o.x,o.y,s.w,s.h));window.DEBUG&&hl("tiles done");const d=[];for(const a of ao.getActivePlayers(e,n))c=a,(l===Eo||c.id!==t)&&(i=await f(a),o=V.worldToViewport(a),_.drawImage(i,o.x-g,o.y-m),q()&&d.push([`${a.name} (${a.points})`,o.x,o.y+h]));var c;_.fillStyle="white",_.textAlign="center";for(const[e,t,l]of d)_.fillText(e,t,l);window.DEBUG&&hl("players done"),a.setActivePlayers(ao.getActivePlayers(e,n)),a.setIdlePlayers(ao.getIdlePlayers(e,n)),a.setPiecesDone(ao.getFinishedPiecesCount(e)),window.DEBUG&&hl("HUD done"),W()&&N.render(),No=!1}}),{setHotkeys:e=>{B.setHotkeys(e)},onBgChange:e=>{So(yo,e),B.addEvent([sn,e])},onColorChange:e=>{So(fo,e),B.addEvent([rn,e])},onNameChange:e=>{So(vo,e),B.addEvent([dn,e])},onSoundsEnabledChange:e=>{Po(mo,e)},onSoundsVolumeChange:e=>{xo(ho,e),Y()},onShowPlayerNamesChange:e=>{Po(wo,e)},replayOnSpeedUp:oe,replayOnSpeedDown:se,replayOnPauseToggle:ie,previewImageUrl:U,player:{background:Q(),color:Z(),name:l===Eo?zo(vo,"anon"):ao.getPlayerName(e,t)||zo(vo,"anon"),soundsEnabled:K(),soundsVolume:H(),showPlayerNames:q()},game:ao.get(e),disconnect:Nn.disconnect,connect:x,unload:()=>{re.forEach((e=>{clearInterval(e)})),de&&clearTimeout(de),ce&&ce.stop()}}}var Vo=e({name:"game",components:{PuzzleStatus:zt,Scores:xt,SettingsOverlay:It,PreviewOverlay:Rt,InfoOverlay:Gt,ConnectionOverlay:_n,HelpOverlay:$n},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,cuttingPuzzle:!0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},game:null,previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await _o(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setPuzzleCut:()=>{this.cuttingPuzzle=!1},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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 Oo={id:"game"},Bo={key:1,class:"overlay"},Uo=n("div",{class:"overlay-content"},[n("div",null,"⏳ Cutting puzzle, please wait... ⏳")],-1),Ro={class:"menu"},$o={class:"tabs"},Go=i("🧩 Puzzles");Vo.render=function(e,i,r,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("info-overlay"),y=a("help-overlay"),f=a("connection-overlay"),v=a("puzzle-status"),w=a("router-link"),b=a("scores");return s(),t("div",Oo,[p(n(g,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),e.g.game?p((s(),t(m,{key:0,onBgclick:i[4]||(i[4]=t=>e.toggle("info",!0)),game:e.g.game},null,8,["game"])),[[x,"info"===e.overlay]]):o("",!0),p(n(y,{onBgclick:i[5]||(i[5]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),e.cuttingPuzzle?(s(),t("div",Bo,[Uo])):o("",!0),n(f,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(v,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",Ro,[n("div",$o,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[Go])),_:1}),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("info",!0))},"ℹ️ Info"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("help",!0))},"⌨️ Hotkeys")])]),n(b,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var Lo=e({name:"replay",components:{PuzzleStatus:zt,Scores:xt,SettingsOverlay:It,PreviewOverlay:Rt,InfoOverlay:Gt,HelpOverlay:$n},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,cuttingPuzzle:!0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},game:null,previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await _o(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,Eo,this.$el,{setPuzzleCut:()=>{this.cuttingPuzzle=!1},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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 Fo={id:"replay"},jo={key:1,class:"overlay"},Wo=n("div",{class:"overlay-content"},[n("div",null,"⏳ Cutting puzzle, please wait... ⏳")],-1),Ho={class:"menu"},Ko={class:"tabs"},qo=i("🧩 Puzzles");Lo.render=function(e,i,d,c,u,g){const h=a("settings-overlay"),m=a("preview-overlay"),y=a("info-overlay"),f=a("help-overlay"),v=a("puzzle-status"),w=a("router-link"),b=a("scores");return s(),t("div",Fo,[p(n(h,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(m,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),e.g.game?p((s(),t(y,{key:0,onBgclick:i[4]||(i[4]=t=>e.toggle("info",!0)),game:e.g.game},null,8,["game"])),[[x,"info"===e.overlay]]):o("",!0),p(n(f,{onBgclick:i[5]||(i[5]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),e.cuttingPuzzle?(s(),t("div",jo,[Wo])):o("",!0),n(v,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:l((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[8]||(i[8]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",Ho,[n("div",Ko,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[qo])),_:1}),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[11]||(i[11]=t=>e.toggle("info",!0))},"ℹ️ Info"),n("div",{class:"opener",onClick:i[12]||(i[12]=t=>e.toggle("help",!0))},"⌨️ Hotkeys")])]),n(b,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=function(){let e=zo("ID","");return e||(e=ae.uniqId(),So("ID",e)),e}(),t=function(){let e=zo("SECRET","");return e||(e=ae.uniqId(),So("SECRET",e)),e}();O(e),B(t);const n=await _("/api/me",{}),l=await n.json(),o=await _("/api/conf",{}),a=await o.json(),s=k({history:P(),routes:[{name:"index",path:"/",component:Z},{name:"new-game",path:"/new-game",component:pt},{name:"game",path:"/g/:id",component:Vo},{name:"replay",path:"/replay/:id",component:Lo}]});s.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const i=A(S);i.config.globalProperties.$me=l,i.config.globalProperties.$config=a,i.config.globalProperties.$clientId=e,i.use(s),i.mount("#app")})(); diff --git a/build/public/assets/index.cbfa449f.js b/build/public/assets/index.cbfa449f.js deleted file mode 100644 index ad9c29a..0000000 --- a/build/public/assets/index.cbfa449f.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as C,s as x,u as k,x as P,y as A}from"./vendor.684f7bc8.js";var S=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const z={id:"app"},T={key:0,class:"nav"},I=i("Games overview"),M=i("New game");S.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",z,[e.showNav?(s(),t("ul",T,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[M])),_:1})])])):l("",!0),n(g)])};let E="",D="";const N=async(e,t,n)=>new Promise(((o,l)=>{const a=new window.XMLHttpRequest;a.open(e,t,!0),a.withCredentials=!0;for(const e in n.headers||{})a.setRequestHeader(e,n.headers[e]);a.setRequestHeader("Client-Id",E),a.setRequestHeader("Client-Secret",D),a.addEventListener("load",(function(e){o({status:this.status,text:this.responseText,json:async()=>JSON.parse(this.responseText)})})),a.addEventListener("error",(function(e){l(new Error("xhr error"))})),a.upload&&n.onUploadProgress&&a.upload.addEventListener("progress",(function(e){n.onUploadProgress&&n.onUploadProgress(e)})),a.send(n.body||null)}));var _=(e,t)=>N("get",e,t),V=(e,t)=>N("post",e,t),O=e=>{E=e},B=e=>{D=e};const U=864e5,R=e=>{const t=Math.floor(e/U);e%=U;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 $=1,G=1e3,L=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},F=(e,t)=>R(t-e),j=R,W=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||L();return`${n} ${F(o,l)}`}}});const H={class:"game-info-text"},K=n("br",null,null,-1),q=n("br",null,null,-1),Y=n("br",null,null,-1),Q=i(" ↪️ Watch replay ");W.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",H,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),K,i(" 👥 "+r(e.game.players),1),q,i(" "+r(e.time(e.game.started,e.game.finished)),1),Y])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[Q])),_:1},8,["to"])):l("",!0)],4)};var Z=e({components:{GameTeaser:W},data:()=>({gamesRunning:[],gamesFinished:[]}),async created(){const e=await _("/api/index-data",{}),t=await e.json();this.gamesRunning=t.gamesRunning,this.gamesFinished=t.gamesFinished}});const X=n("h1",null,"Running games",-1),J=n("h1",null,"Finished games",-1);Z.render=function(e,o,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[X,(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)),J,(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 ee=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}},canEdit(){return!!this.$me.id&&this.$me.id===this.image.uploaderUserId}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});ee.render=function(e,n,o,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:n[2]||(n[2]=(...t)=>e.onClick&&e.onClick(...t))},[e.canEdit?(s(),t("div",{key:0,class:"btn edit",onClick:n[1]||(n[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")):l("",!0)],4)};var te=e({name:"image-library",components:{ImageTeaser:ee},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});te.render=function(e,n,o,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])};class ne{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new ne(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const oe=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},le=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=oe(o.getHours(),"00"),a=oe(o.getMinutes(),"00"),s=oe(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var ae={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",ne.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode,e.shapeMode,e.snapMode]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:ne.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const se={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};se.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var ie=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const re=m();y("data-v-a4fa5e7e");const de={key:0,class:"autocomplete"};f();const ce=re(((e,o,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",de,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));ie.render=ce,ie.__scopeId="data-v-a4fa5e7e";const ue=le("NewImageDialog.vue");var pe=e({name:"new-image-dialog",components:{ResponsiveImage:se,TagsInput:ie},props:{autocompleteTags:{type:Function},uploadProgress:{type:Number},uploading:{type:String}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{uploadProgressPercent(){return this.uploadProgress?Math.round(100*this.uploadProgress):0},canPostToGallery(){return!this.uploading&&!(!this.previewUrl||!this.file)},canSetupGameClick(){return!this.uploading&&!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){ue.info("onDragleave"),this.droppable=!1}}});const ge=n("div",{class:"drop-target"},null,-1),he={key:0,class:"has-image"},me={key:1},ye={class:"upload"},fe=n("span",{class:"btn"},"Upload File",-1),ve={class:"area-settings"},we=n("td",null,[n("label",null,"Title")],-1),be=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ce=n("td",null,[n("label",null,"Tags")],-1),xe={class:"area-buttons"},ke=i("🖼️ Post to gallery"),Pe=i("🧩 Post to gallery "),Ae=n("br",null,null,-1),Se=i(" + set up game");pe.render=function(e,o,l,c,h,m){const y=a("responsive-image"),f=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[ge,e.previewUrl?(s(),t("div",he,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(y,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",me,[n("label",ye,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),fe])]))],34),n("div",ve,[n("table",null,[n("tr",null,[we,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),be,n("tr",null,[Ce,n("td",null,[n(f,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",xe,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},["postToGallery"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[ke],64))],8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},["setupGame"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Pe,Ae,Se],64))],8,["disabled"])])])])};var ze=e({name:"edit-image-dialog",components:{ResponsiveImage:se,TagsInput:ie},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Te={class:"area-image"},Ie={class:"has-image"},Me={class:"area-settings"},Ee=n("td",null,[n("label",null,"Title")],-1),De=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ne=n("td",null,[n("label",null,"Tags")],-1),_e={class:"area-buttons"};var Ve,Oe,Be,Ue,Re,$e,Ge,Le;ze.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Te,[n("div",Ie,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Me,[n("table",null,[n("tr",null,[Ee,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),De,n("tr",null,[Ne,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",_e,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])},(Oe=Ve||(Ve={}))[Oe.Flat=0]="Flat",Oe[Oe.Out=1]="Out",Oe[Oe.In=-1]="In",(Ue=Be||(Be={}))[Ue.FINAL=0]="FINAL",Ue[Ue.ANY=1]="ANY",($e=Re||(Re={}))[$e.NORMAL=0]="NORMAL",$e[$e.ANY=1]="ANY",$e[$e.FLAT=2]="FLAT",(Le=Ge||(Ge={}))[Le.NORMAL=0]="NORMAL",Le[Le.REAL=1]="REAL";var Fe=e({name:"new-game-dialog",components:{ResponsiveImage:se},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Be.ANY,shapeMode:Re.NORMAL,snapMode:Ge.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const je={class:"area-image"},We={class:"has-image"},He={key:0,class:"image-title"},Ke={key:0,class:"image-title-title"},qe={key:1,class:"image-title-dim"},Ye={class:"area-settings"},Qe=n("td",null,[n("label",null,"Pieces")],-1),Ze=n("td",null,[n("label",null,"Scoring: ")],-1),Xe=i(" Any (Score when pieces are connected to each other or on final location)"),Je=n("br",null,null,-1),et=i(" Final (Score when pieces are put to their final location)"),tt=n("td",null,[n("label",null,"Shapes: ")],-1),nt=i(" Normal"),ot=n("br",null,null,-1),lt=i(" Any (flat pieces can occur anywhere)"),at=n("br",null,null,-1),st=i(" Flat (all pieces flat on all sides)"),it=n("td",null,[n("label",null,"Snapping: ")],-1),rt=i(" Normal (pieces snap to final destination and to each other)"),dt=n("br",null,null,-1),ct=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),ut={class:"area-buttons"};Fe.render=function(e,o,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",je,[n("div",We,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title||e.image.width||e.image.height?(s(),t("div",He,[e.image.title?(s(),t("span",Ke,'"'+r(e.image.title)+'"',1)):l("",!0),e.image.width||e.image.height?(s(),t("span",qe,"("+r(e.image.width)+" ✕ "+r(e.image.height)+")",1)):l("",!0)])):l("",!0)]),n("div",Ye,[n("table",null,[n("tr",null,[Qe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Xe]),Je,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),et])])]),n("tr",null,[tt,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),nt]),ot,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),lt]),at,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),st])])]),n("tr",null,[it,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),rt]),dt,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),ct])])])])]),n("div",ut,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var pt=e({components:{ImageLibrary:te,NewImageDialog:pe,EditImageDialog:ze,NewGameDialog:Fe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:"",uploading:"",uploadProgress:0}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await _(`/api/newgame-data${ae.asQueryArgs(this.filters)}`,{}),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){this.uploadProgress=0;const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await V("/api/upload",{body:t,onUploadProgress:e=>{this.uploadProgress=e.loaded/e.total}});return this.uploadProgress=1,await n.json()},async saveImage(e){const t=await V("/api/save-image",{headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){const t=await this.saveImage(e);t.ok?(this.dialog="",await this.loadImages()):alert(t.error)},async postToGalleryClick(e){this.uploading="postToGallery",await this.uploadImage(e),this.uploading="",this.dialog="",await this.loadImages()},async setupGameClick(e){this.uploading="setupGame";const t=await this.uploadImage(e);this.uploading="",this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await V("/api/newgame",{headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const gt={class:"upload-image-teaser"},ht=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),mt={key:0},yt=i(" Tags: "),ft=i(" Sort by: "),vt=n("option",{value:"date_desc"},"Newest first",-1),wt=n("option",{value:"date_asc"},"Oldest first",-1),bt=n("option",{value:"alpha_asc"},"A-Z",-1),Ct=n("option",{value:"alpha_desc"},"Z-A",-1);pt.render=function(e,o,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[n("div",gt,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),ht]),n("div",null,[e.tags.length>0?(s(),t("label",mt,[yt,(s(!0),t(d,null,c(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[ft,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[vt,wt,bt,Ct],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),uploadProgress:e.uploadProgress,uploading:e.uploading,onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","uploadProgress","uploading","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var xt=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 kt={class:"scores"},Pt=n("div",null,"Scores",-1),At=n("td",null,"⚡",-1),St=n("td",null,"💤",-1);xt.render=function(e,o,l,a,i,u){return s(),t("div",kt,[Pt,n("table",null,[(s(!0),t(d,null,c(e.actives,((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)),(s(!0),t(d,null,c(e.idles,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[St,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 j(this.duration)}}});const Tt={class:"timer"};zt.render=function(e,o,l,a,i,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),b(e.$slots,"default")])};var It=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const Mt=m();y("data-v-4d56fc17");const Et=n("td",null,[n("label",null,"Background: ")],-1),Dt=n("td",null,[n("label",null,"Color: ")],-1),Nt=n("td",null,[n("label",null,"Name: ")],-1),_t=n("td",null,[n("label",null,"Sounds: ")],-1),Vt=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Ot={class:"sound-volume"},Bt=n("td",null,[n("label",null,"Show player names: ")],-1);f();const Ut=Mt(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[10]||(o[10]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[9]||(o[9]=u((()=>{}),["stop"]))},[n("tr",null,[Et,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[Dt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[Nt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[_t,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[C,e.modelValue.soundsEnabled]])])]),n("tr",null,[Vt,n("td",Ot,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])]),n("tr",null,[Bt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[8]||(o[8]=t=>e.modelValue.showPlayerNames=t)},null,512),[[C,e.modelValue.showPlayerNames]])])])])]))));It.render=Ut,It.__scopeId="data-v-4d56fc17";var Rt=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const $t={class:"preview"};Rt.render=function(e,o,l,a,i,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)])])};var Gt=e({name:"help-overlay",emits:{bgclick:null},props:{game:{type:Object,required:!0}},computed:{scoreMode(){switch(this.game.scoreMode){case Be.ANY:return["Any","Score when pieces are connected to each other or on final location"];case Be.FINAL:default:return["Final","Score when pieces are put to their final location"]}},shapeMode(){switch(this.game.shapeMode){case Re.FLAT:return["Flat","all pieces flat on all sides"];case Re.ANY:return["Any","flat pieces can occur anywhere"];case Re.NORMAL:default:return["Normal",""]}},snapMode(){switch(this.game.snapMode){case Ge.REAL:return["Real","pieces snap only to corners, already snapped pieces and to each other"];case Ge.NORMAL:default:return["Normal","pieces snap to final destination and to each other"]}}}});const Lt=n("tr",null,[n("td",{colspan:"2"},"Info about this puzzle")],-1),Ft=n("td",null,"Image Title: ",-1),jt=n("td",null,"Snap Mode: ",-1),Wt=n("td",null,"Shape Mode: ",-1),Ht=n("td",null,"Score Mode: ",-1);Gt.render=function(e,o,l,a,i,d){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]=u((()=>{}),["stop"]))},[Lt,n("tr",null,[Ft,n("td",null,r(e.game.puzzle.info.image.title),1)]),n("tr",null,[jt,n("td",null,[n("span",{title:e.snapMode[1]},r(e.scoreMode[0]),9,["title"])])]),n("tr",null,[Wt,n("td",null,[n("span",{title:e.snapMode[1]},r(e.shapeMode[0]),9,["title"])])]),n("tr",null,[Ht,n("td",null,[n("span",{title:e.snapMode[1]},r(e.snapMode[0]),9,["title"])])])])])};var Kt=1,qt=4,Yt=2,Qt=3,Zt=2,Xt=4,Jt=3,en=9,tn=1,nn=2,on=3,ln=4,an=5,sn=6,rn=7,dn=8,cn=10,un=11,pn=12,gn=13,hn=14,mn=15,yn=16,fn=17,vn=18,wn=1,bn=2,Cn=3;const xn=le("Communication.js");let kn,Pn=[],An=e=>{Pn.push(e)},Sn=[],zn=e=>{Sn.push(e)};let Tn=0;const In=e=>{Tn!==e&&(Tn=e,zn(e))};function Mn(e){if(2===Tn)try{kn.send(JSON.stringify(e))}catch(t){xn.info("unable to send message.. maybe because ws is invalid?")}}let En,Dn;var Nn={connect:function(e,t,n){return En=0,Dn={},In(3),new Promise((o=>{kn=new WebSocket(e,n+"|"+t),kn.onopen=()=>{In(2),Mn([Qt])},kn.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===qt){const e=t[1];o(e)}else{if(l!==Kt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&Dn[o])return void delete Dn[o];An(t)}}},kn.onerror=()=>{throw In(1),"[ 2021-05-15 onerror ]"},kn.onclose=e=>{4e3===e.code||1001===e.code?In(4):In(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await _(`/api/replay-data${ae.asQueryArgs(n)}`,{});return await o.json()},disconnect:function(){kn&&kn.close(4e3),En=0,Dn={}},sendClientEvent:function(e){En++,Dn[En]=e,Mn([Yt,En,Dn[En]])},onServerChange:function(e){An=e;for(const t of Pn)An(t);Pn=[]},onConnectionStateChange:function(e){zn=e;for(const t of Sn)zn(t);Sn=[]},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},_n=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Nn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Nn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const Vn={key:0,class:"overlay connection-lost"},On={key:0,class:"overlay-content"},Bn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Un={key:1,class:"overlay-content"},Rn=n("div",null,"Connecting...",-1);_n.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",Vn,[e.lostConnection?(s(),t("div",On,[Bn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",Un,[Rn])):l("",!0)])):l("",!0)};var $n=e({name:"help-overlay",emits:{bgclick:null}});const Gn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),Ln=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),Fn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),jn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),Wn=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Hn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Kn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),qn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Yn=n("tr",null,[n("td",null,"🎯 Center puzzle in screen:"),n("td",null,[n("div",null,[n("kbd",null,"C")])])],-1),Qn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Zn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Xn=n("tr",null,[n("td",null,"👤 Toggle player names:"),n("td",null,[n("div",null,[n("kbd",null,"N")])])],-1),Jn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),eo=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),to=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),no=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);$n.render=function(e,o,l,a,i,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]=u((()=>{}),["stop"]))},[Gn,Ln,Fn,jn,Wn,Hn,Kn,qn,Yn,Qn,Zn,Xn,Jn,eo,to,no])])};var oo=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),lo=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),ao=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),so=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),io=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function ro(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var co={createCanvas:ro,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=ro(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=ro(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(),o}};const uo=le("Debug.js");let po=0,go=0;var ho=e=>{po=performance.now(),go=e},mo=e=>{const t=performance.now(),n=t-po;n>go&&uo.log(e+": "+n),po=t};function yo(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function fo(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var vo={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:yo,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:fo,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 yo(fo(e),fo(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const wo=le("PuzzleGraphics.js");function bo(e,t){const n=ae.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Co={loadPuzzleBitmaps:async function(e){const t=await co.loadImageToBitmap(e.info.imageUrl),n=await co.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){wo.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=vo.pointAdd(a,{x:o,y:0}),c=vo.pointAdd(r,{x:0,y:o}),u=vo.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;oae.decodePiece(xo[e].puzzle.tiles[t]),Bo=(e,t)=>Oo(e,t).group,Uo=(e,t)=>{const n=xo[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},Ro=(e,t)=>{const n=xo[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=xo[e].puzzle.info,o=ae.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return vo.pointAdd(o,l)},$o=(e,t)=>Oo(e,t).pos,Go=e=>{const t=ll(e),n=al(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}},Lo=(e,t)=>{const n=Ho(e),o=Oo(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Fo=(e,t)=>Oo(e,t).z,jo=(e,t)=>{for(const n of xo[e].puzzle.tiles){const e=ae.decodePiece(n);if(e.owner===t)return e.idx}return-1},Wo=e=>xo[e].puzzle.info.tileDrawSize,Ho=e=>xo[e].puzzle.info.tileSize,Ko=e=>xo[e].puzzle.data.maxGroup,qo=e=>xo[e].puzzle.data.maxZ;function Yo(e,t){const n=xo[e].puzzle.info,o=ae.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Qo=(e,t,n)=>{for(const o of t)Vo(e,o,{z:n})},Zo=(e,t,n)=>{const o=$o(e,t);Vo(e,t,{pos:vo.pointAdd(o,n)})},Xo=(e,t,n)=>{const o=Wo(e),l=Go(e),a=n;for(const s of t){const t=Oo(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)Zo(e,s,a)},Jo=(e,t)=>Oo(e,t).owner,el=(e,t)=>{for(const n of t)Vo(e,n,{owner:-1,z:1})},tl=(e,t,n)=>{for(const o of t)Vo(e,o,{owner:n})};function nl(e,t){const n=xo[e].puzzle.tiles,o=ae.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=ae.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const ol=(e,t)=>{const n=Po(e,t);return n?n.points:0},ll=e=>xo[e].puzzle.info.table.width,al=e=>xo[e].puzzle.info.table.height;var sl={setGame:function(e,t){xo[e]=t},exists:function(e){return!!xo[e]||!1},playerExists:So,getActivePlayers:function(e,t){const n=t-30*G;return zo(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*G;return zo(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){So(e,t)?No(e,t,{ts:n}):Ao(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:Do,getPieceCount:To,getImageUrl:function(e){var t;const n=(null==(t=xo[e].puzzle.info.image)?void 0:t.url)||xo[e].puzzle.info.imageUrl;if(!n)throw new Error("[2021-07-11] no image url set");return n},get:function(e){return xo[e]||null},getAllGames:function(){return Object.values(xo).sort(((e,t)=>{const n=Eo(e.id);return n===Eo(t.id)?n?t.puzzle.data.finished-e.puzzle.data.finished:t.puzzle.data.started-e.puzzle.data.started:n?1:-1}))},getPlayerBgColor:(e,t)=>{const n=Po(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Po(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Po(e,t);return n?n.name:null},getPlayerIndexById:ko,getPlayerIdByIndex:function(e,t){return xo[e].players.length>t?ae.decodePlayer(xo[e].players[t]).id:null},changePlayer:No,setPlayer:Ao,setPiece:function(e,t,n){xo[e].puzzle.tiles[t]=ae.encodePiece(n)},setPuzzleData:function(e,t){xo[e].puzzle.data=t},getTableWidth:ll,getTableHeight:al,getPuzzle:e=>xo[e].puzzle,getRng:e=>xo[e].rng.obj,getPuzzleWidth:e=>xo[e].puzzle.info.width,getPuzzleHeight:e=>xo[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return xo[e].puzzle.tiles.map(ae.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=jo(e,t);return n<0?null:xo[e].puzzle.tiles[n]},getPieceDrawOffset:e=>xo[e].puzzle.info.tileDrawOffset,getPieceDrawSize:Wo,getFinalPiecePos:Ro,getStartTs:e=>xo[e].puzzle.data.started,getFinishTs:e=>xo[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=xo[e].puzzle,s=function(e,t){return t in xo[e].evtInfos?xo[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([wn,a.data])},d=t=>{i.push([bn,ae.encodePiece(Oo(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=Po(e,t);n&&i.push([Cn,ae.encodePlayer(n)])},p=n[0];if(p===sn){const l=n[1];No(e,t,{bgcolor:l,ts:o}),u()}else if(p===rn){const l=n[1];No(e,t,{color:l,ts:o}),u()}else if(p===dn){const l=`${n[1]}`.substr(0,16);No(e,t,{name:l,ts:o}),u()}else if(p===en){const l=n[1],a=n[2],s=Po(e,t);if(s){const n=s.x-l,i=s.y-a;No(e,t,{ts:o,x:n,y:i}),u()}}else if(p===tn){const l={x:n[1],y:n[2]};No(e,t,{d:1,ts:o}),u(),s._last_mouse_down=l;const a=((e,t)=>{const n=xo[e].puzzle.info,o=xo[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=qo(e)+1;_o(e,{maxZ:n}),r();const o=nl(e,a);Qo(e,o,qo(e)),tl(e,o,t),c(o)}s._last_mouse=l}else if(p===on){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)No(e,t,{x:l,y:a,ts:o}),u();else{const n=jo(e,t);if(n>=0){No(e,t,{x:l,y:a,ts:o}),u();const r=nl(e,n);let d=vo.pointInBounds(i,Go(e))&&vo.pointInBounds(s._last_mouse_down,Go(e));for(const t of r){const n=Lo(e,t);if(vo.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Xo(e,r,{x:t,y:n}),c(r)}}else No(e,t,{ts:o}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===nn){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=jo(e,t);if(g>=0){const n=nl(e,g);tl(e,n,0),c(n);const s=$o(e,g),i=Ro(e,g);let h=!1;if(Mo(e)===Ge.REAL){for(const t of n)if(Uo(e,t)){h=!0;break}}else h=!0;if(h&&vo.pointDistance(i,s){const l=xo[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=Bo(e,t),l=Bo(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=$o(e,t),s=vo.pointAdd($o(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(vo.pointDistance(a,s){const o=xo[e].puzzle.tiles,l=Bo(e,t),a=Bo(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(_o(e,{maxGroup:Ko(e)+1}),r(),s=Ko(e));if(Vo(e,t,{group:s}),d(t),Vo(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=ae.decodePiece(r);i.includes(t.group)&&(Vo(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=nl(e,t),((e,t)=>-1===Jo(e,t))(e,n))el(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=Fo(e,o);t>n&&(n=t)}return n})(e,l);Qo(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of nl(e,g)){const o=Yo(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])){a=!0;break}}if(a&&Io(e)===Be.ANY){const n=ol(e,t)+1;No(e,t,{d:p,ts:o,points:n}),u()}else No(e,t,{d:p,ts:o}),u();a&&Mo(e)===Ge.REAL&&Do(e)===To(e)&&(_o(e,{finished:o}),r()),a&&l&&l(t)}}else No(e,t,{d:p,ts:o}),u();s._last_mouse=i}else if(p===ln){const l=n[1],a=n[2];No(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else if(p===an){const l=n[1],a=n[2];No(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else No(e,t,{ts:o}),u();return function(e,t,n){xo[e].evtInfos[t]=n}(e,t,s),i}};let il=-10,rl=20,dl=2,cl=15;class ul{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=il+Math.random()*rl,this.vy=-1*(dl+Math.random()*cl),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;dl=t/2,cl=t-dl;const n=1/4*this.canvas.width/(t/2);il=-n,rl=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new ul(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new ul(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{localStorage.setItem(e,t)},Cl=e=>localStorage.getItem(e);var xl=(e,t)=>{bl(e,`${t}`)},kl=(e,t)=>{const n=Cl(e);if(null===n)return t;const o=parseInt(n,10);return isNaN(o)?t:o},Pl=(e,t)=>{bl(e,t?"1":"0")},Al=(e,t)=>{const n=Cl(e);return null===n?t:"1"===n},Sl=(e,t)=>{bl(e,t)},zl=(e,t)=>{const n=Cl(e);return null===n?t:n};const Tl={"./grab.png":lo,"./grab_mask.png":ao,"./hand.png":so,"./hand_mask.png":io},Il={"./click.mp3":oo},Ml="replay";let El=!0,Dl=!0;let Nl=!0;async function _l(e,t,n,o,l,a){void 0===window.DEBUG&&(window.DEBUG=!1);const s=Il["./click.mp3"].default,i=new Audio(s),r=await co.loadImageToBitmap(Tl["./grab.png"].default),d=await co.loadImageToBitmap(Tl["./hand.png"].default),c=await co.loadImageToBitmap(Tl["./grab_mask.png"].default),u=await co.loadImageToBitmap(Tl["./hand_mask.png"].default),p=r.width,g=Math.round(p/2),h=r.height,m=Math.round(h/2),y={},f=async e=>{const t=e.color+" "+e.d;if(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(co.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Nl=!0})),t}(l,co.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};Nn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await Nn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let C=()=>0;const x=async()=>{if("play"===o){const o=await Nn.connect(n,e,t),l=ae.decodeGame(o);sl.setGame(l.id,l),C=()=>L()}else{if(o!==Ml)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=ae.decodeGame(t.game);sl.setGame(n.id,n),w.lastRealTs=L(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,C=()=>w.lastGameTs}}Nl=!0};await x();const k=sl.getPieceDrawOffset(e),P=sl.getPieceDrawSize(e),A=sl.getPuzzleWidth(e),S=sl.getPuzzleHeight(e),z=sl.getTableWidth(e),T=sl.getTableHeight(e),I={x:(z-A)/2,y:(T-S)/2},M={w:A,h:S},E={w:P,h:P},D=await Co.loadPuzzleBitmaps(sl.getPuzzle(e)),N=new gl(v,sl.getRng(e));N.init();const _=v.getContext("2d");v.classList.add("loaded"),a.setPuzzleCut();const V=function(){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)},a=(e,t)=>{if(n==e)return!1;const l=1-n/e;return o(-t.x*l,-t.y*l),n=e,!0},s=o=>({x:o.x/n-e,y:o.y/n-t}),i=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),r=e=>({w:e.w*n,h:e.h*n}),d=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,reset:()=>{e=0,t=0,n=1},move:o,canZoom:e=>n!=l(e),zoom:(e,t)=>a(l(e),t),setZoom:a,worldToViewport:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:r,viewportToWorld:e=>{const{x:t,y:n}=s(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:s,viewportDimToWorld:e=>{const{w:t,h:n}=d(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:d}}(),O=()=>{V.reset(),V.move(-(z-v.width)/2,-(T-v.height)/2);const e=V.worldDimToViewport(M),t=v.width-40,n=v.height-40;if(e.w>t||e.h>n||e.w{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([tn,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([nn,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([on,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?ln:an;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([cn]),o===Ml&&("KeyI"===e.code&&v([gn]),"KeyO"===e.code&&v([hn]),"KeyP"===e.code&&v([pn])),"KeyF"===e.code&&v([fn]),"KeyG"===e.code&&v([vn]),"KeyM"===e.code&&v([un]),"KeyN"===e.code&&v([mn]),"KeyC"===e.code&&v([yn]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});v([en,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([ln,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([an,...e])}},setHotkeys:e=>{a=e}}}(v,window,V,o),U=sl.getImageUrl(e),R=()=>{const t=sl.getStartTs(e),n=sl.getFinishTs(e),o=C();a.setFinished(!!n),a.setDuration((n||o)-t)};R(),a.setPiecesDone(sl.getFinishedPiecesCount(e)),a.setPiecesTotal(sl.getPieceCount(e));const G=C();a.setActivePlayers(sl.getActivePlayers(e,G)),a.setIdlePlayers(sl.getIdlePlayers(e,G));const F=!!sl.getFinishTs(e);let j=F;const W=()=>j&&!F,H=()=>kl(hl,100),K=()=>Al(ml,!1),q=()=>Al(wl,!0),Y=()=>{const e=H();i.volume=e/100,i.play()},Q=()=>o===Ml?zl(yl,"#222222"):sl.getPlayerBgColor(e,t)||zl(yl,"#222222"),Z=()=>o===Ml?zl(fl,"#ffffff"):sl.getPlayerColor(e,t)||zl(fl,"#ffffff");let X="",J="",ee=!1;const te=e=>{ee=e;const[t,n]=e?[X,"grab"]:[J,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},ne=e=>{X=co.colorizedCanvas(r,c,e).toDataURL(),J=co.colorizedCanvas(d,u,e).toDataURL(),te(ee)};ne(Z());const oe=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},le=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,oe())},ie=()=>{w.paused=!w.paused,oe()},re=[];let de;let ce;if("play"===o?re.push(setInterval((()=>{R()}),1e3)):o===Ml&&oe(),"play"===o)Nn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Cn:{const n=ae.decodePlayer(a);n.id!==t&&(sl.setPlayer(e,n.id,n),Nl=!0)}break;case bn:{const t=ae.decodePiece(a);sl.setPiece(e,t.idx,t),Nl=!0}break;case wn:sl.setPuzzleData(e,a),Nl=!0}j=!!sl.getFinishTs(e)}));else if(o===Ml){const t=(t,n)=>{const o=t;if(o[0]===Zt){const t=o[1];return sl.addPlayer(e,t,n),!0}if(o[0]===Xt){const t=sl.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return sl.addPlayer(e,t,n),!0}if(o[0]===Jt){const t=sl.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return sl.handleInput(e,t,l,n),!0}return!1};let n=w.lastGameTs;const o=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=L();if(w.paused)return w.lastRealTs=l,void(de=setTimeout(o,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const o=w.log[w.logPointer],l=n+o[o.length-1],a=w.log[e],i=a[a.length-1],r=l+i;if(r>s){s+500*${let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/o),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{B.createKeyEvents();for(const n of B.consumeAll())if("play"===o){const o=n[0];if(o===en){const e=n[1],t=n[2],o=V.worldDimToViewport({w:e,h:t});Nl=!0,V.move(o.w,o.h)}else if(o===on){if(ue&&!sl.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=V.worldToViewport(e),o=Math.round(t.x-ue.x),l=Math.round(t.y-ue.y);Nl=!0,V.move(o,l),ue=t}}else if(o===rn)ne(n[1]);else if(o===tn){const e={x:n[1],y:n[2]};ue=V.worldToViewport(e),te(!0)}else if(o===nn)ue=null,te(!1);else if(o===ln){const e={x:n[1],y:n[2]};Nl=!0,V.zoom("in",V.worldToViewport(e))}else if(o===an){const e={x:n[1],y:n[2]};Nl=!0,V.zoom("out",V.worldToViewport(e))}else o===cn?a.togglePreview():o===un?a.toggleSoundsEnabled():o===mn?a.togglePlayerNames():o===yn?O():o===fn?(El=!El,Nl=!0):o===vn&&(Dl=!Dl,Nl=!0);const l=C();sl.handleInput(e,t,n,l,(e=>{K()&&Y()})).length>0&&(Nl=!0),Nn.sendClientEvent(n)}else if(o===Ml){const e=n[0];if(e===pn)ie();else if(e===hn)se();else if(e===gn)le();else if(e===en){const e=n[1],t=n[2];Nl=!0,V.move(e,t)}else if(e===on){if(ue){const e={x:n[1],y:n[2]},t=V.worldToViewport(e),o=Math.round(t.x-ue.x),l=Math.round(t.y-ue.y);Nl=!0,V.move(o,l),ue=t}}else if(e===rn)ne(n[1]);else if(e===tn){const e={x:n[1],y:n[2]};ue=V.worldToViewport(e),te(!0)}else if(e===nn)ue=null,te(!1);else if(e===ln){const e={x:n[1],y:n[2]};Nl=!0,V.zoom("in",V.worldToViewport(e))}else if(e===an){const e={x:n[1],y:n[2]};Nl=!0,V.zoom("out",V.worldToViewport(e))}else e===cn?a.togglePreview():e===un?a.toggleSoundsEnabled():e===mn?a.togglePlayerNames():e===yn?O():e===fn?(El=!El,Nl=!0):e===vn&&(Dl=!Dl,Nl=!0)}j=!!sl.getFinishTs(e),W()&&(N.update(),Nl=!0)},render:async()=>{if(!Nl)return;const n=C();let l,s,i;window.DEBUG&&ho(0),_.fillStyle=Q(),_.fillRect(0,0,v.width,v.height),window.DEBUG&&mo("clear done"),l=V.worldToViewportRaw(I),s=V.worldDimToViewportRaw(M),_.fillStyle="rgba(255, 255, 255, .3)",_.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&mo("board done");const r=sl.getPiecesSortedByZIndex(e);window.DEBUG&&mo("get tiles done"),s=V.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?El:Dl)&&(i=D[e.idx],l=V.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),_.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&mo("tiles done");const d=[];for(const a of sl.getActivePlayers(e,n))c=a,(o===Ml||c.id!==t)&&(i=await f(a),l=V.worldToViewport(a),_.drawImage(i,l.x-g,l.y-m),q()&&d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;_.fillStyle="white",_.textAlign="center";for(const[e,t,o]of d)_.fillText(e,t,o);window.DEBUG&&mo("players done"),a.setActivePlayers(sl.getActivePlayers(e,n)),a.setIdlePlayers(sl.getIdlePlayers(e,n)),a.setPiecesDone(sl.getFinishedPiecesCount(e)),window.DEBUG&&mo("HUD done"),W()&&N.render(),Nl=!1}}),{setHotkeys:e=>{B.setHotkeys(e)},onBgChange:e=>{Sl(yl,e),B.addEvent([sn,e])},onColorChange:e=>{Sl(fl,e),B.addEvent([rn,e])},onNameChange:e=>{Sl(vl,e),B.addEvent([dn,e])},onSoundsEnabledChange:e=>{Pl(ml,e)},onSoundsVolumeChange:e=>{xl(hl,e),Y()},onShowPlayerNamesChange:e=>{Pl(wl,e)},replayOnSpeedUp:le,replayOnSpeedDown:se,replayOnPauseToggle:ie,previewImageUrl:U,player:{background:Q(),color:Z(),name:o===Ml?zl(vl,"anon"):sl.getPlayerName(e,t)||zl(vl,"anon"),soundsEnabled:K(),soundsVolume:H(),showPlayerNames:q()},game:sl.get(e),disconnect:Nn.disconnect,connect:x,unload:()=>{re.forEach((e=>{clearInterval(e)})),de&&clearTimeout(de),ce&&ce.stop()}}}var Vl=e({name:"game",components:{PuzzleStatus:zt,Scores:xt,SettingsOverlay:It,PreviewOverlay:Rt,InfoOverlay:Gt,ConnectionOverlay:_n,HelpOverlay:$n},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,cuttingPuzzle:!0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},game:null,previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await _l(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setPuzzleCut:()=>{this.cuttingPuzzle=!1},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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 Ol={id:"game"},Bl={key:1,class:"overlay"},Ul=n("div",{class:"overlay-content"},[n("div",null,"⏳ Cutting puzzle, please wait... ⏳")],-1),Rl={class:"menu"},$l={class:"tabs"},Gl=i("🧩 Puzzles");Vl.render=function(e,i,r,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("info-overlay"),y=a("help-overlay"),f=a("connection-overlay"),v=a("puzzle-status"),w=a("router-link"),b=a("scores");return s(),t("div",Ol,[p(n(g,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),e.g.game?p((s(),t(m,{key:0,onBgclick:i[4]||(i[4]=t=>e.toggle("info",!0)),game:e.g.game},null,8,["game"])),[[x,"info"===e.overlay]]):l("",!0),p(n(y,{onBgclick:i[5]||(i[5]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),e.cuttingPuzzle?(s(),t("div",Bl,[Ul])):l("",!0),n(f,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(v,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",Rl,[n("div",$l,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Gl])),_:1}),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("info",!0))},"ℹ️ Info"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("help",!0))},"⌨️ Hotkeys")])]),n(b,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var Ll=e({name:"replay",components:{PuzzleStatus:zt,Scores:xt,SettingsOverlay:It,PreviewOverlay:Rt,InfoOverlay:Gt,HelpOverlay:$n},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,cuttingPuzzle:!0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},game:null,previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await _l(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,Ml,this.$el,{setPuzzleCut:()=>{this.cuttingPuzzle=!1},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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 Fl={id:"replay"},jl={key:1,class:"overlay"},Wl=n("div",{class:"overlay-content"},[n("div",null,"⏳ Cutting puzzle, please wait... ⏳")],-1),Hl={class:"menu"},Kl={class:"tabs"},ql=i("🧩 Puzzles");Ll.render=function(e,i,d,c,u,g){const h=a("settings-overlay"),m=a("preview-overlay"),y=a("info-overlay"),f=a("help-overlay"),v=a("puzzle-status"),w=a("router-link"),b=a("scores");return s(),t("div",Fl,[p(n(h,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(m,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),e.g.game?p((s(),t(y,{key:0,onBgclick:i[4]||(i[4]=t=>e.toggle("info",!0)),game:e.g.game},null,8,["game"])),[[x,"info"===e.overlay]]):l("",!0),p(n(f,{onBgclick:i[5]||(i[5]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),e.cuttingPuzzle?(s(),t("div",jl,[Wl])):l("",!0),n(v,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[8]||(i[8]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",Hl,[n("div",Kl,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[ql])),_:1}),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[11]||(i[11]=t=>e.toggle("info",!0))},"ℹ️ Info"),n("div",{class:"opener",onClick:i[12]||(i[12]=t=>e.toggle("help",!0))},"⌨️ Hotkeys")])]),n(b,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=function(){let e=zl("ID","");return e||(e=ae.uniqId(),Sl("ID",e)),e}(),t=function(){let e=zl("SECRET","");return e||(e=ae.uniqId(),Sl("SECRET",e)),e}();O(e),B(t);const n=await _("/api/me",{}),o=await n.json(),l=await _("/api/conf",{}),a=await l.json(),s=k({history:P(),routes:[{name:"index",path:"/",component:Z},{name:"new-game",path:"/new-game",component:pt},{name:"game",path:"/g/:id",component:Vl},{name:"replay",path:"/replay/:id",component:Ll}]});s.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const i=A(S);i.config.globalProperties.$me=o,i.config.globalProperties.$config=a,i.config.globalProperties.$clientId=e,i.use(s),i.mount("#app")})(); diff --git a/build/public/index.html b/build/public/index.html index 51d4f80..0974d69 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,7 +4,7 @@ 🧩 jigsaw.hyottoko.club - + From 126384e5bda5cffb8042a0b269b6542a4c145f4e Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sun, 11 Jul 2021 22:56:52 +0200 Subject: [PATCH 40/45] upper case first letter of title --- src/frontend/components/InfoOverlay.vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/frontend/components/InfoOverlay.vue b/src/frontend/components/InfoOverlay.vue index 1bf6220..b6c1835 100644 --- a/src/frontend/components/InfoOverlay.vue +++ b/src/frontend/components/InfoOverlay.vue @@ -48,8 +48,8 @@ export default defineComponent({ }, shapeMode () { switch (this.game.shapeMode) { - case ShapeMode.FLAT: return ['Flat', 'all pieces flat on all sides'] - case ShapeMode.ANY: return ['Any', 'flat pieces can occur anywhere'] + case ShapeMode.FLAT: return ['Flat', 'All pieces flat on all sides'] + case ShapeMode.ANY: return ['Any', 'Flat pieces can occur anywhere'] case ShapeMode.NORMAL: default: return ['Normal', ''] @@ -57,10 +57,10 @@ export default defineComponent({ }, snapMode () { switch (this.game.snapMode) { - case SnapMode.REAL: return ['Real', 'pieces snap only to corners, already snapped pieces and to each other'] + case SnapMode.REAL: return ['Real', 'Pieces snap only to corners, already snapped pieces and to each other'] case SnapMode.NORMAL: default: - return ['Normal', 'pieces snap to final destination and to each other'] + return ['Normal', 'Pieces snap to final destination and to each other'] } }, }, From 4e528cc83d3ffab3c6685204273e608d39a7f814 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Mon, 12 Jul 2021 01:28:14 +0200 Subject: [PATCH 41/45] store games in db --- build/public/assets/index.63ff8630.js | 1 + build/public/assets/index.97691b3e.js | 1 - build/public/index.html | 2 +- build/server/main.js | 214 ++++++++++++++++++-------- scripts/fix_games_image_info.ts | 6 +- scripts/fix_tiles.ts | 9 +- scripts/import_games.ts | 27 ++++ src/common/Types.ts | 2 + src/common/Util.ts | 2 + src/dbpatches/04_games.sqlite | 11 ++ src/server/Game.ts | 13 +- src/server/GameLog.ts | 1 + src/server/GameStorage.ts | 143 +++++++++++++---- src/server/main.ts | 72 +++++---- 14 files changed, 371 insertions(+), 133 deletions(-) create mode 100644 build/public/assets/index.63ff8630.js delete mode 100644 build/public/assets/index.97691b3e.js create mode 100644 scripts/import_games.ts create mode 100644 src/dbpatches/04_games.sqlite diff --git a/build/public/assets/index.63ff8630.js b/build/public/assets/index.63ff8630.js new file mode 100644 index 0000000..bb8b554 --- /dev/null +++ b/build/public/assets/index.63ff8630.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as o,b as l,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as C,s as x,u as k,x as P,y as A}from"./vendor.684f7bc8.js";var S=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const z={id:"app"},T={key:0,class:"nav"},I=i("Games overview"),E=i("New game");S.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",z,[e.showNav?(s(),t("ul",T,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:o((()=>[E])),_:1})])])):l("",!0),n(g)])};let M="",D="";const N=async(e,t,n)=>new Promise(((o,l)=>{const a=new window.XMLHttpRequest;a.open(e,t,!0),a.withCredentials=!0;for(const e in n.headers||{})a.setRequestHeader(e,n.headers[e]);a.setRequestHeader("Client-Id",M),a.setRequestHeader("Client-Secret",D),a.addEventListener("load",(function(e){o({status:this.status,text:this.responseText,json:async()=>JSON.parse(this.responseText)})})),a.addEventListener("error",(function(e){l(new Error("xhr error"))})),a.upload&&n.onUploadProgress&&a.upload.addEventListener("progress",(function(e){n.onUploadProgress&&n.onUploadProgress(e)})),a.send(n.body||null)}));var _=(e,t)=>N("get",e,t),V=(e,t)=>N("post",e,t),O=e=>{M=e},B=e=>{D=e};const U=864e5,R=e=>{const t=Math.floor(e/U);e%=U;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 $=1,G=1e3,L=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},F=(e,t)=>R(t-e),j=R,W=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||L();return`${n} ${F(o,l)}`}}});const H={class:"game-info-text"},K=n("br",null,null,-1),q=n("br",null,null,-1),Y=n("br",null,null,-1),Q=i(" ↪️ Watch replay ");W.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",H,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),K,i(" 👥 "+r(e.game.players),1),q,i(" "+r(e.time(e.game.started,e.game.finished)),1),Y])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[Q])),_:1},8,["to"])):l("",!0)],4)};var Z=e({components:{GameTeaser:W},data:()=>({gamesRunning:[],gamesFinished:[]}),async created(){const e=await _("/api/index-data",{}),t=await e.json();this.gamesRunning=t.gamesRunning,this.gamesFinished=t.gamesFinished}});const X=n("h1",null,"Running games",-1),J=n("h1",null,"Finished games",-1);Z.render=function(e,o,l,i,r,u){const p=a("game-teaser");return s(),t("div",null,[X,(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)),J,(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 ee=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}},canEdit(){return!!this.$me.id&&this.$me.id===this.image.uploaderUserId}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});ee.render=function(e,n,o,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:n[2]||(n[2]=(...t)=>e.onClick&&e.onClick(...t))},[e.canEdit?(s(),t("div",{key:0,class:"btn edit",onClick:n[1]||(n[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")):l("",!0)],4)};var te=e({name:"image-library",components:{ImageTeaser:ee},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});te.render=function(e,n,o,l,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,o)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])};class ne{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new ne(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const oe=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},le=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=oe(o.getHours(),"00"),a=oe(o.getMinutes(),"00"),s=oe(o.getSeconds(),"00");console[t](`${l}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var ae={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",ne.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode,e.shapeMode,e.snapMode,e.creatorUserId]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:ne.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8],creatorUserId:e[9]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const se={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};se.render=function(e,n,o,l,a,i){return s(),t("div",{style:i.style,title:o.title},null,12,["title"])};var ie=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const re=m();y("data-v-a4fa5e7e");const de={key:0,class:"autocomplete"};f();const ce=re(((e,o,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onChange:o[2]||(o[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:o[3]||(o[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[4]||(o[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",de,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,o)=>(s(),t("li",{key:o,class:{active:o===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):l("",!0),(s(!0),t(d,null,c(e.values,((n,o)=>(s(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));ie.render=ce,ie.__scopeId="data-v-a4fa5e7e";const ue=le("NewImageDialog.vue");var pe=e({name:"new-image-dialog",components:{ResponsiveImage:se,TagsInput:ie},props:{autocompleteTags:{type:Function},uploadProgress:{type:Number},uploading:{type:String}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{uploadProgressPercent(){return this.uploadProgress?Math.round(100*this.uploadProgress):0},canPostToGallery(){return!this.uploading&&!(!this.previewUrl||!this.file)},canSetupGameClick(){return!this.uploading&&!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const o=n[0];return o.type.startsWith("image/")?o:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){ue.info("onDragleave"),this.droppable=!1}}});const ge=n("div",{class:"drop-target"},null,-1),he={key:0,class:"has-image"},me={key:1},ye={class:"upload"},fe=n("span",{class:"btn"},"Upload File",-1),ve={class:"area-settings"},we=n("td",null,[n("label",null,"Title")],-1),be=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ce=n("td",null,[n("label",null,"Tags")],-1),xe={class:"area-buttons"},ke=i("🖼️ Post to gallery"),Pe=i("🧩 Post to gallery "),Ae=n("br",null,null,-1),Se=i(" + set up game");pe.render=function(e,o,l,c,h,m){const y=a("responsive-image"),f=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:o[3]||(o[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:o[4]||(o[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:o[5]||(o[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[ge,e.previewUrl?(s(),t("div",he,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(y,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",me,[n("label",ye,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),fe])]))],34),n("div",ve,[n("table",null,[n("tr",null,[we,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[6]||(o[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),be,n("tr",null,[Ce,n("td",null,[n(f,{modelValue:e.tags,"onUpdate:modelValue":o[7]||(o[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",xe,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[8]||(o[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},["postToGallery"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[ke],64))],8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[9]||(o[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},["setupGame"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Pe,Ae,Se],64))],8,["disabled"])])])])};var ze=e({name:"edit-image-dialog",components:{ResponsiveImage:se,TagsInput:ie},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Te={class:"area-image"},Ie={class:"has-image"},Ee={class:"area-settings"},Me=n("td",null,[n("label",null,"Title")],-1),De=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ne=n("td",null,[n("label",null,"Tags")],-1),_e={class:"area-buttons"};var Ve,Oe,Be,Ue,Re,$e,Ge,Le;ze.render=function(e,o,l,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",Te,[n("div",Ie,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ee,[n("table",null,[n("tr",null,[Me,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),De,n("tr",null,[Ne,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",_e,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])},(Oe=Ve||(Ve={}))[Oe.Flat=0]="Flat",Oe[Oe.Out=1]="Out",Oe[Oe.In=-1]="In",(Ue=Be||(Be={}))[Ue.FINAL=0]="FINAL",Ue[Ue.ANY=1]="ANY",($e=Re||(Re={}))[$e.NORMAL=0]="NORMAL",$e[$e.ANY=1]="ANY",$e[$e.FLAT=2]="FLAT",(Le=Ge||(Ge={}))[Le.NORMAL=0]="NORMAL",Le[Le.REAL=1]="REAL";var Fe=e({name:"new-game-dialog",components:{ResponsiveImage:se},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Be.ANY,shapeMode:Re.NORMAL,snapMode:Ge.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const je={class:"area-image"},We={class:"has-image"},He={key:0,class:"image-title"},Ke={key:0,class:"image-title-title"},qe={key:1,class:"image-title-dim"},Ye={class:"area-settings"},Qe=n("td",null,[n("label",null,"Pieces")],-1),Ze=n("td",null,[n("label",null,"Scoring: ")],-1),Xe=i(" Any (Score when pieces are connected to each other or on final location)"),Je=n("br",null,null,-1),et=i(" Final (Score when pieces are put to their final location)"),tt=n("td",null,[n("label",null,"Shapes: ")],-1),nt=i(" Normal"),ot=n("br",null,null,-1),lt=i(" Any (flat pieces can occur anywhere)"),at=n("br",null,null,-1),st=i(" Flat (all pieces flat on all sides)"),it=n("td",null,[n("label",null,"Snapping: ")],-1),rt=i(" Normal (pieces snap to final destination and to each other)"),dt=n("br",null,null,-1),ct=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),ut={class:"area-buttons"};Fe.render=function(e,o,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:o[11]||(o[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[10]||(o[10]=u((()=>{}),["stop"]))},[n("div",je,[n("div",We,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title||e.image.width||e.image.height?(s(),t("div",He,[e.image.title?(s(),t("span",Ke,'"'+r(e.image.title)+'"',1)):l("",!0),e.image.width||e.image.height?(s(),t("span",qe,"("+r(e.image.width)+" ✕ "+r(e.image.height)+")",1)):l("",!0)])):l("",!0)]),n("div",Ye,[n("table",null,[n("tr",null,[Qe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Xe]),Je,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),et])])]),n("tr",null,[tt,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),nt]),ot,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),lt]),at,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),st])])]),n("tr",null,[it,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[7]||(o[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),rt]),dt,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":o[8]||(o[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),ct])])])])]),n("div",ut,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[9]||(o[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var pt=e({components:{ImageLibrary:te,NewImageDialog:pe,EditImageDialog:ze,NewGameDialog:Fe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:"",uploading:"",uploadProgress:0}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await _(`/api/newgame-data${ae.asQueryArgs(this.filters)}`,{}),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){this.uploadProgress=0;const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await V("/api/upload",{body:t,onUploadProgress:e=>{this.uploadProgress=e.loaded/e.total}});return this.uploadProgress=1,await n.json()},async saveImage(e){const t=await V("/api/save-image",{headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){const t=await this.saveImage(e);t.ok?(this.dialog="",await this.loadImages()):alert(t.error)},async postToGalleryClick(e){this.uploading="postToGallery",await this.uploadImage(e),this.uploading="",this.dialog="",await this.loadImages()},async setupGameClick(e){this.uploading="setupGame";const t=await this.uploadImage(e);this.uploading="",this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await V("/api/newgame",{headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const gt={class:"upload-image-teaser"},ht=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),mt={key:0},yt=i(" Tags: "),ft=i(" Sort by: "),vt=n("option",{value:"date_desc"},"Newest first",-1),wt=n("option",{value:"date_asc"},"Oldest first",-1),bt=n("option",{value:"alpha_asc"},"A-Z",-1),Ct=n("option",{value:"alpha_desc"},"Z-A",-1);pt.render=function(e,o,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[n("div",gt,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),ht]),n("div",null,[e.tags.length>0?(s(),t("label",mt,[yt,(s(!0),t(d,null,c(e.relevantTags,((n,o)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):l("",!0),n("label",null,[ft,p(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[vt,wt,bt,Ct],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:o[4]||(o[4]=t=>e.dialog=""),uploadProgress:e.uploadProgress,uploading:e.uploading,onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","uploadProgress","uploading","onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var xt=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 kt={class:"scores"},Pt=n("div",null,"Scores",-1),At=n("td",null,"⚡",-1),St=n("td",null,"💤",-1);xt.render=function(e,o,l,a,i,u){return s(),t("div",kt,[Pt,n("table",null,[(s(!0),t(d,null,c(e.actives,((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)),(s(!0),t(d,null,c(e.idles,((e,o)=>(s(),t("tr",{key:o,style:{color:e.color}},[St,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 j(this.duration)}}});const Tt={class:"timer"};zt.render=function(e,o,l,a,i,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),b(e.$slots,"default")])};var It=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const Et=m();y("data-v-4d56fc17");const Mt=n("td",null,[n("label",null,"Background: ")],-1),Dt=n("td",null,[n("label",null,"Color: ")],-1),Nt=n("td",null,[n("label",null,"Name: ")],-1),_t=n("td",null,[n("label",null,"Sounds: ")],-1),Vt=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Ot={class:"sound-volume"},Bt=n("td",null,[n("label",null,"Show player names: ")],-1);f();const Ut=Et(((e,o,l,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:o[10]||(o[10]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[9]||(o[9]=u((()=>{}),["stop"]))},[n("tr",null,[Mt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[Dt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[Nt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[_t,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[C,e.modelValue.soundsEnabled]])])]),n("tr",null,[Vt,n("td",Ot,[n("span",{onClick:o[5]||(o[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:o[6]||(o[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:o[7]||(o[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])]),n("tr",null,[Bt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":o[8]||(o[8]=t=>e.modelValue.showPlayerNames=t)},null,512),[[C,e.modelValue.showPlayerNames]])])])])]))));It.render=Ut,It.__scopeId="data-v-4d56fc17";var Rt=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const $t={class:"preview"};Rt.render=function(e,o,l,a,i,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)])])};var Gt=e({name:"help-overlay",emits:{bgclick:null},props:{game:{type:Object,required:!0}},computed:{scoreMode(){switch(this.game.scoreMode){case Be.ANY:return["Any","Score when pieces are connected to each other or on final location"];case Be.FINAL:default:return["Final","Score when pieces are put to their final location"]}},shapeMode(){switch(this.game.shapeMode){case Re.FLAT:return["Flat","All pieces flat on all sides"];case Re.ANY:return["Any","Flat pieces can occur anywhere"];case Re.NORMAL:default:return["Normal",""]}},snapMode(){switch(this.game.snapMode){case Ge.REAL:return["Real","Pieces snap only to corners, already snapped pieces and to each other"];case Ge.NORMAL:default:return["Normal","Pieces snap to final destination and to each other"]}}}});const Lt=n("tr",null,[n("td",{colspan:"2"},"Info about this puzzle")],-1),Ft=n("td",null,"Image Title: ",-1),jt=n("td",null,"Scoring: ",-1),Wt=n("td",null,"Shapes: ",-1),Ht=n("td",null,"Snapping: ",-1);Gt.render=function(e,o,l,a,i,d){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]=u((()=>{}),["stop"]))},[Lt,n("tr",null,[Ft,n("td",null,r(e.game.puzzle.info.image.title),1)]),n("tr",null,[jt,n("td",null,[n("span",{title:e.snapMode[1]},r(e.scoreMode[0]),9,["title"])])]),n("tr",null,[Wt,n("td",null,[n("span",{title:e.snapMode[1]},r(e.shapeMode[0]),9,["title"])])]),n("tr",null,[Ht,n("td",null,[n("span",{title:e.snapMode[1]},r(e.snapMode[0]),9,["title"])])])])])};var Kt=1,qt=4,Yt=2,Qt=3,Zt=2,Xt=4,Jt=3,en=9,tn=1,nn=2,on=3,ln=4,an=5,sn=6,rn=7,dn=8,cn=10,un=11,pn=12,gn=13,hn=14,mn=15,yn=16,fn=17,vn=18,wn=1,bn=2,Cn=3;const xn=le("Communication.js");let kn,Pn=[],An=e=>{Pn.push(e)},Sn=[],zn=e=>{Sn.push(e)};let Tn=0;const In=e=>{Tn!==e&&(Tn=e,zn(e))};function En(e){if(2===Tn)try{kn.send(JSON.stringify(e))}catch(t){xn.info("unable to send message.. maybe because ws is invalid?")}}let Mn,Dn;var Nn={connect:function(e,t,n){return Mn=0,Dn={},In(3),new Promise((o=>{kn=new WebSocket(e,n+"|"+t),kn.onopen=()=>{In(2),En([Qt])},kn.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===qt){const e=t[1];o(e)}else{if(l!==Kt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&Dn[o])return void delete Dn[o];An(t)}}},kn.onerror=()=>{throw In(1),"[ 2021-05-15 onerror ]"},kn.onclose=e=>{4e3===e.code||1001===e.code?In(4):In(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},o=await _(`/api/replay-data${ae.asQueryArgs(n)}`,{});return await o.json()},disconnect:function(){kn&&kn.close(4e3),Mn=0,Dn={}},sendClientEvent:function(e){Mn++,Dn[Mn]=e,En([Yt,Mn,Dn[Mn]])},onServerChange:function(e){An=e;for(const t of Pn)An(t);Pn=[]},onConnectionStateChange:function(e){zn=e;for(const t of Sn)zn(t);Sn=[]},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},_n=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Nn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Nn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const Vn={key:0,class:"overlay connection-lost"},On={key:0,class:"overlay-content"},Bn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Un={key:1,class:"overlay-content"},Rn=n("div",null,"Connecting...",-1);_n.render=function(e,o,a,i,r,d){return e.show?(s(),t("div",Vn,[e.lostConnection?(s(),t("div",On,[Bn,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(s(),t("div",Un,[Rn])):l("",!0)])):l("",!0)};var $n=e({name:"help-overlay",emits:{bgclick:null}});const Gn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),Ln=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),Fn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),jn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),Wn=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Hn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Kn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),qn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Yn=n("tr",null,[n("td",null,"🎯 Center puzzle in screen:"),n("td",null,[n("div",null,[n("kbd",null,"C")])])],-1),Qn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Zn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Xn=n("tr",null,[n("td",null,"👤 Toggle player names:"),n("td",null,[n("div",null,[n("kbd",null,"N")])])],-1),Jn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),eo=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),to=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),no=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);$n.render=function(e,o,l,a,i,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]=u((()=>{}),["stop"]))},[Gn,Ln,Fn,jn,Wn,Hn,Kn,qn,Yn,Qn,Zn,Xn,Jn,eo,to,no])])};var oo=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),lo=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),ao=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),so=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),io=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function ro(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var co={createCanvas:ro,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=ro(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=ro(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(),o}};const uo=le("Debug.js");let po=0,go=0;var ho=e=>{po=performance.now(),go=e},mo=e=>{const t=performance.now(),n=t-po;n>go&&uo.log(e+": "+n),po=t};function yo(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function fo(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var vo={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:yo,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:fo,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 yo(fo(e),fo(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const wo=le("PuzzleGraphics.js");function bo(e,t){const n=ae.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Co={loadPuzzleBitmaps:async function(e){const t=await co.loadImageToBitmap(e.info.imageUrl),n=await co.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){wo.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,a=n.tileDrawSize,s=o/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:l,y:l},r=vo.pointAdd(a,{x:o,y:0}),c=vo.pointAdd(r,{x:0,y:o}),u=vo.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;oae.decodePiece(xo[e].puzzle.tiles[t]),Bo=(e,t)=>Oo(e,t).group,Uo=(e,t)=>{const n=xo[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},Ro=(e,t)=>{const n=xo[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=xo[e].puzzle.info,o=ae.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return vo.pointAdd(o,l)},$o=(e,t)=>Oo(e,t).pos,Go=e=>{const t=ll(e),n=al(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}},Lo=(e,t)=>{const n=Ho(e),o=Oo(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Fo=(e,t)=>Oo(e,t).z,jo=(e,t)=>{for(const n of xo[e].puzzle.tiles){const e=ae.decodePiece(n);if(e.owner===t)return e.idx}return-1},Wo=e=>xo[e].puzzle.info.tileDrawSize,Ho=e=>xo[e].puzzle.info.tileSize,Ko=e=>xo[e].puzzle.data.maxGroup,qo=e=>xo[e].puzzle.data.maxZ;function Yo(e,t){const n=xo[e].puzzle.info,o=ae.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Qo=(e,t,n)=>{for(const o of t)Vo(e,o,{z:n})},Zo=(e,t,n)=>{const o=$o(e,t);Vo(e,t,{pos:vo.pointAdd(o,n)})},Xo=(e,t,n)=>{const o=Wo(e),l=Go(e),a=n;for(const s of t){const t=Oo(e,s);t.pos.x+n.xl.x+l.w&&(a.x=Math.min(l.x+l.w-t.pos.x+o,a.x)),t.pos.y+n.yl.y+l.h&&(a.y=Math.min(l.y+l.h-t.pos.y+o,a.y))}for(const s of t)Zo(e,s,a)},Jo=(e,t)=>Oo(e,t).owner,el=(e,t)=>{for(const n of t)Vo(e,n,{owner:-1,z:1})},tl=(e,t,n)=>{for(const o of t)Vo(e,o,{owner:n})};function nl(e,t){const n=xo[e].puzzle.tiles,o=ae.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=ae.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const ol=(e,t)=>{const n=Po(e,t);return n?n.points:0},ll=e=>xo[e].puzzle.info.table.width,al=e=>xo[e].puzzle.info.table.height;var sl={setGame:function(e,t){xo[e]=t},exists:function(e){return!!xo[e]||!1},playerExists:So,getActivePlayers:function(e,t){const n=t-30*G;return zo(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*G;return zo(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){So(e,t)?No(e,t,{ts:n}):Ao(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:Do,getPieceCount:To,getImageUrl:function(e){var t;const n=(null==(t=xo[e].puzzle.info.image)?void 0:t.url)||xo[e].puzzle.info.imageUrl;if(!n)throw new Error("[2021-07-11] no image url set");return n},get:function(e){return xo[e]||null},getAllGames:function(){return Object.values(xo).sort(((e,t)=>{const n=Mo(e.id);return n===Mo(t.id)?n?t.puzzle.data.finished-e.puzzle.data.finished:t.puzzle.data.started-e.puzzle.data.started:n?1:-1}))},getPlayerBgColor:(e,t)=>{const n=Po(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Po(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Po(e,t);return n?n.name:null},getPlayerIndexById:ko,getPlayerIdByIndex:function(e,t){return xo[e].players.length>t?ae.decodePlayer(xo[e].players[t]).id:null},changePlayer:No,setPlayer:Ao,setPiece:function(e,t,n){xo[e].puzzle.tiles[t]=ae.encodePiece(n)},setPuzzleData:function(e,t){xo[e].puzzle.data=t},getTableWidth:ll,getTableHeight:al,getPuzzle:e=>xo[e].puzzle,getRng:e=>xo[e].rng.obj,getPuzzleWidth:e=>xo[e].puzzle.info.width,getPuzzleHeight:e=>xo[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return xo[e].puzzle.tiles.map(ae.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=jo(e,t);return n<0?null:xo[e].puzzle.tiles[n]},getPieceDrawOffset:e=>xo[e].puzzle.info.tileDrawOffset,getPieceDrawSize:Wo,getFinalPiecePos:Ro,getStartTs:e=>xo[e].puzzle.data.started,getFinishTs:e=>xo[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=xo[e].puzzle,s=function(e,t){return t in xo[e].evtInfos?xo[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([wn,a.data])},d=t=>{i.push([bn,ae.encodePiece(Oo(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=Po(e,t);n&&i.push([Cn,ae.encodePlayer(n)])},p=n[0];if(p===sn){const l=n[1];No(e,t,{bgcolor:l,ts:o}),u()}else if(p===rn){const l=n[1];No(e,t,{color:l,ts:o}),u()}else if(p===dn){const l=`${n[1]}`.substr(0,16);No(e,t,{name:l,ts:o}),u()}else if(p===en){const l=n[1],a=n[2],s=Po(e,t);if(s){const n=s.x-l,i=s.y-a;No(e,t,{ts:o,x:n,y:i}),u()}}else if(p===tn){const l={x:n[1],y:n[2]};No(e,t,{d:1,ts:o}),u(),s._last_mouse_down=l;const a=((e,t)=>{const n=xo[e].puzzle.info,o=xo[e].puzzle.tiles;let l=-1,a=-1;for(let s=0;sl)&&(l=e.z,a=s)}return a})(e,l);if(a>=0){const n=qo(e)+1;_o(e,{maxZ:n}),r();const o=nl(e,a);Qo(e,o,qo(e)),tl(e,o,t),c(o)}s._last_mouse=l}else if(p===on){const l=n[1],a=n[2],i={x:l,y:a};if(null===s._last_mouse_down)No(e,t,{x:l,y:a,ts:o}),u();else{const n=jo(e,t);if(n>=0){No(e,t,{x:l,y:a,ts:o}),u();const r=nl(e,n);let d=vo.pointInBounds(i,Go(e))&&vo.pointInBounds(s._last_mouse_down,Go(e));for(const t of r){const n=Lo(e,t);if(vo.pointInBounds(i,n)){d=!0;break}}if(d){const t=l-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Xo(e,r,{x:t,y:n}),c(r)}}else No(e,t,{ts:o}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===nn){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=jo(e,t);if(g>=0){const n=nl(e,g);tl(e,n,0),c(n);const s=$o(e,g),i=Ro(e,g);let h=!1;if(Eo(e)===Ge.REAL){for(const t of n)if(Uo(e,t)){h=!0;break}}else h=!0;if(h&&vo.pointDistance(i,s){const l=xo[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=Bo(e,t),l=Bo(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=$o(e,t),s=vo.pointAdd($o(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(vo.pointDistance(a,s){const o=xo[e].puzzle.tiles,l=Bo(e,t),a=Bo(e,n);let s;const i=[];l&&i.push(l),a&&i.push(a),l?s=l:a?s=a:(_o(e,{maxGroup:Ko(e)+1}),r(),s=Ko(e));if(Vo(e,t,{group:s}),d(t),Vo(e,n,{group:s}),d(n),i.length>0)for(const r of o){const t=ae.decodePiece(r);i.includes(t.group)&&(Vo(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),l=nl(e,t),((e,t)=>-1===Jo(e,t))(e,n))el(e,l);else{const t=((e,t)=>{let n=0;for(const o of t){const t=Fo(e,o);t>n&&(n=t)}return n})(e,l);Qo(e,l,t)}return c(l),!0}return!1};let a=!1;for(const t of nl(e,g)){const o=Yo(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])){a=!0;break}}if(a&&Io(e)===Be.ANY){const n=ol(e,t)+1;No(e,t,{d:p,ts:o,points:n}),u()}else No(e,t,{d:p,ts:o}),u();a&&Eo(e)===Ge.REAL&&Do(e)===To(e)&&(_o(e,{finished:o}),r()),a&&l&&l(t)}}else No(e,t,{d:p,ts:o}),u();s._last_mouse=i}else if(p===ln){const l=n[1],a=n[2];No(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else if(p===an){const l=n[1],a=n[2];No(e,t,{x:l,y:a,ts:o}),u(),s._last_mouse={x:l,y:a}}else No(e,t,{ts:o}),u();return function(e,t,n){xo[e].evtInfos[t]=n}(e,t,s),i}};let il=-10,rl=20,dl=2,cl=15;class ul{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=il+Math.random()*rl,this.vy=-1*(dl+Math.random()*cl),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;dl=t/2,cl=t-dl;const n=1/4*this.canvas.width/(t/2);il=-n,rl=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new ul(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new ul(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{localStorage.setItem(e,t)},Cl=e=>localStorage.getItem(e);var xl=(e,t)=>{bl(e,`${t}`)},kl=(e,t)=>{const n=Cl(e);if(null===n)return t;const o=parseInt(n,10);return isNaN(o)?t:o},Pl=(e,t)=>{bl(e,t?"1":"0")},Al=(e,t)=>{const n=Cl(e);return null===n?t:"1"===n},Sl=(e,t)=>{bl(e,t)},zl=(e,t)=>{const n=Cl(e);return null===n?t:n};const Tl={"./grab.png":lo,"./grab_mask.png":ao,"./hand.png":so,"./hand_mask.png":io},Il={"./click.mp3":oo},El="replay";let Ml=!0,Dl=!0;let Nl=!0;async function _l(e,t,n,o,l,a){void 0===window.DEBUG&&(window.DEBUG=!1);const s=Il["./click.mp3"].default,i=new Audio(s),r=await co.loadImageToBitmap(Tl["./grab.png"].default),d=await co.loadImageToBitmap(Tl["./hand.png"].default),c=await co.loadImageToBitmap(Tl["./grab_mask.png"].default),u=await co.loadImageToBitmap(Tl["./hand_mask.png"].default),p=r.width,g=Math.round(p/2),h=r.height,m=Math.round(h/2),y={},f=async e=>{const t=e.color+" "+e.d;if(!y[t]){const n=e.d?r:d;if(e.color){const o=e.d?c:u;y[t]=await createImageBitmap(co.colorizedCanvas(n,o,e.color))}else y[t]=n}return y[t]},v=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,Nl=!0})),t}(l,co.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};Nn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await Nn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let C=()=>0;const x=async()=>{if("play"===o){const o=await Nn.connect(n,e,t),l=ae.decodeGame(o);sl.setGame(l.id,l),C=()=>L()}else{if(o!==El)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=ae.decodeGame(t.game);sl.setGame(n.id,n),w.lastRealTs=L(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,C=()=>w.lastGameTs}}Nl=!0};await x();const k=sl.getPieceDrawOffset(e),P=sl.getPieceDrawSize(e),A=sl.getPuzzleWidth(e),S=sl.getPuzzleHeight(e),z=sl.getTableWidth(e),T=sl.getTableHeight(e),I={x:(z-A)/2,y:(T-S)/2},E={w:A,h:S},M={w:P,h:P},D=await Co.loadPuzzleBitmaps(sl.getPuzzle(e)),N=new gl(v,sl.getRng(e));N.init();const _=v.getContext("2d");v.classList.add("loaded"),a.setPuzzleCut();const V=function(){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)},a=(e,t)=>{if(n==e)return!1;const l=1-n/e;return o(-t.x*l,-t.y*l),n=e,!0},s=o=>({x:o.x/n-e,y:o.y/n-t}),i=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),r=e=>({w:e.w*n,h:e.h*n}),d=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,reset:()=>{e=0,t=0,n=1},move:o,canZoom:e=>n!=l(e),zoom:(e,t)=>a(l(e),t),setZoom:a,worldToViewport:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:r,viewportToWorld:e=>{const{x:t,y:n}=s(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:s,viewportDimToWorld:e=>{const{w:t,h:n}=d(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:d}}(),O=()=>{V.reset(),V.move(-(z-v.width)/2,-(T-v.height)/2);const e=V.worldDimToViewport(E),t=v.width-40,n=v.height-40;if(e.w>t||e.h>n||e.w{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([tn,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([nn,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([on,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?ln:an;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([cn]),o===El&&("KeyI"===e.code&&v([gn]),"KeyO"===e.code&&v([hn]),"KeyP"===e.code&&v([pn])),"KeyF"===e.code&&v([fn]),"KeyG"===e.code&&v([vn]),"KeyM"===e.code&&v([un]),"KeyN"===e.code&&v([mn]),"KeyC"===e.code&&v([yn]))}));const v=e=>{l.push(e)};return{addEvent:v,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const o=(p?24:12)*Math.sqrt(n.getCurrentZoom()),l=n.viewportDimToWorld({w:e*o,h:t*o});v([en,l.w,l.h]),f&&(f[0]-=l.w,f[1]-=l.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([ln,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([an,...e])}},setHotkeys:e=>{a=e}}}(v,window,V,o),U=sl.getImageUrl(e),R=()=>{const t=sl.getStartTs(e),n=sl.getFinishTs(e),o=C();a.setFinished(!!n),a.setDuration((n||o)-t)};R(),a.setPiecesDone(sl.getFinishedPiecesCount(e)),a.setPiecesTotal(sl.getPieceCount(e));const G=C();a.setActivePlayers(sl.getActivePlayers(e,G)),a.setIdlePlayers(sl.getIdlePlayers(e,G));const F=!!sl.getFinishTs(e);let j=F;const W=()=>j&&!F,H=()=>kl(hl,100),K=()=>Al(ml,!1),q=()=>Al(wl,!0),Y=()=>{const e=H();i.volume=e/100,i.play()},Q=()=>o===El?zl(yl,"#222222"):sl.getPlayerBgColor(e,t)||zl(yl,"#222222"),Z=()=>o===El?zl(fl,"#ffffff"):sl.getPlayerColor(e,t)||zl(fl,"#ffffff");let X="",J="",ee=!1;const te=e=>{ee=e;const[t,n]=e?[X,"grab"]:[J,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},ne=e=>{X=co.colorizedCanvas(r,c,e).toDataURL(),J=co.colorizedCanvas(d,u,e).toDataURL(),te(ee)};ne(Z());const oe=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},le=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,oe())},ie=()=>{w.paused=!w.paused,oe()},re=[];let de;let ce;if("play"===o?re.push(setInterval((()=>{R()}),1e3)):o===El&&oe(),"play"===o)Nn.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Cn:{const n=ae.decodePlayer(a);n.id!==t&&(sl.setPlayer(e,n.id,n),Nl=!0)}break;case bn:{const t=ae.decodePiece(a);sl.setPiece(e,t.idx,t),Nl=!0}break;case wn:sl.setPuzzleData(e,a),Nl=!0}j=!!sl.getFinishTs(e)}));else if(o===El){const t=(t,n)=>{const o=t;if(o[0]===Zt){const t=o[1];return sl.addPlayer(e,t,n),!0}if(o[0]===Xt){const t=sl.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return sl.addPlayer(e,t,n),!0}if(o[0]===Jt){const t=sl.getPlayerIdByIndex(e,o[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const l=o[2];return sl.handleInput(e,t,l,n),!0}return!1};let n=w.lastGameTs;const o=async()=>{w.logPointer+1>=w.log.length&&await b(e);const l=L();if(w.paused)return w.lastRealTs=l,void(de=setTimeout(o,50));const a=(l-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const o=w.log[w.logPointer],l=n+o[o.length-1],a=w.log[e],i=a[a.length-1],r=l+i;if(r>s){s+500*${let t=!1;const n=e.fps||60,o=e.slow||1,l=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=o*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,l(i);a(c/o),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{B.createKeyEvents();for(const n of B.consumeAll())if("play"===o){const o=n[0];if(o===en){const e=n[1],t=n[2],o=V.worldDimToViewport({w:e,h:t});Nl=!0,V.move(o.w,o.h)}else if(o===on){if(ue&&!sl.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=V.worldToViewport(e),o=Math.round(t.x-ue.x),l=Math.round(t.y-ue.y);Nl=!0,V.move(o,l),ue=t}}else if(o===rn)ne(n[1]);else if(o===tn){const e={x:n[1],y:n[2]};ue=V.worldToViewport(e),te(!0)}else if(o===nn)ue=null,te(!1);else if(o===ln){const e={x:n[1],y:n[2]};Nl=!0,V.zoom("in",V.worldToViewport(e))}else if(o===an){const e={x:n[1],y:n[2]};Nl=!0,V.zoom("out",V.worldToViewport(e))}else o===cn?a.togglePreview():o===un?a.toggleSoundsEnabled():o===mn?a.togglePlayerNames():o===yn?O():o===fn?(Ml=!Ml,Nl=!0):o===vn&&(Dl=!Dl,Nl=!0);const l=C();sl.handleInput(e,t,n,l,(e=>{K()&&Y()})).length>0&&(Nl=!0),Nn.sendClientEvent(n)}else if(o===El){const e=n[0];if(e===pn)ie();else if(e===hn)se();else if(e===gn)le();else if(e===en){const e=n[1],t=n[2];Nl=!0,V.move(e,t)}else if(e===on){if(ue){const e={x:n[1],y:n[2]},t=V.worldToViewport(e),o=Math.round(t.x-ue.x),l=Math.round(t.y-ue.y);Nl=!0,V.move(o,l),ue=t}}else if(e===rn)ne(n[1]);else if(e===tn){const e={x:n[1],y:n[2]};ue=V.worldToViewport(e),te(!0)}else if(e===nn)ue=null,te(!1);else if(e===ln){const e={x:n[1],y:n[2]};Nl=!0,V.zoom("in",V.worldToViewport(e))}else if(e===an){const e={x:n[1],y:n[2]};Nl=!0,V.zoom("out",V.worldToViewport(e))}else e===cn?a.togglePreview():e===un?a.toggleSoundsEnabled():e===mn?a.togglePlayerNames():e===yn?O():e===fn?(Ml=!Ml,Nl=!0):e===vn&&(Dl=!Dl,Nl=!0)}j=!!sl.getFinishTs(e),W()&&(N.update(),Nl=!0)},render:async()=>{if(!Nl)return;const n=C();let l,s,i;window.DEBUG&&ho(0),_.fillStyle=Q(),_.fillRect(0,0,v.width,v.height),window.DEBUG&&mo("clear done"),l=V.worldToViewportRaw(I),s=V.worldDimToViewportRaw(E),_.fillStyle="rgba(255, 255, 255, .3)",_.fillRect(l.x,l.y,s.w,s.h),window.DEBUG&&mo("board done");const r=sl.getPiecesSortedByZIndex(e);window.DEBUG&&mo("get tiles done"),s=V.worldDimToViewportRaw(M);for(const e of r)(-1===e.owner?Ml:Dl)&&(i=D[e.idx],l=V.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),_.drawImage(i,0,0,i.width,i.height,l.x,l.y,s.w,s.h));window.DEBUG&&mo("tiles done");const d=[];for(const a of sl.getActivePlayers(e,n))c=a,(o===El||c.id!==t)&&(i=await f(a),l=V.worldToViewport(a),_.drawImage(i,l.x-g,l.y-m),q()&&d.push([`${a.name} (${a.points})`,l.x,l.y+h]));var c;_.fillStyle="white",_.textAlign="center";for(const[e,t,o]of d)_.fillText(e,t,o);window.DEBUG&&mo("players done"),a.setActivePlayers(sl.getActivePlayers(e,n)),a.setIdlePlayers(sl.getIdlePlayers(e,n)),a.setPiecesDone(sl.getFinishedPiecesCount(e)),window.DEBUG&&mo("HUD done"),W()&&N.render(),Nl=!1}}),{setHotkeys:e=>{B.setHotkeys(e)},onBgChange:e=>{Sl(yl,e),B.addEvent([sn,e])},onColorChange:e=>{Sl(fl,e),B.addEvent([rn,e])},onNameChange:e=>{Sl(vl,e),B.addEvent([dn,e])},onSoundsEnabledChange:e=>{Pl(ml,e)},onSoundsVolumeChange:e=>{xl(hl,e),Y()},onShowPlayerNamesChange:e=>{Pl(wl,e)},replayOnSpeedUp:le,replayOnSpeedDown:se,replayOnPauseToggle:ie,previewImageUrl:U,player:{background:Q(),color:Z(),name:o===El?zl(vl,"anon"):sl.getPlayerName(e,t)||zl(vl,"anon"),soundsEnabled:K(),soundsVolume:H(),showPlayerNames:q()},game:sl.get(e),disconnect:Nn.disconnect,connect:x,unload:()=>{re.forEach((e=>{clearInterval(e)})),de&&clearTimeout(de),ce&&ce.stop()}}}var Vl=e({name:"game",components:{PuzzleStatus:zt,Scores:xt,SettingsOverlay:It,PreviewOverlay:Rt,InfoOverlay:Gt,ConnectionOverlay:_n,HelpOverlay:$n},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,cuttingPuzzle:!0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},game:null,previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await _l(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setPuzzleCut:()=>{this.cuttingPuzzle=!1},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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 Ol={id:"game"},Bl={key:1,class:"overlay"},Ul=n("div",{class:"overlay-content"},[n("div",null,"⏳ Cutting puzzle, please wait... ⏳")],-1),Rl={class:"menu"},$l={class:"tabs"},Gl=i("🧩 Puzzles");Vl.render=function(e,i,r,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("info-overlay"),y=a("help-overlay"),f=a("connection-overlay"),v=a("puzzle-status"),w=a("router-link"),b=a("scores");return s(),t("div",Ol,[p(n(g,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),e.g.game?p((s(),t(m,{key:0,onBgclick:i[4]||(i[4]=t=>e.toggle("info",!0)),game:e.g.game},null,8,["game"])),[[x,"info"===e.overlay]]):l("",!0),p(n(y,{onBgclick:i[5]||(i[5]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),e.cuttingPuzzle?(s(),t("div",Bl,[Ul])):l("",!0),n(f,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(v,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",Rl,[n("div",$l,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Gl])),_:1}),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("info",!0))},"ℹ️ Info"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("help",!0))},"⌨️ Hotkeys")])]),n(b,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var Ll=e({name:"replay",components:{PuzzleStatus:zt,Scores:xt,SettingsOverlay:It,PreviewOverlay:Rt,InfoOverlay:Gt,HelpOverlay:$n},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,cuttingPuzzle:!0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},game:null,previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await _l(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,El,this.$el,{setPuzzleCut:()=>{this.cuttingPuzzle=!1},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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 Fl={id:"replay"},jl={key:1,class:"overlay"},Wl=n("div",{class:"overlay-content"},[n("div",null,"⏳ Cutting puzzle, please wait... ⏳")],-1),Hl={class:"menu"},Kl={class:"tabs"},ql=i("🧩 Puzzles");Ll.render=function(e,i,d,c,u,g){const h=a("settings-overlay"),m=a("preview-overlay"),y=a("info-overlay"),f=a("help-overlay"),v=a("puzzle-status"),w=a("router-link"),b=a("scores");return s(),t("div",Fl,[p(n(h,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(m,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),e.g.game?p((s(),t(y,{key:0,onBgclick:i[4]||(i[4]=t=>e.toggle("info",!0)),game:e.g.game},null,8,["game"])),[[x,"info"===e.overlay]]):l("",!0),p(n(f,{onBgclick:i[5]||(i[5]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),e.cuttingPuzzle?(s(),t("div",jl,[Wl])):l("",!0),n(v,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[8]||(i[8]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",Hl,[n("div",Kl,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[ql])),_:1}),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[11]||(i[11]=t=>e.toggle("info",!0))},"ℹ️ Info"),n("div",{class:"opener",onClick:i[12]||(i[12]=t=>e.toggle("help",!0))},"⌨️ Hotkeys")])]),n(b,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=function(){let e=zl("ID","");return e||(e=ae.uniqId(),Sl("ID",e)),e}(),t=function(){let e=zl("SECRET","");return e||(e=ae.uniqId(),Sl("SECRET",e)),e}();O(e),B(t);const n=await _("/api/me",{}),o=await n.json(),l=await _("/api/conf",{}),a=await l.json(),s=k({history:P(),routes:[{name:"index",path:"/",component:Z},{name:"new-game",path:"/new-game",component:pt},{name:"game",path:"/g/:id",component:Vl},{name:"replay",path:"/replay/:id",component:Ll}]});s.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const i=A(S);i.config.globalProperties.$me=o,i.config.globalProperties.$config=a,i.config.globalProperties.$clientId=e,i.use(s),i.mount("#app")})(); diff --git a/build/public/assets/index.97691b3e.js b/build/public/assets/index.97691b3e.js deleted file mode 100644 index 11c7d04..0000000 --- a/build/public/assets/index.97691b3e.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as n,w as l,b as o,r as a,o as s,e as i,t as r,F as d,f as c,g as u,h as p,v as g,i as h,j as m,p as y,k as f,l as v,m as w,n as b,q as C,s as x,u as k,x as P,y as A}from"./vendor.684f7bc8.js";var S=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const z={id:"app"},T={key:0,class:"nav"},I=i("Games overview"),E=i("New game");S.render=function(e,i,r,d,c,u){const p=a("router-link"),g=a("router-view");return s(),t("div",z,[e.showNav?(s(),t("ul",T,[n("li",null,[n(p,{class:"btn",to:{name:"index"}},{default:l((()=>[I])),_:1})]),n("li",null,[n(p,{class:"btn",to:{name:"new-game"}},{default:l((()=>[E])),_:1})])])):o("",!0),n(g)])};let M="",D="";const N=async(e,t,n)=>new Promise(((l,o)=>{const a=new window.XMLHttpRequest;a.open(e,t,!0),a.withCredentials=!0;for(const e in n.headers||{})a.setRequestHeader(e,n.headers[e]);a.setRequestHeader("Client-Id",M),a.setRequestHeader("Client-Secret",D),a.addEventListener("load",(function(e){l({status:this.status,text:this.responseText,json:async()=>JSON.parse(this.responseText)})})),a.addEventListener("error",(function(e){o(new Error("xhr error"))})),a.upload&&n.onUploadProgress&&a.upload.addEventListener("progress",(function(e){n.onUploadProgress&&n.onUploadProgress(e)})),a.send(n.body||null)}));var _=(e,t)=>N("get",e,t),V=(e,t)=>N("post",e,t),O=e=>{M=e},B=e=>{D=e};const U=864e5,R=e=>{const t=Math.floor(e/U);e%=U;const n=Math.floor(e/36e5);e%=36e5;const l=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${l}m ${Math.floor(e/1e3)}s`};var $=1,G=1e3,L=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},F=(e,t)=>R(t-e),j=R,W=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",l=e,o=t||L();return`${n} ${F(l,o)}`}}});const H={class:"game-info-text"},K=n("br",null,null,-1),q=n("br",null,null,-1),Y=n("br",null,null,-1),Q=i(" ↪️ Watch replay ");W.render=function(e,d,c,u,p,g){const h=a("router-link");return s(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:l((()=>[n("span",H,[i(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),K,i(" 👥 "+r(e.game.players),1),q,i(" "+r(e.time(e.game.started,e.game.finished)),1),Y])])),_:1},8,["to"]),e.game.hasReplay?(s(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:l((()=>[Q])),_:1},8,["to"])):o("",!0)],4)};var Z=e({components:{GameTeaser:W},data:()=>({gamesRunning:[],gamesFinished:[]}),async created(){const e=await _("/api/index-data",{}),t=await e.json();this.gamesRunning=t.gamesRunning,this.gamesFinished=t.gamesFinished}});const X=n("h1",null,"Running games",-1),J=n("h1",null,"Finished games",-1);Z.render=function(e,l,o,i,r,u){const p=a("game-teaser");return s(),t("div",null,[X,(s(!0),t(d,null,c(e.gamesRunning,((e,l)=>(s(),t("div",{class:"game-teaser-wrap",key:l},[n(p,{game:e},null,8,["game"])])))),128)),J,(s(!0),t(d,null,c(e.gamesFinished,((e,l)=>(s(),t("div",{class:"game-teaser-wrap",key:l},[n(p,{game:e},null,8,["game"])])))),128))])};var ee=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}},canEdit(){return!!this.$me.id&&this.$me.id===this.image.uploaderUserId}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});ee.render=function(e,n,l,a,i,r){return s(),t("div",{class:"imageteaser",style:e.style,onClick:n[2]||(n[2]=(...t)=>e.onClick&&e.onClick(...t))},[e.canEdit?(s(),t("div",{key:0,class:"btn edit",onClick:n[1]||(n[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")):o("",!0)],4)};var te=e({name:"image-library",components:{ImageTeaser:ee},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});te.render=function(e,n,l,o,i,r){const u=a("image-teaser");return s(),t("div",null,[(s(!0),t(d,null,c(e.images,((n,l)=>(s(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:l},null,8,["image","onClick","onEditClick"])))),128))])};class ne{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),l=t[n];t[n]=t[e],t[e]=l}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new ne(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const le=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},oe=(...e)=>{const t=t=>(...n)=>{const l=new Date,o=le(l.getHours(),"00"),a=le(l.getMinutes(),"00"),s=le(l.getSeconds(),"00");console[t](`${o}:${a}:${s}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var ae={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",ne.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode,e.shapeMode,e.snapMode]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:ne.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6],shapeMode:e[7],snapMode:e[8]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const l=[n,e[n]].map(encodeURIComponent);t.push(l.join("="))}return 0===t.length?"":`?${t.join("&")}`}};const se={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};se.render=function(e,n,l,o,a,i){return s(),t("div",{style:i.style,title:l.title},null,12,["title"])};var ie=e({name:"tags-input",props:{modelValue:{type:Array,required:!0},autocompleteTags:{type:Function}},emits:{"update:modelValue":null},data:()=>({input:"",values:[],autocomplete:{idx:-1,values:[]}}),created(){this.values=this.modelValue},methods:{onKeyUp(e){return"ArrowDown"===e.code&&this.autocomplete.values.length>0?(this.autocomplete.idx0?(this.autocomplete.idx>0&&this.autocomplete.idx--,e.stopPropagation(),!1):","===e.key?(this.add(),e.stopPropagation(),!1):void(this.input&&this.autocompleteTags?(this.autocomplete.values=this.autocompleteTags(this.input,this.values),this.autocomplete.idx=-1):(this.autocomplete.values=[],this.autocomplete.idx=-1))},addVal(e){const t=e.replace(/,/g,"").trim();t&&(this.values.includes(t)||this.values.push(t),this.input="",this.autocomplete.values=[],this.autocomplete.idx=-1,this.$emit("update:modelValue",this.values),this.$refs.input.focus())},add(){const e=this.autocomplete.idx>=0?this.autocomplete.values[this.autocomplete.idx]:this.input;this.addVal(e)},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const re=m();y("data-v-a4fa5e7e");const de={key:0,class:"autocomplete"};f();const ce=re(((e,l,a,i,u,m)=>(s(),t("div",null,[p(n("input",{ref:"input",class:"input",type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.input=t),placeholder:"Plants, People",onChange:l[2]||(l[2]=(...t)=>e.onChange&&e.onChange(...t)),onKeydown:l[3]||(l[3]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:l[4]||(l[4]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[g,e.input]]),e.autocomplete.values?(s(),t("div",de,[n("ul",null,[(s(!0),t(d,null,c(e.autocomplete.values,((n,l)=>(s(),t("li",{key:l,class:{active:l===e.autocomplete.idx},onClick:t=>e.addVal(n)},r(n),11,["onClick"])))),128))])])):o("",!0),(s(!0),t(d,null,c(e.values,((n,l)=>(s(),t("span",{key:l,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));ie.render=ce,ie.__scopeId="data-v-a4fa5e7e";const ue=oe("NewImageDialog.vue");var pe=e({name:"new-image-dialog",components:{ResponsiveImage:se,TagsInput:ie},props:{autocompleteTags:{type:Function},uploadProgress:{type:Number},uploading:{type:String}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[],droppable:!1}),computed:{uploadProgressPercent(){return this.uploadProgress?Math.round(100*this.uploadProgress):0},canPostToGallery(){return!this.uploading&&!(!this.previewUrl||!this.file)},canSetupGameClick(){return!this.uploading&&!(!this.previewUrl||!this.file)}},methods:{imageFromDragEvt(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.items;if(!n||0===n.length)return null;const l=n[0];return l.type.startsWith("image/")?l:null},onFileSelect(e){const t=e.target;if(!t.files)return;const n=t.files[0];n&&this.preview(n)},preview(e){const t=new FileReader;t.readAsDataURL(e),t.onload=t=>{this.previewUrl=t.target.result,this.file=e}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})},onDrop(e){this.droppable=!1;const t=this.imageFromDragEvt(e);if(!t)return!1;const n=t.getAsFile();return!!n&&(this.file=n,this.preview(n),e.preventDefault(),!1)},onDragover(e){return!!this.imageFromDragEvt(e)&&(this.droppable=!0,e.preventDefault(),!1)},onDragleave(){ue.info("onDragleave"),this.droppable=!1}}});const ge=n("div",{class:"drop-target"},null,-1),he={key:0,class:"has-image"},me={key:1},ye={class:"upload"},fe=n("span",{class:"btn"},"Upload File",-1),ve={class:"area-settings"},we=n("td",null,[n("label",null,"Title")],-1),be=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ce=n("td",null,[n("label",null,"Tags")],-1),xe={class:"area-buttons"},ke=i("🖼️ Post to gallery"),Pe=i("🧩 Post to gallery "),Ae=n("br",null,null,-1),Se=i(" + set up game");pe.render=function(e,l,o,c,h,m){const y=a("responsive-image"),f=a("tags-input");return s(),t("div",{class:"overlay new-image-dialog",onClick:l[11]||(l[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[10]||(l[10]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl,droppable:e.droppable}],onDrop:l[3]||(l[3]=(...t)=>e.onDrop&&e.onDrop(...t)),onDragover:l[4]||(l[4]=(...t)=>e.onDragover&&e.onDragover(...t)),onDragleave:l[5]||(l[5]=(...t)=>e.onDragleave&&e.onDragleave(...t))},[ge,e.previewUrl?(s(),t("div",he,[n("span",{class:"remove btn",onClick:l[1]||(l[1]=t=>e.previewUrl="")},"X"),n(y,{src:e.previewUrl},null,8,["src"])])):(s(),t("div",me,[n("label",ye,[n("input",{type:"file",style:{display:"none"},onChange:l[2]||(l[2]=(...t)=>e.onFileSelect&&e.onFileSelect(...t)),accept:"image/*"},null,32),fe])]))],34),n("div",ve,[n("table",null,[n("tr",null,[we,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":l[6]||(l[6]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),be,n("tr",null,[Ce,n("td",null,[n(f,{modelValue:e.tags,"onUpdate:modelValue":l[7]||(l[7]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",xe,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:l[8]||(l[8]=(...t)=>e.postToGallery&&e.postToGallery(...t))},["postToGallery"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[ke],64))],8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:l[9]||(l[9]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},["setupGame"===e.uploading?(s(),t(d,{key:0},[i("Uploading ("+r(e.uploadProgressPercent)+"%)",1)],64)):(s(),t(d,{key:1},[Pe,Ae,Se],64))],8,["disabled"])])])])};var ze=e({name:"edit-image-dialog",components:{ResponsiveImage:se,TagsInput:ie},props:{image:{type:Object,required:!0},autocompleteTags:{type:Function}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const Te={class:"area-image"},Ie={class:"has-image"},Ee={class:"area-settings"},Me=n("td",null,[n("label",null,"Title")],-1),De=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),Ne=n("td",null,[n("label",null,"Tags")],-1),_e={class:"area-buttons"};var Ve,Oe,Be,Ue,Re,$e,Ge,Le;ze.render=function(e,l,o,i,r,d){const c=a("responsive-image"),h=a("tags-input");return s(),t("div",{class:"overlay edit-image-dialog",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("div",Te,[n("div",Ie,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ee,[n("table",null,[n("tr",null,[Me,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[g,e.title]])])]),De,n("tr",null,[Ne,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[2]||(l[2]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",_e,[n("button",{class:"btn",onClick:l[3]||(l[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])},(Oe=Ve||(Ve={}))[Oe.Flat=0]="Flat",Oe[Oe.Out=1]="Out",Oe[Oe.In=-1]="In",(Ue=Be||(Be={}))[Ue.FINAL=0]="FINAL",Ue[Ue.ANY=1]="ANY",($e=Re||(Re={}))[$e.NORMAL=0]="NORMAL",$e[$e.ANY=1]="ANY",$e[$e.FLAT=2]="FLAT",(Le=Ge||(Ge={}))[Le.NORMAL=0]="NORMAL",Le[Le.REAL=1]="REAL";var Fe=e({name:"new-game-dialog",components:{ResponsiveImage:se},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Be.ANY,shapeMode:Re.NORMAL,snapMode:Ge.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt,snapMode:this.snapModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},shapeModeInt(){return parseInt(`${this.shapeMode}`,10)},snapModeInt(){return parseInt(`${this.snapMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const je={class:"area-image"},We={class:"has-image"},He={key:0,class:"image-title"},Ke={key:0,class:"image-title-title"},qe={key:1,class:"image-title-dim"},Ye={class:"area-settings"},Qe=n("td",null,[n("label",null,"Pieces")],-1),Ze=n("td",null,[n("label",null,"Scoring: ")],-1),Xe=i(" Any (Score when pieces are connected to each other or on final location)"),Je=n("br",null,null,-1),et=i(" Final (Score when pieces are put to their final location)"),tt=n("td",null,[n("label",null,"Shapes: ")],-1),nt=i(" Normal"),lt=n("br",null,null,-1),ot=i(" Any (flat pieces can occur anywhere)"),at=n("br",null,null,-1),st=i(" Flat (all pieces flat on all sides)"),it=n("td",null,[n("label",null,"Snapping: ")],-1),rt=i(" Normal (pieces snap to final destination and to each other)"),dt=n("br",null,null,-1),ct=i(" Real (pieces snap only to corners, already snapped pieces and to each other)"),ut={class:"area-buttons"};Fe.render=function(e,l,i,d,c,h){const m=a("responsive-image");return s(),t("div",{class:"overlay new-game-dialog",onClick:l[11]||(l[11]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[10]||(l[10]=u((()=>{}),["stop"]))},[n("div",je,[n("div",We,[n(m,{src:e.image.url,title:e.image.title},null,8,["src","title"])]),e.image.title||e.image.width||e.image.height?(s(),t("div",He,[e.image.title?(s(),t("span",Ke,'"'+r(e.image.title)+'"',1)):o("",!0),e.image.width||e.image.height?(s(),t("span",qe,"("+r(e.image.width)+" ✕ "+r(e.image.height)+")",1)):o("",!0)])):o("",!0)]),n("div",Ye,[n("table",null,[n("tr",null,[Qe,n("td",null,[p(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.tiles=t)},null,512),[[g,e.tiles]])])]),n("tr",null,[Ze,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[2]||(l[2]=t=>e.scoreMode=t),value:"1"},null,512),[[v,e.scoreMode]]),Xe]),Je,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[3]||(l[3]=t=>e.scoreMode=t),value:"0"},null,512),[[v,e.scoreMode]]),et])])]),n("tr",null,[tt,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[4]||(l[4]=t=>e.shapeMode=t),value:"0"},null,512),[[v,e.shapeMode]]),nt]),lt,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[5]||(l[5]=t=>e.shapeMode=t),value:"1"},null,512),[[v,e.shapeMode]]),ot]),at,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[6]||(l[6]=t=>e.shapeMode=t),value:"2"},null,512),[[v,e.shapeMode]]),st])])]),n("tr",null,[it,n("td",null,[n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[7]||(l[7]=t=>e.snapMode=t),value:"0"},null,512),[[v,e.snapMode]]),rt]),dt,n("label",null,[p(n("input",{type:"radio","onUpdate:modelValue":l[8]||(l[8]=t=>e.snapMode=t),value:"1"},null,512),[[v,e.snapMode]]),ct])])])])]),n("div",ut,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:l[9]||(l[9]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var pt=e({components:{ImageLibrary:te,NewImageDialog:pe,EditImageDialog:ze,NewGameDialog:Fe},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:"",uploading:"",uploadProgress:0}),async created(){await this.loadImages()},computed:{relevantTags(){return this.tags.filter((e=>e.total>0))}},methods:{autocompleteTags(e,t){return this.tags.filter((n=>!t.includes(n.title)&&n.title.toLowerCase().startsWith(e.toLowerCase()))).slice(0,10).map((e=>e.title))},toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await _(`/api/newgame-data${ae.asQueryArgs(this.filters)}`,{}),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){this.uploadProgress=0;const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await V("/api/upload",{body:t,onUploadProgress:e=>{this.uploadProgress=e.loaded/e.total}});return this.uploadProgress=1,await n.json()},async saveImage(e){const t=await V("/api/save-image",{headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){const t=await this.saveImage(e);t.ok?(this.dialog="",await this.loadImages()):alert(t.error)},async postToGalleryClick(e){this.uploading="postToGallery",await this.uploadImage(e),this.uploading="",this.dialog="",await this.loadImages()},async setupGameClick(e){this.uploading="setupGame";const t=await this.uploadImage(e);this.uploading="",this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await V("/api/newgame",{headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const gt={class:"upload-image-teaser"},ht=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),mt={key:0},yt=i(" Tags: "),ft=i(" Sort by: "),vt=n("option",{value:"date_desc"},"Newest first",-1),wt=n("option",{value:"date_asc"},"Oldest first",-1),bt=n("option",{value:"alpha_asc"},"A-Z",-1),Ct=n("option",{value:"alpha_desc"},"Z-A",-1);pt.render=function(e,l,i,u,g,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),v=a("new-game-dialog");return s(),t("div",null,[n("div",gt,[n("div",{class:"btn btn-big",onClick:l[1]||(l[1]=t=>e.dialog="new-image")},"Upload your image"),ht]),n("div",null,[e.tags.length>0?(s(),t("label",mt,[yt,(s(!0),t(d,null,c(e.relevantTags,((n,l)=>(s(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:l,onClick:t=>e.toggleTag(n)},r(n.title)+" ("+r(n.total)+")",11,["onClick"])))),128))])):o("",!0),n("label",null,[ft,p(n("select",{"onUpdate:modelValue":l[2]||(l[2]=t=>e.filters.sort=t),onChange:l[3]||(l[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[vt,wt,bt,Ct],544),[[w,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(s(),t(y,{key:0,autocompleteTags:e.autocompleteTags,onBgclick:l[4]||(l[4]=t=>e.dialog=""),uploadProgress:e.uploadProgress,uploading:e.uploading,onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["autocompleteTags","uploadProgress","uploading","onPostToGalleryClick","onSetupGameClick"])):o("",!0),"edit-image"===e.dialog?(s(),t(f,{key:1,autocompleteTags:e.autocompleteTags,onBgclick:l[5]||(l[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["autocompleteTags","onSaveClick","image"])):o("",!0),e.image&&"new-game"===e.dialog?(s(),t(v,{key:2,onBgclick:l[6]||(l[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):o("",!0)])};var xt=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 kt={class:"scores"},Pt=n("div",null,"Scores",-1),At=n("td",null,"⚡",-1),St=n("td",null,"💤",-1);xt.render=function(e,l,o,a,i,u){return s(),t("div",kt,[Pt,n("table",null,[(s(!0),t(d,null,c(e.actives,((e,l)=>(s(),t("tr",{key:l,style:{color:e.color}},[At,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,l)=>(s(),t("tr",{key:l,style:{color:e.color}},[St,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 j(this.duration)}}});const Tt={class:"timer"};zt.render=function(e,l,o,a,i,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),b(e.$slots,"default")])};var It=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:{type:Object,required:!0}},methods:{updateVolume(e){this.modelValue.soundsVolume=e.target.value},decreaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)-5;this.modelValue.soundsVolume=Math.max(0,e)},increaseVolume(){const e=parseInt(this.modelValue.soundsVolume,10)+5;this.modelValue.soundsVolume=Math.min(100,e)}},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const Et=m();y("data-v-4d56fc17");const Mt=n("td",null,[n("label",null,"Background: ")],-1),Dt=n("td",null,[n("label",null,"Color: ")],-1),Nt=n("td",null,[n("label",null,"Name: ")],-1),_t=n("td",null,[n("label",null,"Sounds: ")],-1),Vt=n("td",null,[n("label",null,"Sounds Volume: ")],-1),Ot={class:"sound-volume"},Bt=n("td",null,[n("label",null,"Show player names: ")],-1);f();const Ut=Et(((e,l,o,a,i,r)=>(s(),t("div",{class:"overlay transparent",onClick:l[10]||(l[10]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:l[9]||(l[9]=u((()=>{}),["stop"]))},[n("tr",null,[Mt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":l[1]||(l[1]=t=>e.modelValue.background=t)},null,512),[[g,e.modelValue.background]])])]),n("tr",null,[Dt,n("td",null,[p(n("input",{type:"color","onUpdate:modelValue":l[2]||(l[2]=t=>e.modelValue.color=t)},null,512),[[g,e.modelValue.color]])])]),n("tr",null,[Nt,n("td",null,[p(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":l[3]||(l[3]=t=>e.modelValue.name=t)},null,512),[[g,e.modelValue.name]])])]),n("tr",null,[_t,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":l[4]||(l[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[C,e.modelValue.soundsEnabled]])])]),n("tr",null,[Vt,n("td",Ot,[n("span",{onClick:l[5]||(l[5]=(...t)=>e.decreaseVolume&&e.decreaseVolume(...t))},"🔉"),n("input",{type:"range",min:"0",max:"100",value:e.modelValue.soundsVolume,onChange:l[6]||(l[6]=(...t)=>e.updateVolume&&e.updateVolume(...t))},null,40,["value"]),n("span",{onClick:l[7]||(l[7]=(...t)=>e.increaseVolume&&e.increaseVolume(...t))},"🔊")])]),n("tr",null,[Bt,n("td",null,[p(n("input",{type:"checkbox","onUpdate:modelValue":l[8]||(l[8]=t=>e.modelValue.showPlayerNames=t)},null,512),[[C,e.modelValue.showPlayerNames]])])])])]))));It.render=Ut,It.__scopeId="data-v-4d56fc17";var Rt=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const $t={class:"preview"};Rt.render=function(e,l,o,a,i,r){return s(),t("div",{class:"overlay",onClick:l[1]||(l[1]=t=>e.$emit("bgclick"))},[n("div",$t,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var Gt=e({name:"help-overlay",emits:{bgclick:null},props:{game:{type:Object,required:!0}},computed:{scoreMode(){switch(this.game.scoreMode){case Be.ANY:return["Any","Score when pieces are connected to each other or on final location"];case Be.FINAL:default:return["Final","Score when pieces are put to their final location"]}},shapeMode(){switch(this.game.shapeMode){case Re.FLAT:return["Flat","all pieces flat on all sides"];case Re.ANY:return["Any","flat pieces can occur anywhere"];case Re.NORMAL:default:return["Normal",""]}},snapMode(){switch(this.game.snapMode){case Ge.REAL:return["Real","pieces snap only to corners, already snapped pieces and to each other"];case Ge.NORMAL:default:return["Normal","pieces snap to final destination and to each other"]}}}});const Lt=n("tr",null,[n("td",{colspan:"2"},"Info about this puzzle")],-1),Ft=n("td",null,"Image Title: ",-1),jt=n("td",null,"Scoring: ",-1),Wt=n("td",null,"Shapes: ",-1),Ht=n("td",null,"Snapping: ",-1);Gt.render=function(e,l,o,a,i,d){return s(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[Lt,n("tr",null,[Ft,n("td",null,r(e.game.puzzle.info.image.title),1)]),n("tr",null,[jt,n("td",null,[n("span",{title:e.snapMode[1]},r(e.scoreMode[0]),9,["title"])])]),n("tr",null,[Wt,n("td",null,[n("span",{title:e.snapMode[1]},r(e.shapeMode[0]),9,["title"])])]),n("tr",null,[Ht,n("td",null,[n("span",{title:e.snapMode[1]},r(e.snapMode[0]),9,["title"])])])])])};var Kt=1,qt=4,Yt=2,Qt=3,Zt=2,Xt=4,Jt=3,en=9,tn=1,nn=2,ln=3,on=4,an=5,sn=6,rn=7,dn=8,cn=10,un=11,pn=12,gn=13,hn=14,mn=15,yn=16,fn=17,vn=18,wn=1,bn=2,Cn=3;const xn=oe("Communication.js");let kn,Pn=[],An=e=>{Pn.push(e)},Sn=[],zn=e=>{Sn.push(e)};let Tn=0;const In=e=>{Tn!==e&&(Tn=e,zn(e))};function En(e){if(2===Tn)try{kn.send(JSON.stringify(e))}catch(t){xn.info("unable to send message.. maybe because ws is invalid?")}}let Mn,Dn;var Nn={connect:function(e,t,n){return Mn=0,Dn={},In(3),new Promise((l=>{kn=new WebSocket(e,n+"|"+t),kn.onopen=()=>{In(2),En([Qt])},kn.onmessage=e=>{const t=JSON.parse(e.data),o=t[0];if(o===qt){const e=t[1];l(e)}else{if(o!==Kt)throw`[ 2021-05-09 invalid connect msgType ${o} ]`;{const e=t[1],l=t[2];if(e===n&&Dn[l])return void delete Dn[l];An(t)}}},kn.onerror=()=>{throw In(1),"[ 2021-05-15 onerror ]"},kn.onclose=e=>{4e3===e.code||1001===e.code?In(4):In(1)}}))},requestReplayData:async function(e,t){const n={gameId:e,offset:t},l=await _(`/api/replay-data${ae.asQueryArgs(n)}`,{});return await l.json()},disconnect:function(){kn&&kn.close(4e3),Mn=0,Dn={}},sendClientEvent:function(e){Mn++,Dn[Mn]=e,En([Yt,Mn,Dn[Mn]])},onServerChange:function(e){An=e;for(const t of Pn)An(t);Pn=[]},onConnectionStateChange:function(e){zn=e;for(const t of Sn)zn(t);Sn=[]},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},_n=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Nn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Nn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const Vn={key:0,class:"overlay connection-lost"},On={key:0,class:"overlay-content"},Bn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Un={key:1,class:"overlay-content"},Rn=n("div",null,"Connecting...",-1);_n.render=function(e,l,a,i,r,d){return e.show?(s(),t("div",Vn,[e.lostConnection?(s(),t("div",On,[Bn,n("span",{class:"btn",onClick:l[1]||(l[1]=t=>e.$emit("reconnect"))},"Reconnect")])):o("",!0),e.connecting?(s(),t("div",Un,[Rn])):o("",!0)])):o("",!0)};var $n=e({name:"help-overlay",emits:{bgclick:null}});const Gn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),i("/"),n("kbd",null,"↑"),i("/🖱️")])])],-1),Ln=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),i("/"),n("kbd",null,"↓"),i("/🖱️")])])],-1),Fn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),i("/"),n("kbd",null,"←"),i("/🖱️")])])],-1),jn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),i("/"),n("kbd",null,"→"),i("/🖱️")])])],-1),Wn=n("tr",null,[n("td"),n("td",null,[n("div",null,[i("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Hn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),i("/🖱️-Wheel")])])],-1),Kn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),i("/🖱️-Wheel")])])],-1),qn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Yn=n("tr",null,[n("td",null,"🎯 Center puzzle in screen:"),n("td",null,[n("div",null,[n("kbd",null,"C")])])],-1),Qn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Zn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),Xn=n("tr",null,[n("td",null,"👤 Toggle player names:"),n("td",null,[n("div",null,[n("kbd",null,"N")])])],-1),Jn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1),el=n("tr",null,[n("td",null,"⏫ Speed up (replay):"),n("td",null,[n("div",null,[n("kbd",null,"I")])])],-1),tl=n("tr",null,[n("td",null,"⏬ Speed down (replay):"),n("td",null,[n("div",null,[n("kbd",null,"O")])])],-1),nl=n("tr",null,[n("td",null,"⏸️ Pause (replay):"),n("td",null,[n("div",null,[n("kbd",null,"P")])])],-1);$n.render=function(e,l,o,a,i,r){return s(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[Gn,Ln,Fn,jn,Wn,Hn,Kn,qn,Yn,Qn,Zn,Xn,Jn,el,tl,nl])])};var ll=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.bb97cb07.mp3"}),ol=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),al=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),sl=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),il=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function rl(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var dl={createCanvas:rl,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const l=rl(t,n);return l.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(l)},colorizedCanvas:function(e,t,n){const l=rl(e.width,e.height),o=l.getContext("2d");return o.save(),o.drawImage(t,0,0),o.fillStyle=n,o.globalCompositeOperation="source-in",o.fillRect(0,0,t.width,t.height),o.restore(),o.save(),o.globalCompositeOperation="destination-over",o.drawImage(e,0,0),o.restore(),l}};const cl=oe("Debug.js");let ul=0,pl=0;var gl=e=>{ul=performance.now(),pl=e},hl=e=>{const t=performance.now(),n=t-ul;n>pl&&cl.log(e+": "+n),ul=t};function ml(e,t){const n=e.x-t.x,l=e.y-t.y;return Math.sqrt(n*n+l*l)}function yl(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var fl={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:ml,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:yl,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 ml(yl(e),yl(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const vl=oe("PuzzleGraphics.js");function wl(e,t){const n=ae.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var bl={loadPuzzleBitmaps:async function(e){const t=await dl.loadImageToBitmap(e.info.imageUrl),n=await dl.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){vl.log("start createPuzzleTileBitmaps");const l=n.tileSize,o=n.tileMarginWidth,a=n.tileDrawSize,s=l/100,i=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,a={x:o,y:o},r=fl.pointAdd(a,{x:l,y:0}),c=fl.pointAdd(r,{x:0,y:l}),u=fl.pointSub(c,{x:l,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let l=0;lae.decodePiece(Cl[e].puzzle.tiles[t]),Ol=(e,t)=>Vl(e,t).group,Bl=(e,t)=>{const n=Cl[e].puzzle.info;return 0===t||t===n.tilesX-1||t===n.tiles-n.tilesX||t===n.tiles-1},Ul=(e,t)=>{const n=Cl[e].puzzle.info,l={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},o=function(e,t){const n=Cl[e].puzzle.info,l=ae.coordByPieceIdx(n,t),o=l.x*n.tileSize,a=l.y*n.tileSize;return{x:o,y:a}}(e,t);return fl.pointAdd(l,o)},Rl=(e,t)=>Vl(e,t).pos,$l=e=>{const t=lo(e),n=oo(e),l=Math.round(t/4),o=Math.round(n/4);return{x:0-l,y:0-o,w:t+2*l,h:n+2*o}},Gl=(e,t)=>{const n=Wl(e),l=Vl(e,t);return{x:l.pos.x,y:l.pos.y,w:n,h:n}},Ll=(e,t)=>Vl(e,t).z,Fl=(e,t)=>{for(const n of Cl[e].puzzle.tiles){const e=ae.decodePiece(n);if(e.owner===t)return e.idx}return-1},jl=e=>Cl[e].puzzle.info.tileDrawSize,Wl=e=>Cl[e].puzzle.info.tileSize,Hl=e=>Cl[e].puzzle.data.maxGroup,Kl=e=>Cl[e].puzzle.data.maxZ;function ql(e,t){const n=Cl[e].puzzle.info,l=ae.coordByPieceIdx(n,t);return[l.y>0?t-n.tilesX:-1,l.x0?t-1:-1]}const Yl=(e,t,n)=>{for(const l of t)_l(e,l,{z:n})},Ql=(e,t,n)=>{const l=Rl(e,t);_l(e,t,{pos:fl.pointAdd(l,n)})},Zl=(e,t,n)=>{const l=jl(e),o=$l(e),a=n;for(const s of t){const t=Vl(e,s);t.pos.x+n.xo.x+o.w&&(a.x=Math.min(o.x+o.w-t.pos.x+l,a.x)),t.pos.y+n.yo.y+o.h&&(a.y=Math.min(o.y+o.h-t.pos.y+l,a.y))}for(const s of t)Ql(e,s,a)},Xl=(e,t)=>Vl(e,t).owner,Jl=(e,t)=>{for(const n of t)_l(e,n,{owner:-1,z:1})},eo=(e,t,n)=>{for(const l of t)_l(e,l,{owner:n})};function to(e,t){const n=Cl[e].puzzle.tiles,l=ae.decodePiece(n[t]),o=[];if(l.group)for(const a of n){const e=ae.decodePiece(a);e.group===l.group&&o.push(e.idx)}else o.push(l.idx);return o}const no=(e,t)=>{const n=kl(e,t);return n?n.points:0},lo=e=>Cl[e].puzzle.info.table.width,oo=e=>Cl[e].puzzle.info.table.height;var ao={setGame:function(e,t){Cl[e]=t},exists:function(e){return!!Cl[e]||!1},playerExists:Al,getActivePlayers:function(e,t){const n=t-30*G;return Sl(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*G;return Sl(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Al(e,t)?Dl(e,t,{ts:n}):Pl(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:Ml,getPieceCount:zl,getImageUrl:function(e){var t;const n=(null==(t=Cl[e].puzzle.info.image)?void 0:t.url)||Cl[e].puzzle.info.imageUrl;if(!n)throw new Error("[2021-07-11] no image url set");return n},get:function(e){return Cl[e]||null},getAllGames:function(){return Object.values(Cl).sort(((e,t)=>{const n=El(e.id);return n===El(t.id)?n?t.puzzle.data.finished-e.puzzle.data.finished:t.puzzle.data.started-e.puzzle.data.started:n?1:-1}))},getPlayerBgColor:(e,t)=>{const n=kl(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=kl(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=kl(e,t);return n?n.name:null},getPlayerIndexById:xl,getPlayerIdByIndex:function(e,t){return Cl[e].players.length>t?ae.decodePlayer(Cl[e].players[t]).id:null},changePlayer:Dl,setPlayer:Pl,setPiece:function(e,t,n){Cl[e].puzzle.tiles[t]=ae.encodePiece(n)},setPuzzleData:function(e,t){Cl[e].puzzle.data=t},getTableWidth:lo,getTableHeight:oo,getPuzzle:e=>Cl[e].puzzle,getRng:e=>Cl[e].rng.obj,getPuzzleWidth:e=>Cl[e].puzzle.info.width,getPuzzleHeight:e=>Cl[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Cl[e].puzzle.tiles.map(ae.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Fl(e,t);return n<0?null:Cl[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Cl[e].puzzle.info.tileDrawOffset,getPieceDrawSize:jl,getFinalPiecePos:Ul,getStartTs:e=>Cl[e].puzzle.data.started,getFinishTs:e=>Cl[e].puzzle.data.finished,handleInput:function(e,t,n,l,o){const a=Cl[e].puzzle,s=function(e,t){return t in Cl[e].evtInfos?Cl[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),i=[],r=()=>{i.push([wn,a.data])},d=t=>{i.push([bn,ae.encodePiece(Vl(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=kl(e,t);n&&i.push([Cn,ae.encodePlayer(n)])},p=n[0];if(p===sn){const o=n[1];Dl(e,t,{bgcolor:o,ts:l}),u()}else if(p===rn){const o=n[1];Dl(e,t,{color:o,ts:l}),u()}else if(p===dn){const o=`${n[1]}`.substr(0,16);Dl(e,t,{name:o,ts:l}),u()}else if(p===en){const o=n[1],a=n[2],s=kl(e,t);if(s){const n=s.x-o,i=s.y-a;Dl(e,t,{ts:l,x:n,y:i}),u()}}else if(p===tn){const o={x:n[1],y:n[2]};Dl(e,t,{d:1,ts:l}),u(),s._last_mouse_down=o;const a=((e,t)=>{const n=Cl[e].puzzle.info,l=Cl[e].puzzle.tiles;let o=-1,a=-1;for(let s=0;so)&&(o=e.z,a=s)}return a})(e,o);if(a>=0){const n=Kl(e)+1;Nl(e,{maxZ:n}),r();const l=to(e,a);Yl(e,l,Kl(e)),eo(e,l,t),c(l)}s._last_mouse=o}else if(p===ln){const o=n[1],a=n[2],i={x:o,y:a};if(null===s._last_mouse_down)Dl(e,t,{x:o,y:a,ts:l}),u();else{const n=Fl(e,t);if(n>=0){Dl(e,t,{x:o,y:a,ts:l}),u();const r=to(e,n);let d=fl.pointInBounds(i,$l(e))&&fl.pointInBounds(s._last_mouse_down,$l(e));for(const t of r){const n=Gl(e,t);if(fl.pointInBounds(i,n)){d=!0;break}}if(d){const t=o-s._last_mouse_down.x,n=a-s._last_mouse_down.y;Zl(e,r,{x:t,y:n}),c(r)}}else Dl(e,t,{ts:l}),u();s._last_mouse_down=i}s._last_mouse=i}else if(p===nn){const i={x:n[1],y:n[2]},p=0;s._last_mouse_down=null;const g=Fl(e,t);if(g>=0){const n=to(e,g);eo(e,n,0),c(n);const s=Rl(e,g),i=Ul(e,g);let h=!1;if(Il(e)===Ge.REAL){for(const t of n)if(Bl(e,t)){h=!0;break}}else h=!0;if(h&&fl.pointDistance(i,s){const o=Cl[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const l=Ol(e,t),o=Ol(e,n);return!(!l||l!==o)})(e,t,n))return!1;const a=Rl(e,t),s=fl.pointAdd(Rl(e,n),{x:l[0]*o.tileSize,y:l[1]*o.tileSize});if(fl.pointDistance(a,s){const l=Cl[e].puzzle.tiles,o=Ol(e,t),a=Ol(e,n);let s;const i=[];o&&i.push(o),a&&i.push(a),o?s=o:a?s=a:(Nl(e,{maxGroup:Hl(e)+1}),r(),s=Hl(e));if(_l(e,t,{group:s}),d(t),_l(e,n,{group:s}),d(n),i.length>0)for(const r of l){const t=ae.decodePiece(r);i.includes(t.group)&&(_l(e,t.idx,{group:s}),d(t.idx))}})(e,t,n),o=to(e,t),((e,t)=>-1===Xl(e,t))(e,n))Jl(e,o);else{const t=((e,t)=>{let n=0;for(const l of t){const t=Ll(e,l);t>n&&(n=t)}return n})(e,o);Yl(e,o,t)}return c(o),!0}return!1};let a=!1;for(const t of to(e,g)){const l=ql(e,t);if(n(e,t,l[0],[0,1])||n(e,t,l[1],[-1,0])||n(e,t,l[2],[0,-1])||n(e,t,l[3],[1,0])){a=!0;break}}if(a&&Tl(e)===Be.ANY){const n=no(e,t)+1;Dl(e,t,{d:p,ts:l,points:n}),u()}else Dl(e,t,{d:p,ts:l}),u();a&&Il(e)===Ge.REAL&&Ml(e)===zl(e)&&(Nl(e,{finished:l}),r()),a&&o&&o(t)}}else Dl(e,t,{d:p,ts:l}),u();s._last_mouse=i}else if(p===on){const o=n[1],a=n[2];Dl(e,t,{x:o,y:a,ts:l}),u(),s._last_mouse={x:o,y:a}}else if(p===an){const o=n[1],a=n[2];Dl(e,t,{x:o,y:a,ts:l}),u(),s._last_mouse={x:o,y:a}}else Dl(e,t,{ts:l}),u();return function(e,t,n){Cl[e].evtInfos[t]=n}(e,t,s),i}};let so=-10,io=20,ro=2,co=15;class uo{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=so+Math.random()*io,this.vy=-1*(ro+Math.random()*co),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;ro=t/2,co=t-ro;const n=1/4*this.canvas.width/(t/2);so=-n,io=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new uo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new uo(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{localStorage.setItem(e,t)},Co=e=>localStorage.getItem(e);var xo=(e,t)=>{bo(e,`${t}`)},ko=(e,t)=>{const n=Co(e);if(null===n)return t;const l=parseInt(n,10);return isNaN(l)?t:l},Po=(e,t)=>{bo(e,t?"1":"0")},Ao=(e,t)=>{const n=Co(e);return null===n?t:"1"===n},So=(e,t)=>{bo(e,t)},zo=(e,t)=>{const n=Co(e);return null===n?t:n};const To={"./grab.png":ol,"./grab_mask.png":al,"./hand.png":sl,"./hand_mask.png":il},Io={"./click.mp3":ll},Eo="replay";let Mo=!0,Do=!0;let No=!0;async function _o(e,t,n,l,o,a){void 0===window.DEBUG&&(window.DEBUG=!1);const s=Io["./click.mp3"].default,i=new Audio(s),r=await dl.loadImageToBitmap(To["./grab.png"].default),d=await dl.loadImageToBitmap(To["./hand.png"].default),c=await dl.loadImageToBitmap(To["./grab_mask.png"].default),u=await dl.loadImageToBitmap(To["./hand_mask.png"].default),p=r.width,g=Math.round(p/2),h=r.height,m=Math.round(h/2),y={},f=async e=>{const t=e.color+" "+e.d;if(!y[t]){const n=e.d?r:d;if(e.color){const l=e.d?c:u;y[t]=await createImageBitmap(dl.colorizedCanvas(n,l,e.color))}else y[t]=n}return y[t]},v=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,No=!0})),t}(o,dl.createCanvas()),w={final:!1,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,skipNonActionPhases:!0,dataOffset:0};Nn.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{const t=w.dataOffset;w.dataOffset+=1e4;const n=await Nn.requestReplayData(e,t);return w.log=w.log.slice(w.logPointer),w.logPointer=0,w.log.push(...n.log),0===n.log.length&&(w.final=!0),n};let C=()=>0;const x=async()=>{if("play"===l){const l=await Nn.connect(n,e,t),o=ae.decodeGame(l);ao.setGame(o.id,o),C=()=>L()}else{if(l!==Eo)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await b(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=ae.decodeGame(t.game);ao.setGame(n.id,n),w.lastRealTs=L(),w.gameStartTs=parseInt(t.log[0][4],10),w.lastGameTs=w.gameStartTs,C=()=>w.lastGameTs}}No=!0};await x();const k=ao.getPieceDrawOffset(e),P=ao.getPieceDrawSize(e),A=ao.getPuzzleWidth(e),S=ao.getPuzzleHeight(e),z=ao.getTableWidth(e),T=ao.getTableHeight(e),I={x:(z-A)/2,y:(T-S)/2},E={w:A,h:S},M={w:P,h:P},D=await bl.loadPuzzleBitmaps(ao.getPuzzle(e)),N=new go(v,ao.getRng(e));N.init();const _=v.getContext("2d");v.classList.add("loaded"),a.setPuzzleCut();const V=function(){let e=0,t=0,n=1;const l=(l,o)=>{e+=l/n,t+=o/n},o=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},a=(e,t)=>{if(n==e)return!1;const o=1-n/e;return l(-t.x*o,-t.y*o),n=e,!0},s=l=>({x:l.x/n-e,y:l.y/n-t}),i=l=>({x:(l.x+e)*n,y:(l.y+t)*n}),r=e=>({w:e.w*n,h:e.h*n}),d=e=>({w:e.w/n,h:e.h/n});return{getCurrentZoom:()=>n,reset:()=>{e=0,t=0,n=1},move:l,canZoom:e=>n!=o(e),zoom:(e,t)=>a(o(e),t),setZoom:a,worldToViewport:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:i,worldDimToViewport:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:r,viewportToWorld:e=>{const{x:t,y:n}=s(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:s,viewportDimToWorld:e=>{const{w:t,h:n}=d(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:d}}(),O=()=>{V.reset(),V.move(-(z-v.width)/2,-(T-v.height)/2);const e=V.worldDimToViewport(E),t=v.width-40,n=v.height-40;if(e.w>t||e.h>n||e.w{const l=n.viewportToWorld({x:e,y:t});return[l.x,l.y]},h=e=>g(e.offsetX,e.offsetY),m=()=>g(e.width/2,e.height/2),y=(e,t)=>{a&&("ShiftLeft"===t.code||"ShiftRight"===t.code?p=e:"ArrowUp"===t.code||"KeyW"===t.code?r=e:"ArrowDown"===t.code||"KeyS"===t.code?d=e:"ArrowLeft"===t.code||"KeyA"===t.code?s=e:"ArrowRight"===t.code||"KeyD"===t.code?i=e:"KeyQ"===t.code?u=e:"KeyE"===t.code&&(c=e))};let f=null;e.addEventListener("mousedown",(e=>{f=h(e),0===e.button&&v([tn,...f])})),e.addEventListener("mouseup",(e=>{f=h(e),0===e.button&&v([nn,...f])})),e.addEventListener("mousemove",(e=>{f=h(e),v([ln,...f])})),e.addEventListener("wheel",(e=>{if(f=h(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?on:an;v([t,...f])}})),t.addEventListener("keydown",(e=>y(!0,e))),t.addEventListener("keyup",(e=>y(!1,e))),t.addEventListener("keypress",(e=>{a&&("Space"===e.code&&v([cn]),l===Eo&&("KeyI"===e.code&&v([gn]),"KeyO"===e.code&&v([hn]),"KeyP"===e.code&&v([pn])),"KeyF"===e.code&&v([fn]),"KeyG"===e.code&&v([vn]),"KeyM"===e.code&&v([un]),"KeyN"===e.code&&v([mn]),"KeyC"===e.code&&v([yn]))}));const v=e=>{o.push(e)};return{addEvent:v,consumeAll:()=>{if(0===o.length)return[];const e=o.slice();return o=[],e},createKeyEvents:()=>{const e=(s?1:0)-(i?1:0),t=(r?1:0)-(d?1:0);if(0!==e||0!==t){const l=(p?24:12)*Math.sqrt(n.getCurrentZoom()),o=n.viewportDimToWorld({w:e*l,h:t*l});v([en,o.w,o.h]),f&&(f[0]-=o.w,f[1]-=o.h)}if(c&&u);else if(c){if(n.canZoom("in")){const e=f||m();v([on,...e])}}else if(u&&n.canZoom("out")){const e=f||m();v([an,...e])}},setHotkeys:e=>{a=e}}}(v,window,V,l),U=ao.getImageUrl(e),R=()=>{const t=ao.getStartTs(e),n=ao.getFinishTs(e),l=C();a.setFinished(!!n),a.setDuration((n||l)-t)};R(),a.setPiecesDone(ao.getFinishedPiecesCount(e)),a.setPiecesTotal(ao.getPieceCount(e));const G=C();a.setActivePlayers(ao.getActivePlayers(e,G)),a.setIdlePlayers(ao.getIdlePlayers(e,G));const F=!!ao.getFinishTs(e);let j=F;const W=()=>j&&!F,H=()=>ko(ho,100),K=()=>Ao(mo,!1),q=()=>Ao(wo,!0),Y=()=>{const e=H();i.volume=e/100,i.play()},Q=()=>l===Eo?zo(yo,"#222222"):ao.getPlayerBgColor(e,t)||zo(yo,"#222222"),Z=()=>l===Eo?zo(fo,"#ffffff"):ao.getPlayerColor(e,t)||zo(fo,"#ffffff");let X="",J="",ee=!1;const te=e=>{ee=e;const[t,n]=e?[X,"grab"]:[J,"default"];v.style.cursor=`url('${t}') ${g} ${m}, ${n}`},ne=e=>{X=dl.colorizedCanvas(r,c,e).toDataURL(),J=dl.colorizedCanvas(d,u,e).toDataURL(),te(ee)};ne(Z());const le=()=>{a.setReplaySpeed&&a.setReplaySpeed(w.speeds[w.speedIdx]),a.setReplayPaused&&a.setReplayPaused(w.paused)},oe=()=>{w.speedIdx+1{w.speedIdx>=1&&(w.speedIdx--,le())},ie=()=>{w.paused=!w.paused,le()},re=[];let de;let ce;if("play"===l?re.push(setInterval((()=>{R()}),1e3)):l===Eo&&le(),"play"===l)Nn.onServerChange((n=>{n[0],n[1],n[2];const l=n[3];for(const[o,a]of l)switch(o){case Cn:{const n=ae.decodePlayer(a);n.id!==t&&(ao.setPlayer(e,n.id,n),No=!0)}break;case bn:{const t=ae.decodePiece(a);ao.setPiece(e,t.idx,t),No=!0}break;case wn:ao.setPuzzleData(e,a),No=!0}j=!!ao.getFinishTs(e)}));else if(l===Eo){const t=(t,n)=>{const l=t;if(l[0]===Zt){const t=l[1];return ao.addPlayer(e,t,n),!0}if(l[0]===Xt){const t=ao.getPlayerIdByIndex(e,l[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";return ao.addPlayer(e,t,n),!0}if(l[0]===Jt){const t=ao.getPlayerIdByIndex(e,l[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const o=l[2];return ao.handleInput(e,t,o,n),!0}return!1};let n=w.lastGameTs;const l=async()=>{w.logPointer+1>=w.log.length&&await b(e);const o=L();if(w.paused)return w.lastRealTs=o,void(de=setTimeout(l,50));const a=(o-w.lastRealTs)*w.speeds[w.speedIdx];let s=w.lastGameTs+a;for(;;){if(w.paused)break;const e=w.logPointer+1;if(e>=w.log.length)break;const l=w.log[w.logPointer],o=n+l[l.length-1],a=w.log[e],i=a[a.length-1],r=o+i;if(r>s){s+500*${let t=!1;const n=e.fps||60,l=e.slow||1,o=e.update,a=e.render,s=window.requestAnimationFrame,i=1/n,r=l*i;let d,c=0,u=window.performance.now();const p=()=>{for(d=window.performance.now(),c+=Math.min(1,(d-u)/1e3);c>r;)c-=r,o(i);a(c/l),u=d,t||s(p)};return s(p),{stop:()=>{t=!0}}})({update:()=>{B.createKeyEvents();for(const n of B.consumeAll())if("play"===l){const l=n[0];if(l===en){const e=n[1],t=n[2],l=V.worldDimToViewport({w:e,h:t});No=!0,V.move(l.w,l.h)}else if(l===ln){if(ue&&!ao.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=V.worldToViewport(e),l=Math.round(t.x-ue.x),o=Math.round(t.y-ue.y);No=!0,V.move(l,o),ue=t}}else if(l===rn)ne(n[1]);else if(l===tn){const e={x:n[1],y:n[2]};ue=V.worldToViewport(e),te(!0)}else if(l===nn)ue=null,te(!1);else if(l===on){const e={x:n[1],y:n[2]};No=!0,V.zoom("in",V.worldToViewport(e))}else if(l===an){const e={x:n[1],y:n[2]};No=!0,V.zoom("out",V.worldToViewport(e))}else l===cn?a.togglePreview():l===un?a.toggleSoundsEnabled():l===mn?a.togglePlayerNames():l===yn?O():l===fn?(Mo=!Mo,No=!0):l===vn&&(Do=!Do,No=!0);const o=C();ao.handleInput(e,t,n,o,(e=>{K()&&Y()})).length>0&&(No=!0),Nn.sendClientEvent(n)}else if(l===Eo){const e=n[0];if(e===pn)ie();else if(e===hn)se();else if(e===gn)oe();else if(e===en){const e=n[1],t=n[2];No=!0,V.move(e,t)}else if(e===ln){if(ue){const e={x:n[1],y:n[2]},t=V.worldToViewport(e),l=Math.round(t.x-ue.x),o=Math.round(t.y-ue.y);No=!0,V.move(l,o),ue=t}}else if(e===rn)ne(n[1]);else if(e===tn){const e={x:n[1],y:n[2]};ue=V.worldToViewport(e),te(!0)}else if(e===nn)ue=null,te(!1);else if(e===on){const e={x:n[1],y:n[2]};No=!0,V.zoom("in",V.worldToViewport(e))}else if(e===an){const e={x:n[1],y:n[2]};No=!0,V.zoom("out",V.worldToViewport(e))}else e===cn?a.togglePreview():e===un?a.toggleSoundsEnabled():e===mn?a.togglePlayerNames():e===yn?O():e===fn?(Mo=!Mo,No=!0):e===vn&&(Do=!Do,No=!0)}j=!!ao.getFinishTs(e),W()&&(N.update(),No=!0)},render:async()=>{if(!No)return;const n=C();let o,s,i;window.DEBUG&&gl(0),_.fillStyle=Q(),_.fillRect(0,0,v.width,v.height),window.DEBUG&&hl("clear done"),o=V.worldToViewportRaw(I),s=V.worldDimToViewportRaw(E),_.fillStyle="rgba(255, 255, 255, .3)",_.fillRect(o.x,o.y,s.w,s.h),window.DEBUG&&hl("board done");const r=ao.getPiecesSortedByZIndex(e);window.DEBUG&&hl("get tiles done"),s=V.worldDimToViewportRaw(M);for(const e of r)(-1===e.owner?Mo:Do)&&(i=D[e.idx],o=V.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),_.drawImage(i,0,0,i.width,i.height,o.x,o.y,s.w,s.h));window.DEBUG&&hl("tiles done");const d=[];for(const a of ao.getActivePlayers(e,n))c=a,(l===Eo||c.id!==t)&&(i=await f(a),o=V.worldToViewport(a),_.drawImage(i,o.x-g,o.y-m),q()&&d.push([`${a.name} (${a.points})`,o.x,o.y+h]));var c;_.fillStyle="white",_.textAlign="center";for(const[e,t,l]of d)_.fillText(e,t,l);window.DEBUG&&hl("players done"),a.setActivePlayers(ao.getActivePlayers(e,n)),a.setIdlePlayers(ao.getIdlePlayers(e,n)),a.setPiecesDone(ao.getFinishedPiecesCount(e)),window.DEBUG&&hl("HUD done"),W()&&N.render(),No=!1}}),{setHotkeys:e=>{B.setHotkeys(e)},onBgChange:e=>{So(yo,e),B.addEvent([sn,e])},onColorChange:e=>{So(fo,e),B.addEvent([rn,e])},onNameChange:e=>{So(vo,e),B.addEvent([dn,e])},onSoundsEnabledChange:e=>{Po(mo,e)},onSoundsVolumeChange:e=>{xo(ho,e),Y()},onShowPlayerNamesChange:e=>{Po(wo,e)},replayOnSpeedUp:oe,replayOnSpeedDown:se,replayOnPauseToggle:ie,previewImageUrl:U,player:{background:Q(),color:Z(),name:l===Eo?zo(vo,"anon"):ao.getPlayerName(e,t)||zo(vo,"anon"),soundsEnabled:K(),soundsVolume:H(),showPlayerNames:q()},game:ao.get(e),disconnect:Nn.disconnect,connect:x,unload:()=>{re.forEach((e=>{clearInterval(e)})),de&&clearTimeout(de),ce&&ce.stop()}}}var Vo=e({name:"game",components:{PuzzleStatus:zt,Scores:xt,SettingsOverlay:It,PreviewOverlay:Rt,InfoOverlay:Gt,ConnectionOverlay:_n,HelpOverlay:$n},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,cuttingPuzzle:!0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},game:null,previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}}}),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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await _o(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setPuzzleCut:()=>{this.cuttingPuzzle=!1},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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 Oo={id:"game"},Bo={key:1,class:"overlay"},Uo=n("div",{class:"overlay-content"},[n("div",null,"⏳ Cutting puzzle, please wait... ⏳")],-1),Ro={class:"menu"},$o={class:"tabs"},Go=i("🧩 Puzzles");Vo.render=function(e,i,r,d,c,u){const g=a("settings-overlay"),h=a("preview-overlay"),m=a("info-overlay"),y=a("help-overlay"),f=a("connection-overlay"),v=a("puzzle-status"),w=a("router-link"),b=a("scores");return s(),t("div",Oo,[p(n(g,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),e.g.game?p((s(),t(m,{key:0,onBgclick:i[4]||(i[4]=t=>e.toggle("info",!0)),game:e.g.game},null,8,["game"])),[[x,"info"===e.overlay]]):o("",!0),p(n(y,{onBgclick:i[5]||(i[5]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),e.cuttingPuzzle?(s(),t("div",Bo,[Uo])):o("",!0),n(f,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(v,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",Ro,[n("div",$o,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[Go])),_:1}),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("info",!0))},"ℹ️ Info"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("help",!0))},"⌨️ Hotkeys")])]),n(b,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var Lo=e({name:"replay",components:{PuzzleStatus:zt,Scores:xt,SettingsOverlay:It,PreviewOverlay:Rt,InfoOverlay:Gt,HelpOverlay:$n},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,cuttingPuzzle:!0,g:{player:{background:"",color:"",name:"",soundsEnabled:!1,soundsVolume:100,showPlayerNames:!0},game:null,previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},onSoundsEnabledChange:e=>{},onSoundsVolumeChange:e=>{},onShowPlayerNamesChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},connect:()=>{},disconnect:()=>{},unload:()=>{}},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.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.$watch((()=>this.g.player.soundsVolume),(e=>{this.g.onSoundsVolumeChange(e)})),this.$watch((()=>this.g.player.showPlayerNames),(e=>{this.g.onShowPlayerNamesChange(e)})),this.g=await _o(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,Eo,this.$el,{setPuzzleCut:()=>{this.cuttingPuzzle=!1},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},toggleSoundsEnabled:()=>{this.g.player.soundsEnabled=!this.g.player.soundsEnabled},togglePlayerNames:()=>{this.g.player.showPlayerNames=!this.g.player.showPlayerNames}}))},unmounted(){this.g.unload(),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 Fo={id:"replay"},jo={key:1,class:"overlay"},Wo=n("div",{class:"overlay-content"},[n("div",null,"⏳ Cutting puzzle, please wait... ⏳")],-1),Ho={class:"menu"},Ko={class:"tabs"},qo=i("🧩 Puzzles");Lo.render=function(e,i,d,c,u,g){const h=a("settings-overlay"),m=a("preview-overlay"),y=a("info-overlay"),f=a("help-overlay"),v=a("puzzle-status"),w=a("router-link"),b=a("scores");return s(),t("div",Fo,[p(n(h,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[x,"settings"===e.overlay]]),p(n(m,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[x,"preview"===e.overlay]]),e.g.game?p((s(),t(y,{key:0,onBgclick:i[4]||(i[4]=t=>e.toggle("info",!0)),game:e.g.game},null,8,["game"])),[[x,"info"===e.overlay]]):o("",!0),p(n(f,{onBgclick:i[5]||(i[5]=t=>e.toggle("help",!0))},null,512),[[x,"help"===e.overlay]]),e.cuttingPuzzle?(s(),t("div",jo,[Wo])):o("",!0),n(v,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:l((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[8]||(i[8]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",Ho,[n("div",Ko,[n(w,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[qo])),_:1}),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[11]||(i[11]=t=>e.toggle("info",!0))},"ℹ️ Info"),n("div",{class:"opener",onClick:i[12]||(i[12]=t=>e.toggle("help",!0))},"⌨️ Hotkeys")])]),n(b,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=function(){let e=zo("ID","");return e||(e=ae.uniqId(),So("ID",e)),e}(),t=function(){let e=zo("SECRET","");return e||(e=ae.uniqId(),So("SECRET",e)),e}();O(e),B(t);const n=await _("/api/me",{}),l=await n.json(),o=await _("/api/conf",{}),a=await o.json(),s=k({history:P(),routes:[{name:"index",path:"/",component:Z},{name:"new-game",path:"/new-game",component:pt},{name:"game",path:"/g/:id",component:Vo},{name:"replay",path:"/replay/:id",component:Lo}]});s.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const i=A(S);i.config.globalProperties.$me=l,i.config.globalProperties.$config=a,i.config.globalProperties.$clientId=e,i.use(s),i.mount("#app")})(); diff --git a/build/public/index.html b/build/public/index.html index 0974d69..773251f 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,7 +4,7 @@ 🧩 jigsaw.hyottoko.club - + diff --git a/build/server/main.js b/build/server/main.js index 5e3f14e..9ef521f 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -155,6 +155,7 @@ function encodeGame(data) { data.scoreMode, data.shapeMode, data.snapMode, + data.creatorUserId, ]; } function decodeGame(data) { @@ -170,6 +171,7 @@ function decodeGame(data) { scoreMode: data[6], shapeMode: data[7], snapMode: data[8], + creatorUserId: data[9], }; } function coordByPieceIdx(info, pieceIdx) { @@ -1358,6 +1360,7 @@ const get = (gameId, offset = 0) => { log[0][5] = DefaultScoreMode(log[0][5]); log[0][6] = DefaultShapeMode(log[0][6]); log[0][7] = DefaultSnapMode(log[0][7]); + log[0][8] = log[0][8] || null; } return log; }; @@ -1753,7 +1756,63 @@ function setDirty(gameId) { function setClean(gameId) { delete dirtyGames[gameId]; } -function loadGames() { +function loadGamesFromDb(db) { + const gameRows = db.getMany('games'); + for (const gameRow of gameRows) { + loadGameFromDb(db, gameRow.id); + } +} +function loadGameFromDb(db, gameId) { + const gameRow = db.get('games', { id: gameId }); + let game; + try { + game = JSON.parse(gameRow.data); + } + catch { + log$3.log(`[ERR] unable to load game from db ${gameId}`); + } + if (typeof game.puzzle.data.started === 'undefined') { + game.puzzle.data.started = gameRow.created; + } + if (typeof game.puzzle.data.finished === 'undefined') { + game.puzzle.data.finished = gameRow.finished; + } + if (!Array.isArray(game.players)) { + game.players = Object.values(game.players); + } + const gameObject = storeDataToGame(game, game.creator_user_id); + GameCommon.setGame(gameObject.id, gameObject); +} +function persistGamesToDb(db) { + for (const gameId of Object.keys(dirtyGames)) { + persistGameToDb(db, gameId); + } +} +function persistGameToDb(db, gameId) { + const game = GameCommon.get(gameId); + if (!game) { + log$3.error(`[ERROR] unable to persist non existing game ${gameId}`); + return; + } + if (game.id in dirtyGames) { + setClean(game.id); + } + db.upsert('games', { + id: game.id, + creator_user_id: game.creatorUserId, + image_id: game.puzzle.info.image?.id, + created: game.puzzle.data.started, + finished: game.puzzle.data.finished, + data: gameToStoreData(game) + }, { + id: game.id, + }); + log$3.info(`[INFO] persisted game ${game.id}`); +} +/** + * @deprecated + */ +function loadGamesFromDisk() { const files = fs.readdirSync(DATA_DIR); for (const f of files) { const m = f.match(/^([a-z0-9]+)\.json$/); @@ -1761,10 +1820,13 @@ function loadGames() { continue; } const gameId = m[1]; - loadGame(gameId); + loadGameFromDisk(gameId); } } -function loadGame(gameId) { +/** + * @deprecated + */ +function loadGameFromDisk(gameId) { const file = `${DATA_DIR}/${gameId}.json`; const contents = fs.readFileSync(file, 'utf-8'); let game; @@ -1786,27 +1848,21 @@ function loadGame(gameId) { if (!Array.isArray(game.players)) { game.players = Object.values(game.players); } - const gameObject = { - id: game.id, - rng: { - type: game.rng ? game.rng.type : '_fake_', - obj: game.rng ? Rng.unserialize(game.rng.obj) : new Rng(0), - }, - puzzle: game.puzzle, - players: game.players, - evtInfos: {}, - scoreMode: DefaultScoreMode(game.scoreMode), - shapeMode: DefaultShapeMode(game.shapeMode), - snapMode: DefaultSnapMode(game.snapMode), - }; + const gameObject = storeDataToGame(game, null); GameCommon.setGame(gameObject.id, gameObject); } -function persistGames() { +/** + * @deprecated + */ +function persistGamesToDisk() { for (const gameId of Object.keys(dirtyGames)) { - persistGame(gameId); + persistGameToDisk(gameId); } } -function persistGame(gameId) { +/** + * @deprecated + */ +function persistGameToDisk(gameId) { const game = GameCommon.get(gameId); if (!game) { log$3.error(`[ERROR] unable to persist non existing game ${gameId}`); @@ -1815,7 +1871,27 @@ function persistGame(gameId) { if (game.id in dirtyGames) { setClean(game.id); } - fs.writeFileSync(`${DATA_DIR}/${game.id}.json`, JSON.stringify({ + fs.writeFileSync(`${DATA_DIR}/${game.id}.json`, gameToStoreData(game)); + log$3.info(`[INFO] persisted game ${game.id}`); +} +function storeDataToGame(storeData, creatorUserId) { + return { + id: storeData.id, + creatorUserId, + rng: { + type: storeData.rng ? storeData.rng.type : '_fake_', + obj: storeData.rng ? Rng.unserialize(storeData.rng.obj) : new Rng(0), + }, + puzzle: storeData.puzzle, + players: storeData.players, + evtInfos: {}, + scoreMode: DefaultScoreMode(storeData.scoreMode), + shapeMode: DefaultShapeMode(storeData.shapeMode), + snapMode: DefaultSnapMode(storeData.snapMode), + }; +} +function gameToStoreData(game) { + return JSON.stringify({ id: game.id, rng: { type: game.rng.type, @@ -1826,22 +1902,27 @@ function persistGame(gameId) { scoreMode: game.scoreMode, shapeMode: game.shapeMode, snapMode: game.snapMode, - })); - log$3.info(`[INFO] persisted game ${game.id}`); + }); } var GameStorage = { - loadGames, - loadGame, - persistGames, - persistGame, + // disk functions are deprecated + loadGamesFromDisk, + loadGameFromDisk, + persistGamesToDisk, + persistGameToDisk, + loadGamesFromDb, + loadGameFromDb, + persistGamesToDb, + persistGameToDb, setDirty, }; -async function createGameObject(gameId, targetTiles, image, ts, scoreMode, shapeMode, snapMode) { +async function createGameObject(gameId, targetTiles, image, ts, scoreMode, shapeMode, snapMode, creatorUserId) { const seed = Util.hash(gameId + ' ' + ts); const rng = new Rng(seed); return { id: gameId, + creatorUserId, rng: { type: 'Rng', obj: rng }, puzzle: await createPuzzle(rng, targetTiles, image, ts, shapeMode), players: [], @@ -1851,10 +1932,10 @@ async function createGameObject(gameId, targetTiles, image, ts, scoreMode, shape snapMode, }; } -async function createGame(gameId, targetTiles, image, ts, scoreMode, shapeMode, snapMode) { - const gameObject = await createGameObject(gameId, targetTiles, image, ts, scoreMode, shapeMode, snapMode); +async function createGame(gameId, targetTiles, image, ts, scoreMode, shapeMode, snapMode, creatorUserId) { + const gameObject = await createGameObject(gameId, targetTiles, image, ts, scoreMode, shapeMode, snapMode, creatorUserId); GameLog.create(gameId, ts); - GameLog.log(gameId, Protocol.LOG_HEADER, 1, targetTiles, image, ts, scoreMode, shapeMode, snapMode); + GameLog.log(gameId, Protocol.LOG_HEADER, 1, targetTiles, image, ts, scoreMode, shapeMode, snapMode, gameObject.creatorUserId); GameCommon.setGame(gameObject.id, gameObject); GameStorage.setDirty(gameId); } @@ -2101,10 +2182,7 @@ const storage = multer.diskStorage({ }); const upload = multer({ storage }).single('file'); app.get('/api/me', (req, res) => { - let user = db.get('users', { - 'client_id': req.headers['client-id'], - 'client_secret': req.headers['client-secret'], - }); + let user = getUser(db, req); res.send({ id: user ? user.id : null, created: user ? user.created : null, @@ -2137,7 +2215,7 @@ app.get('/api/replay-data', async (req, res) => { if (offset === 0) { // also need the game game = await Game.createGameObject(gameId, log[0][2], log[0][3], // must be ImageInfo - log[0][4], log[0][5], log[0][6], log[0][7]); + log[0][4], log[0][5], log[0][6], log[0][7], log[0][8]); } res.send({ log, game: game ? Util.encodeGame(game) : null }); }); @@ -2168,6 +2246,28 @@ app.get('/api/index-data', (req, res) => { gamesFinished: games.filter(g => !!g.finished), }); }); +const getOrCreateUser = (db, req) => { + let user = getUser(db, req); + if (!user) { + db.insert('users', { + 'client_id': req.headers['client-id'], + 'client_secret': req.headers['client-secret'], + 'created': Time.timestamp(), + }); + user = getUser(db, req); + } + return user; +}; +const getUser = (db, req) => { + let user = db.get('users', { + 'client_id': req.headers['client-id'], + 'client_secret': req.headers['client-secret'], + }); + if (user) { + user.id = parseInt(user.id, 10); + } + return user; +}; const setImageTags = (db, imageId, tags) => { tags.forEach((tag) => { const slug = Util.slug(tag); @@ -2181,21 +2281,14 @@ const setImageTags = (db, imageId, tags) => { }); }; app.post('/api/save-image', express.json(), (req, res) => { - let user = db.get('users', { - 'client_id': req.headers['client-id'], - 'client_secret': req.headers['client-secret'], - }); - let userId = null; - if (user) { - userId = parseInt(user.id, 10); - } - else { + let user = getUser(db, req); + if (!user || !user.id) { res.status(403).send({ ok: false, error: 'forbidden' }); return; } const data = req.body; let image = db.get('images', { id: data.id }); - if (parseInt(image.uploader_user_id, 10) !== userId) { + if (parseInt(image.uploader_user_id, 10) !== user.id) { res.status(403).send({ ok: false, error: 'forbidden' }); return; } @@ -2223,24 +2316,10 @@ app.post('/api/upload', (req, res) => { log.log(err); res.status(400).send("Something went wrong!"); } - let user = db.get('users', { - 'client_id': req.headers['client-id'], - 'client_secret': req.headers['client-secret'], - }); - let userId = null; - if (user) { - userId = user.id; - } - else { - userId = db.insert('users', { - 'client_id': req.headers['client-id'], - 'client_secret': req.headers['client-secret'], - 'created': Time.timestamp(), - }); - } + const user = getOrCreateUser(db, req); const dim = await Images.getDimensions(`${UPLOAD_DIR}/${req.file.filename}`); const imageId = db.insert('images', { - uploader_user_id: userId, + uploader_user_id: user.id, filename: req.file.filename, filename_original: req.file.originalname, title: req.body.title || '', @@ -2256,12 +2335,17 @@ app.post('/api/upload', (req, res) => { }); }); app.post('/api/newgame', express.json(), async (req, res) => { + let user = getOrCreateUser(db, req); + if (!user || !user.id) { + res.status(403).send({ ok: false, error: 'forbidden' }); + return; + } const gameSettings = req.body; log.log(gameSettings); const gameId = Util.uniqId(); if (!GameCommon.exists(gameId)) { const ts = Time.timestamp(); - await Game.createGame(gameId, gameSettings.tiles, gameSettings.image, ts, gameSettings.scoreMode, gameSettings.shapeMode, gameSettings.snapMode); + await Game.createGame(gameId, gameSettings.tiles, gameSettings.image, ts, gameSettings.scoreMode, gameSettings.shapeMode, gameSettings.snapMode, user.id); } res.send({ id: gameId }); }); @@ -2343,7 +2427,7 @@ wss.on('message', async ({ socket, data }) => { log.error(e); } }); -GameStorage.loadGames(); +GameStorage.loadGamesFromDb(db); const server = app.listen(port, hostname, () => log.log(`server running on http://${hostname}:${port}`)); wss.listen(); const memoryUsageHuman = () => { @@ -2357,7 +2441,7 @@ memoryUsageHuman(); // persist games in fixed interval const persistInterval = setInterval(() => { log.log('Persisting games...'); - GameStorage.persistGames(); + GameStorage.persistGamesToDb(db); memoryUsageHuman(); }, config.persistence.interval); const gracefulShutdown = (signal) => { @@ -2365,7 +2449,7 @@ const gracefulShutdown = (signal) => { log.log('clearing persist interval...'); clearInterval(persistInterval); log.log('persisting games...'); - GameStorage.persistGames(); + GameStorage.persistGamesToDb(db); log.log('shutting down webserver...'); server.close(); log.log('shutting down websocketserver...'); diff --git a/scripts/fix_games_image_info.ts b/scripts/fix_games_image_info.ts index 559b512..49ac4ed 100644 --- a/scripts/fix_games_image_info.ts +++ b/scripts/fix_games_image_info.ts @@ -47,7 +47,7 @@ function fixOne(gameId: string) { log.log(g.puzzle.info.image.title, imageRow.id) - GameStorage.persistGame(gameId) + GameStorage.persistGameToDb(db, gameId) } else if (g.puzzle.info.image?.id) { const imageId = g.puzzle.info.image.id @@ -55,7 +55,7 @@ function fixOne(gameId: string) { log.log(g.puzzle.info.image.title, imageId) - GameStorage.persistGame(gameId) + GameStorage.persistGameToDb(db, gameId) } // fix log @@ -81,7 +81,7 @@ function fixOne(gameId: string) { } function fix() { - GameStorage.loadGames() + GameStorage.loadGamesFromDisk() GameCommon.getAllGames().forEach((game: Game) => { fixOne(game.id) }) diff --git a/scripts/fix_tiles.ts b/scripts/fix_tiles.ts index 5eb7ee9..a45b19b 100644 --- a/scripts/fix_tiles.ts +++ b/scripts/fix_tiles.ts @@ -1,11 +1,16 @@ import GameCommon from '../src/common/GameCommon' import { logger } from '../src/common/Util' +import Db from '../src/server/Db' +import { DB_FILE, DB_PATCHES_DIR } from '../src/server/Dirs' import GameStorage from '../src/server/GameStorage' const log = logger('fix_tiles.js') +const db = new Db(DB_FILE, DB_PATCHES_DIR) +db.patch(true) + function fix_tiles(gameId) { - GameStorage.loadGame(gameId) + GameStorage.loadGameFromDb(db, gameId) let changed = false const tiles = GameCommon.getPiecesSortedByZIndex(gameId) for (let tile of tiles) { @@ -27,7 +32,7 @@ function fix_tiles(gameId) { } } if (changed) { - GameStorage.persistGame(gameId) + GameStorage.persistGameToDb(db, gameId) } } diff --git a/scripts/import_games.ts b/scripts/import_games.ts new file mode 100644 index 0000000..f2e5cfa --- /dev/null +++ b/scripts/import_games.ts @@ -0,0 +1,27 @@ +import GameCommon from '../src/common/GameCommon' +import { Game } from '../src/common/Types' +import { logger } from '../src/common/Util' +import { DB_FILE, DB_PATCHES_DIR } from '../src/server/Dirs' +import Db from '../src/server/Db' +import GameStorage from '../src/server/GameStorage' + +const log = logger('import_games.ts') + +console.log(DB_FILE) + +const db = new Db(DB_FILE, DB_PATCHES_DIR) +db.patch(true) + +function run() { + GameStorage.loadGamesFromDisk() + GameCommon.getAllGames().forEach((game: Game) => { + if (!game.puzzle.info.image?.id) { + log.error(game.id + " has no image") + log.error(game.puzzle.info.image) + return + } + GameStorage.persistGameToDb(db, game.id) + }) +} + +run() diff --git a/src/common/Types.ts b/src/common/Types.ts index acf0d7b..ccbb318 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -51,6 +51,7 @@ export type EncodedGame = FixedLengthArray<[ ScoreMode, ShapeMode, SnapMode, + number|null, ]> export interface ReplayData { @@ -72,6 +73,7 @@ interface GameRng { export interface Game { id: string + creatorUserId: number|null players: Array puzzle: Puzzle evtInfos: Record diff --git a/src/common/Util.ts b/src/common/Util.ts index dbf94b6..4de6495 100644 --- a/src/common/Util.ts +++ b/src/common/Util.ts @@ -133,6 +133,7 @@ function encodeGame(data: Game): EncodedGame { data.scoreMode, data.shapeMode, data.snapMode, + data.creatorUserId, ] } @@ -149,6 +150,7 @@ function decodeGame(data: EncodedGame): Game { scoreMode: data[6], shapeMode: data[7], snapMode: data[8], + creatorUserId: data[9], } } diff --git a/src/dbpatches/04_games.sqlite b/src/dbpatches/04_games.sqlite new file mode 100644 index 0000000..edde806 --- /dev/null +++ b/src/dbpatches/04_games.sqlite @@ -0,0 +1,11 @@ +CREATE TABLE games ( + id TEXT PRIMARY KEY, + + creator_user_id INTEGER, + image_id INTEGER NOT NULL, + + created TIMESTAMP NOT NULL, + finished TIMESTAMP NOT NULL, + + data TEXT NOT NULL +); diff --git a/src/server/Game.ts b/src/server/Game.ts index 0aef26f..4b1aba2 100644 --- a/src/server/Game.ts +++ b/src/server/Game.ts @@ -16,12 +16,14 @@ async function createGameObject( ts: Timestamp, scoreMode: ScoreMode, shapeMode: ShapeMode, - snapMode: SnapMode + snapMode: SnapMode, + creatorUserId: number|null ): Promise { const seed = Util.hash(gameId + ' ' + ts) const rng = new Rng(seed) return { id: gameId, + creatorUserId, rng: { type: 'Rng', obj: rng }, puzzle: await createPuzzle(rng, targetTiles, image, ts, shapeMode), players: [], @@ -39,7 +41,8 @@ async function createGame( ts: Timestamp, scoreMode: ScoreMode, shapeMode: ShapeMode, - snapMode: SnapMode + snapMode: SnapMode, + creatorUserId: number ): Promise { const gameObject = await createGameObject( gameId, @@ -48,7 +51,8 @@ async function createGame( ts, scoreMode, shapeMode, - snapMode + snapMode, + creatorUserId ) GameLog.create(gameId, ts) @@ -61,7 +65,8 @@ async function createGame( ts, scoreMode, shapeMode, - snapMode + snapMode, + gameObject.creatorUserId ) GameCommon.setGame(gameObject.id, gameObject) diff --git a/src/server/GameLog.ts b/src/server/GameLog.ts index a8b2050..05ef870 100644 --- a/src/server/GameLog.ts +++ b/src/server/GameLog.ts @@ -90,6 +90,7 @@ const get = ( log[0][5] = DefaultScoreMode(log[0][5]) log[0][6] = DefaultShapeMode(log[0][6]) log[0][7] = DefaultSnapMode(log[0][7]) + log[0][8] = log[0][8] || null } return log } diff --git a/src/server/GameStorage.ts b/src/server/GameStorage.ts index 35d0023..b5df21a 100644 --- a/src/server/GameStorage.ts +++ b/src/server/GameStorage.ts @@ -5,6 +5,7 @@ import Util, { logger } from './../common/Util' import { Rng } from './../common/Rng' import { DATA_DIR } from './Dirs' import Time from './../common/Time' +import Db from './Db' const log = logger('GameStorage.js') @@ -15,8 +16,73 @@ function setDirty(gameId: string): void { function setClean(gameId: string): void { delete dirtyGames[gameId] } +function loadGamesFromDb(db: Db): void { + const gameRows = db.getMany('games') + for (const gameRow of gameRows) { + loadGameFromDb(db, gameRow.id) + } +} -function loadGames(): void { +function loadGameFromDb(db: Db, gameId: string): void { + const gameRow = db.get('games', {id: gameId}) + + let game + try { + game = JSON.parse(gameRow.data) + } catch { + log.log(`[ERR] unable to load game from db ${gameId}`); + } + if (typeof game.puzzle.data.started === 'undefined') { + game.puzzle.data.started = gameRow.created + } + if (typeof game.puzzle.data.finished === 'undefined') { + game.puzzle.data.finished = gameRow.finished + } + if (!Array.isArray(game.players)) { + game.players = Object.values(game.players) + } + + const gameObject: Game = storeDataToGame(game, game.creator_user_id) + GameCommon.setGame(gameObject.id, gameObject) +} + +function persistGamesToDb(db: Db): void { + for (const gameId of Object.keys(dirtyGames)) { + persistGameToDb(db, gameId) + } +} + +function persistGameToDb(db: Db, gameId: string): void { + const game: Game|null = GameCommon.get(gameId) + if (!game) { + log.error(`[ERROR] unable to persist non existing game ${gameId}`) + return + } + + if (game.id in dirtyGames) { + setClean(game.id) + } + + db.upsert('games', { + id: game.id, + + creator_user_id: game.creatorUserId, + image_id: game.puzzle.info.image?.id, + + created: game.puzzle.data.started, + finished: game.puzzle.data.finished, + + data: gameToStoreData(game) + }, { + id: game.id, + }) + log.info(`[INFO] persisted game ${game.id}`) +} + +/** + * @deprecated + */ +function loadGamesFromDisk(): void { const files = fs.readdirSync(DATA_DIR) for (const f of files) { const m = f.match(/^([a-z0-9]+)\.json$/) @@ -24,11 +90,14 @@ function loadGames(): void { continue } const gameId = m[1] - loadGame(gameId) + loadGameFromDisk(gameId) } } -function loadGame(gameId: string): void { +/** + * @deprecated + */ +function loadGameFromDisk(gameId: string): void { const file = `${DATA_DIR}/${gameId}.json` const contents = fs.readFileSync(file, 'utf-8') let game @@ -49,29 +118,23 @@ function loadGame(gameId: string): void { if (!Array.isArray(game.players)) { game.players = Object.values(game.players) } - const gameObject: Game = { - id: game.id, - rng: { - type: game.rng ? game.rng.type : '_fake_', - obj: game.rng ? Rng.unserialize(game.rng.obj) : new Rng(0), - }, - puzzle: game.puzzle, - players: game.players, - evtInfos: {}, - scoreMode: DefaultScoreMode(game.scoreMode), - shapeMode: DefaultShapeMode(game.shapeMode), - snapMode: DefaultSnapMode(game.snapMode), - } + const gameObject: Game = storeDataToGame(game, null) GameCommon.setGame(gameObject.id, gameObject) } -function persistGames(): void { +/** + * @deprecated + */ +function persistGamesToDisk(): void { for (const gameId of Object.keys(dirtyGames)) { - persistGame(gameId) + persistGameToDisk(gameId) } } -function persistGame(gameId: string): void { +/** + * @deprecated + */ +function persistGameToDisk(gameId: string): void { const game = GameCommon.get(gameId) if (!game) { log.error(`[ERROR] unable to persist non existing game ${gameId}`) @@ -81,7 +144,29 @@ function persistGame(gameId: string): void { if (game.id in dirtyGames) { setClean(game.id) } - fs.writeFileSync(`${DATA_DIR}/${game.id}.json`, JSON.stringify({ + fs.writeFileSync(`${DATA_DIR}/${game.id}.json`, gameToStoreData(game)) + log.info(`[INFO] persisted game ${game.id}`) +} + +function storeDataToGame(storeData: any, creatorUserId: number|null): Game { + return { + id: storeData.id, + creatorUserId, + rng: { + type: storeData.rng ? storeData.rng.type : '_fake_', + obj: storeData.rng ? Rng.unserialize(storeData.rng.obj) : new Rng(0), + }, + puzzle: storeData.puzzle, + players: storeData.players, + evtInfos: {}, + scoreMode: DefaultScoreMode(storeData.scoreMode), + shapeMode: DefaultShapeMode(storeData.shapeMode), + snapMode: DefaultSnapMode(storeData.snapMode), + } +} + +function gameToStoreData(game: Game): string { + return JSON.stringify({ id: game.id, rng: { type: game.rng.type, @@ -92,14 +177,20 @@ function persistGame(gameId: string): void { scoreMode: game.scoreMode, shapeMode: game.shapeMode, snapMode: game.snapMode, - })) - log.info(`[INFO] persisted game ${game.id}`) + }); } export default { - loadGames, - loadGame, - persistGames, - persistGame, + // disk functions are deprecated + loadGamesFromDisk, + loadGameFromDisk, + persistGamesToDisk, + persistGameToDisk, + + loadGamesFromDb, + loadGameFromDb, + persistGamesToDb, + persistGameToDb, + setDirty, } diff --git a/src/server/main.ts b/src/server/main.ts index ad29d1d..47cdaba 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -58,10 +58,7 @@ const storage = multer.diskStorage({ const upload = multer({storage}).single('file'); app.get('/api/me', (req, res): void => { - let user = db.get('users', { - 'client_id': req.headers['client-id'], - 'client_secret': req.headers['client-secret'], - }) + let user = getUser(db, req) res.send({ id: user ? user.id : null, created: user ? user.created : null, @@ -103,6 +100,7 @@ app.get('/api/replay-data', async (req, res): Promise => { log[0][5], log[0][6], log[0][7], + log[0][8], // creatorUserId ) } res.send({ log, game: game ? Util.encodeGame(game) : null }) @@ -144,6 +142,30 @@ interface SaveImageRequestData { tags: string[] } +const getOrCreateUser = (db: Db, req: any): any => { + let user = getUser(db, req) + if (!user) { + db.insert('users', { + 'client_id': req.headers['client-id'], + 'client_secret': req.headers['client-secret'], + 'created': Time.timestamp(), + }) + user = getUser(db, req) + } + return user +} + +const getUser = (db: Db, req: any): any => { + let user = db.get('users', { + 'client_id': req.headers['client-id'], + 'client_secret': req.headers['client-secret'], + }) + if (user) { + user.id = parseInt(user.id, 10) + } + return user +} + const setImageTags = (db: Db, imageId: number, tags: string[]): void => { tags.forEach((tag: string) => { const slug = Util.slug(tag) @@ -158,21 +180,15 @@ const setImageTags = (db: Db, imageId: number, tags: string[]): void => { } app.post('/api/save-image', express.json(), (req, res): void => { - let user = db.get('users', { - 'client_id': req.headers['client-id'], - 'client_secret': req.headers['client-secret'], - }) - let userId: number|null = null - if (user) { - userId = parseInt(user.id, 10) - } else { + let user = getUser(db, req) + if (!user || !user.id) { res.status(403).send({ ok: false, error: 'forbidden' }) return } const data = req.body as SaveImageRequestData let image = db.get('images', {id: data.id}) - if (parseInt(image.uploader_user_id, 10) !== userId) { + if (parseInt(image.uploader_user_id, 10) !== user.id) { res.status(403).send({ ok: false, error: 'forbidden' }) return } @@ -205,26 +221,13 @@ app.post('/api/upload', (req, res): void => { res.status(400).send("Something went wrong!"); } - let user = db.get('users', { - 'client_id': req.headers['client-id'], - 'client_secret': req.headers['client-secret'], - }) - let userId: number|null = null - if (user) { - userId = user.id - } else { - userId = db.insert('users', { - 'client_id': req.headers['client-id'], - 'client_secret': req.headers['client-secret'], - 'created': Time.timestamp(), - }) as number - } + const user = getOrCreateUser(db, req) const dim = await Images.getDimensions( `${UPLOAD_DIR}/${req.file.filename}` ) const imageId = db.insert('images', { - uploader_user_id: userId, + uploader_user_id: user.id, filename: req.file.filename, filename_original: req.file.originalname, title: req.body.title || '', @@ -243,6 +246,12 @@ app.post('/api/upload', (req, res): void => { }) app.post('/api/newgame', express.json(), async (req, res): Promise => { + let user = getOrCreateUser(db, req) + if (!user || !user.id) { + res.status(403).send({ ok: false, error: 'forbidden' }) + return + } + const gameSettings = req.body as GameSettings log.log(gameSettings) const gameId = Util.uniqId() @@ -256,6 +265,7 @@ app.post('/api/newgame', express.json(), async (req, res): Promise => { gameSettings.scoreMode, gameSettings.shapeMode, gameSettings.snapMode, + user.id, ) } res.send({ id: gameId }) @@ -355,7 +365,7 @@ wss.on('message', async ( } }) -GameStorage.loadGames() +GameStorage.loadGamesFromDb(db) const server = app.listen( port, hostname, @@ -378,7 +388,7 @@ memoryUsageHuman() // persist games in fixed interval const persistInterval = setInterval(() => { log.log('Persisting games...') - GameStorage.persistGames() + GameStorage.persistGamesToDb(db) memoryUsageHuman() }, config.persistence.interval) @@ -390,7 +400,7 @@ const gracefulShutdown = (signal: string): void => { clearInterval(persistInterval) log.log('persisting games...') - GameStorage.persistGames() + GameStorage.persistGamesToDb(db) log.log('shutting down webserver...') server.close() From e7f86b5ef8879355915355362d2842f7f8f647be Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Thu, 15 Jul 2021 23:10:27 +0200 Subject: [PATCH 42/45] cleanup --- src/server/Game.ts | 42 ++++++++++--------- src/server/GameLog.ts | 2 +- src/server/GameStorage.ts | 34 ++------------- src/server/Images.ts | 17 +++++++- src/server/Users.ts | 36 ++++++++++++++++ src/server/main.ts | 88 ++++++++------------------------------- 6 files changed, 95 insertions(+), 124 deletions(-) create mode 100644 src/server/Users.ts diff --git a/src/server/Game.ts b/src/server/Game.ts index 4b1aba2..2eb5ae0 100644 --- a/src/server/Game.ts +++ b/src/server/Game.ts @@ -1,5 +1,5 @@ import GameCommon from './../common/GameCommon' -import { Change, Game, Input, ScoreMode, ShapeMode, SnapMode,ImageInfo, Timestamp } from './../common/Types' +import { Change, Game, Input, ScoreMode, ShapeMode, SnapMode,ImageInfo, Timestamp, GameSettings } from './../common/Types' import Util, { logger } from './../common/Util' import { Rng } from './../common/Rng' import GameLog from './GameLog' @@ -34,24 +34,24 @@ async function createGameObject( } } -async function createGame( - gameId: string, - targetTiles: number, - image: ImageInfo, +async function createNewGame( + gameSettings: GameSettings, ts: Timestamp, - scoreMode: ScoreMode, - shapeMode: ShapeMode, - snapMode: SnapMode, creatorUserId: number -): Promise { +): Promise { + let gameId; + do { + gameId = Util.uniqId() + } while (GameCommon.exists(gameId)) + const gameObject = await createGameObject( gameId, - targetTiles, - image, + gameSettings.tiles, + gameSettings.image, ts, - scoreMode, - shapeMode, - snapMode, + gameSettings.scoreMode, + gameSettings.shapeMode, + gameSettings.snapMode, creatorUserId ) @@ -60,17 +60,19 @@ async function createGame( gameId, Protocol.LOG_HEADER, 1, - targetTiles, - image, + gameSettings.tiles, + gameSettings.image, ts, - scoreMode, - shapeMode, - snapMode, + gameSettings.scoreMode, + gameSettings.shapeMode, + gameSettings.snapMode, gameObject.creatorUserId ) GameCommon.setGame(gameObject.id, gameObject) GameStorage.setDirty(gameId) + + return gameId } function addPlayer(gameId: string, playerId: string, ts: Timestamp): void { @@ -105,7 +107,7 @@ function handleInput( export default { createGameObject, - createGame, + createNewGame, addPlayer, handleInput, } diff --git a/src/server/GameLog.ts b/src/server/GameLog.ts index 05ef870..ec7a898 100644 --- a/src/server/GameLog.ts +++ b/src/server/GameLog.ts @@ -90,7 +90,7 @@ const get = ( log[0][5] = DefaultScoreMode(log[0][5]) log[0][6] = DefaultShapeMode(log[0][6]) log[0][7] = DefaultSnapMode(log[0][7]) - log[0][8] = log[0][8] || null + log[0][8] = log[0][8] || null // creatorUserId } return log } diff --git a/src/server/GameStorage.ts b/src/server/GameStorage.ts index b5df21a..4b2de65 100644 --- a/src/server/GameStorage.ts +++ b/src/server/GameStorage.ts @@ -41,7 +41,7 @@ function loadGameFromDb(db: Db, gameId: string): void { if (!Array.isArray(game.players)) { game.players = Object.values(game.players) } - + const gameObject: Game = storeDataToGame(game, game.creator_user_id) GameCommon.setGame(gameObject.id, gameObject) } @@ -122,32 +122,6 @@ function loadGameFromDisk(gameId: string): void { GameCommon.setGame(gameObject.id, gameObject) } -/** - * @deprecated - */ -function persistGamesToDisk(): void { - for (const gameId of Object.keys(dirtyGames)) { - persistGameToDisk(gameId) - } -} - -/** - * @deprecated - */ -function persistGameToDisk(gameId: string): void { - const game = GameCommon.get(gameId) - if (!game) { - log.error(`[ERROR] unable to persist non existing game ${gameId}`) - return - } - - if (game.id in dirtyGames) { - setClean(game.id) - } - fs.writeFileSync(`${DATA_DIR}/${game.id}.json`, gameToStoreData(game)) - log.info(`[INFO] persisted game ${game.id}`) -} - function storeDataToGame(storeData: any, creatorUserId: number|null): Game { return { id: storeData.id, @@ -181,16 +155,14 @@ function gameToStoreData(game: Game): string { } export default { - // disk functions are deprecated + // disk functions are deprecated loadGamesFromDisk, loadGameFromDisk, - persistGamesToDisk, - persistGameToDisk, loadGamesFromDb, loadGameFromDb, persistGamesToDb, persistGameToDb, - + setDirty, } diff --git a/src/server/Images.ts b/src/server/Images.ts index dd46e07..7632b2a 100644 --- a/src/server/Images.ts +++ b/src/server/Images.ts @@ -6,7 +6,7 @@ import sharp from 'sharp' import {UPLOAD_DIR, UPLOAD_URL} from './Dirs' import Db, { OrderBy, WhereRaw } from './Db' import { Dim } from '../common/Geometry' -import { logger } from '../common/Util' +import Util, { logger } from '../common/Util' import { Tag, ImageInfo } from '../common/Types' const log = logger('Images.ts') @@ -209,6 +209,20 @@ async function getDimensions(imagePath: string): Promise { } } +const setTags = (db: Db, imageId: number, tags: string[]): void => { + db.delete('image_x_category', { image_id: imageId }) + tags.forEach((tag: string) => { + const slug = Util.slug(tag) + const id = db.upsert('categories', { slug, title: tag }, { slug }, 'id') + if (id) { + db.insert('image_x_category', { + image_id: imageId, + category_id: id, + }) + } + }) +} + export default { allImagesFromDisk, imageFromDb, @@ -216,4 +230,5 @@ export default { getAllTags, resizeImage, getDimensions, + setTags, } diff --git a/src/server/Users.ts b/src/server/Users.ts new file mode 100644 index 0000000..4c5fb00 --- /dev/null +++ b/src/server/Users.ts @@ -0,0 +1,36 @@ +import Time from '../common/Time' +import Db from './Db' + +const TABLE = 'users' + +const HEADER_CLIENT_ID = 'client-id' +const HEADER_CLIENT_SECRET = 'client-secret' + +const getOrCreateUser = (db: Db, req: any): any => { + let user = getUser(db, req) + if (!user) { + db.insert(TABLE, { + 'client_id': req.headers[HEADER_CLIENT_ID], + 'client_secret': req.headers[HEADER_CLIENT_SECRET], + 'created': Time.timestamp(), + }) + user = getUser(db, req) + } + return user +} + +const getUser = (db: Db, req: any): any => { + const user = db.get(TABLE, { + 'client_id': req.headers[HEADER_CLIENT_ID], + 'client_secret': req.headers[HEADER_CLIENT_SECRET], + }) + if (user) { + user.id = parseInt(user.id, 10) + } + return user +} + +export default { + getOrCreateUser, + getUser, +} diff --git a/src/server/main.ts b/src/server/main.ts index 47cdaba..8242b21 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -22,6 +22,7 @@ import GameCommon from '../common/GameCommon' import { ServerEvent, Game as GameType, GameSettings } from '../common/Types' import GameStorage from './GameStorage' import Db from './Db' +import Users from './Users' const db = new Db(DB_FILE, DB_PATCHES_DIR) db.patch() @@ -58,7 +59,7 @@ const storage = multer.diskStorage({ const upload = multer({storage}).single('file'); app.get('/api/me', (req, res): void => { - let user = getUser(db, req) + let user = Users.getUser(db, req) res.send({ id: user ? user.id : null, created: user ? user.created : null, @@ -142,52 +143,15 @@ interface SaveImageRequestData { tags: string[] } -const getOrCreateUser = (db: Db, req: any): any => { - let user = getUser(db, req) - if (!user) { - db.insert('users', { - 'client_id': req.headers['client-id'], - 'client_secret': req.headers['client-secret'], - 'created': Time.timestamp(), - }) - user = getUser(db, req) - } - return user -} - -const getUser = (db: Db, req: any): any => { - let user = db.get('users', { - 'client_id': req.headers['client-id'], - 'client_secret': req.headers['client-secret'], - }) - if (user) { - user.id = parseInt(user.id, 10) - } - return user -} - -const setImageTags = (db: Db, imageId: number, tags: string[]): void => { - tags.forEach((tag: string) => { - const slug = Util.slug(tag) - const id = db.upsert('categories', { slug, title: tag }, { slug }, 'id') - if (id) { - db.insert('image_x_category', { - image_id: imageId, - category_id: id, - }) - } - }) -} - app.post('/api/save-image', express.json(), (req, res): void => { - let user = getUser(db, req) + const user = Users.getUser(db, req) if (!user || !user.id) { res.status(403).send({ ok: false, error: 'forbidden' }) return } const data = req.body as SaveImageRequestData - let image = db.get('images', {id: data.id}) + const image = db.get('images', {id: data.id}) if (parseInt(image.uploader_user_id, 10) !== user.id) { res.status(403).send({ ok: false, error: 'forbidden' }) return @@ -199,11 +163,7 @@ app.post('/api/save-image', express.json(), (req, res): void => { id: data.id, }) - db.delete('image_x_category', { image_id: data.id }) - - if (data.tags) { - setImageTags(db, data.id, data.tags) - } + Images.setTags(db, data.id, data.tags || []) res.send({ ok: true }) }) @@ -211,17 +171,19 @@ app.post('/api/upload', (req, res): void => { upload(req, res, async (err: any): Promise => { if (err) { log.log(err) - res.status(400).send("Something went wrong!"); + res.status(400).send("Something went wrong!") + return } try { await Images.resizeImage(req.file.filename) } catch (err) { log.log(err) - res.status(400).send("Something went wrong!"); + res.status(400).send("Something went wrong!") + return } - const user = getOrCreateUser(db, req) + const user = Users.getOrCreateUser(db, req) const dim = await Images.getDimensions( `${UPLOAD_DIR}/${req.file.filename}` @@ -238,7 +200,7 @@ app.post('/api/upload', (req, res): void => { if (req.body.tags) { const tags = req.body.tags.split(',').filter((tag: string) => !!tag) - setImageTags(db, imageId as number, tags) + Images.setTags(db, imageId as number, tags) } res.send(Images.imageFromDb(db, imageId as number)) @@ -246,28 +208,12 @@ app.post('/api/upload', (req, res): void => { }) app.post('/api/newgame', express.json(), async (req, res): Promise => { - let user = getOrCreateUser(db, req) - if (!user || !user.id) { - res.status(403).send({ ok: false, error: 'forbidden' }) - return - } - - const gameSettings = req.body as GameSettings - log.log(gameSettings) - const gameId = Util.uniqId() - if (!GameCommon.exists(gameId)) { - const ts = Time.timestamp() - await Game.createGame( - gameId, - gameSettings.tiles, - gameSettings.image, - ts, - gameSettings.scoreMode, - gameSettings.shapeMode, - gameSettings.snapMode, - user.id, - ) - } + const user = Users.getOrCreateUser(db, req) + const gameId = await Game.createNewGame( + req.body as GameSettings, + Time.timestamp(), + user.id + ) res.send({ id: gameId }) }) From b4980e367cc8ecae10fb6e6b5ae18ff5b51139f6 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Thu, 15 Jul 2021 23:59:25 +0200 Subject: [PATCH 43/45] fix wording --- src/frontend/components/NewGameDialog.vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/frontend/components/NewGameDialog.vue b/src/frontend/components/NewGameDialog.vue index 731c71f..eff46a7 100644 --- a/src/frontend/components/NewGameDialog.vue +++ b/src/frontend/components/NewGameDialog.vue @@ -35,20 +35,20 @@ Normal
+ Any (Flat pieces can occur anywhere)
+ Flat (All pieces flat on all sides) + Normal (Pieces snap to final destination and to each other)
+ Real (Pieces snap only to corners, already snapped pieces and to each other) From bf4897bf83f0a777180c9ca7cee2fb04007332c4 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Fri, 16 Jul 2021 00:05:50 +0200 Subject: [PATCH 44/45] fix type hint --- src/frontend/views/Replay.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/views/Replay.vue b/src/frontend/views/Replay.vue index 1bf775a..97ec422 100644 --- a/src/frontend/views/Replay.vue +++ b/src/frontend/views/Replay.vue @@ -49,7 +49,7 @@ import InfoOverlay from './../components/InfoOverlay.vue' import HelpOverlay from './../components/HelpOverlay.vue' import { main, MODE_REPLAY } from './../game' -import { Player } from '../../common/Types' +import { Game, Player } from '../../common/Types' export default defineComponent({ name: 'replay', From 68a267bd70666e784129e25275ed7d1d2720abd9 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sun, 10 Oct 2021 12:09:50 +0200 Subject: [PATCH 45/45] only watch build dir --- scripts/server | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/server b/scripts/server index 182e3a4..4d1a1c9 100755 --- a/scripts/server +++ b/scripts/server @@ -1,4 +1,4 @@ #!/bin/sh # server for built files -nodemon --max-old-space-size=64 -e js build/server/main.js -c config.json +nodemon --watch build --max-old-space-size=64 -e js build/server/main.js -c config.json