From 08b332ac6ff5f570d94dc153262a63453beb42b6 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 29 May 2021 09:06:14 +0200 Subject: [PATCH 01/78] add import script for existing game logs --- build/server/main.js | 41 ++++++++++++++++++++++++++++ scripts/import_game_logs.ts | 47 +++++++++++++++++++++++++++++++++ src/dbpatches/02_gamelog.sqlite | 7 +++++ src/server/Db.ts | 46 ++++++++++++++++++++++++++++++++ src/server/main.ts | 3 +-- 5 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 scripts/import_game_logs.ts create mode 100644 src/dbpatches/02_gamelog.sqlite diff --git a/build/server/main.js b/build/server/main.js index 098ff8d..618ce84 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -1730,6 +1730,9 @@ var GameSockets = { }; const log$1 = logger('Db.ts'); +// assume 32766 SQLITE_MAX_VARIABLE_NUMBER +// @see https://sqlite.org/limits.html +const SQLITE_MAX_VARIABLE_NUMBER = 32766; class Db { constructor(file, patchesDir) { this.file = file; @@ -1855,6 +1858,9 @@ class Db { } return this.get(table, check)[idcol]; // get id manually } + /** + * Inserts data into table and returns the last insert id + */ insert(table, data) { const keys = Object.keys(data); const values = keys.map(k => data[k]); @@ -1863,6 +1869,41 @@ class Db { + ' VALUES (' + keys.map(k => '?').join(',') + ')'; return this.run(sql, values).lastInsertRowid; } + /** + * Inserts multiple datas into table. Returns the total number + * of changes. + */ + insertMany(table, datas) { + if (datas.length === 0) { + return 0; + } + const keys = Object.keys(datas[0]); + const runChunk = (vars, values) => { + const sql = `INSERT INTO ${table} + (${keys.join(',')}) + VALUES ${vars.join(',')}`; + return this.run(sql, values).changes; + }; + let len = 0; + let vars = []; + let values = []; + let changes = 0; + for (const data of datas) { + if (len + keys.length > SQLITE_MAX_VARIABLE_NUMBER) { + changes += runChunk(vars, values); + len = 0; + vars = []; + values = []; + } + len += keys.length; + vars.push('(' + keys.map(_ => '?').join(',') + ')'); + values.push(...keys.map(k => data[k])); + } + if (len > 0) { + changes += runChunk(vars, values); + } + return changes; + } update(table, data, whereRaw = {}) { const keys = Object.keys(data); if (keys.length === 0) { diff --git a/scripts/import_game_logs.ts b/scripts/import_game_logs.ts new file mode 100644 index 0000000..8b88bb9 --- /dev/null +++ b/scripts/import_game_logs.ts @@ -0,0 +1,47 @@ +import { DB_FILE, DB_PATCHES_DIR, DATA_DIR } from '../src/server/Dirs' +import Db from '../src/server/Db' +import fs from 'fs' +import { logger } from '../src/common/Util' + +const log = logger('import_game_logs.ts') +const db = new Db(DB_FILE, DB_PATCHES_DIR) +db.patch(true) + + +for (const file of fs.readdirSync(DATA_DIR)) { + const m = file.match(/^log_(.*)\.log$/) + if (!m) { + continue + } + + const gameId = m[1] + log.info(`Importing log for game ${file}`) + + const contents = fs.readFileSync(`${DATA_DIR}/${file}`, 'utf-8') + let t = 0 + let datas = [] + for (const line of contents.split("\n")) { + if (line === '') { + continue + } + let parsed + try { + parsed = JSON.parse(line) + } catch (e) { + log.error('bad game', e) + break + } + if (t === 0) { + t = parsed[4] + } else { + t += parsed[parsed.length - 1] + } + datas.push({ + game_id: gameId, + created: t / 1000, + entry: line, + }) + } + db.insertMany('game_log', datas) + log.info(`Done.`) +} diff --git a/src/dbpatches/02_gamelog.sqlite b/src/dbpatches/02_gamelog.sqlite new file mode 100644 index 0000000..4fe0160 --- /dev/null +++ b/src/dbpatches/02_gamelog.sqlite @@ -0,0 +1,7 @@ +CREATE TABLE game_log ( + game_id TEXT, + + created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + + entry TEXT NOT NULL +); diff --git a/src/server/Db.ts b/src/server/Db.ts index a3277f0..b61c9bd 100644 --- a/src/server/Db.ts +++ b/src/server/Db.ts @@ -5,6 +5,10 @@ import { logger } from '../common/Util' const log = logger('Db.ts') +// assume 32766 SQLITE_MAX_VARIABLE_NUMBER +// @see https://sqlite.org/limits.html +const SQLITE_MAX_VARIABLE_NUMBER = 32766 + /** * TODO: create a more specific type for OrderBy. * It looks like this (example): @@ -186,6 +190,9 @@ class Db { return this.get(table, check)[idcol] // get id manually } + /** + * Inserts data into table and returns the last insert id + */ insert (table: string, data: Data): Integer.IntLike { const keys = Object.keys(data) const values = keys.map(k => data[k]) @@ -195,6 +202,45 @@ class Db { return this.run(sql, values).lastInsertRowid } + /** + * Inserts multiple datas into table. Returns the total number + * of changes. + */ + insertMany (table: string, datas: Data[]): number { + if (datas.length === 0) { + return 0 + } + + const keys = Object.keys(datas[0]) + + const runChunk = (vars: string[], values: any[]) => { + const sql = `INSERT INTO ${table} + (${keys.join(',')}) + VALUES ${vars.join(',')}` + return this.run(sql, values).changes + } + + let len: number = 0 + let vars: string[] = [] + let values: any[] = [] + let changes = 0 + for (const data of datas) { + if (len + keys.length > SQLITE_MAX_VARIABLE_NUMBER) { + changes += runChunk(vars, values) + len = 0 + vars = [] + values = [] + } + len += keys.length + vars.push('(' + keys.map(_ => '?').join(',') + ')') + values.push(...keys.map(k => data[k])) + } + if (len > 0) { + changes += runChunk(vars, values) + } + return changes + } + update (table: string, data: Data, whereRaw: WhereRaw = {}): void { const keys = Object.keys(data) if (keys.length === 0) { diff --git a/src/server/main.ts b/src/server/main.ts index c5c89c4..7bc31cf 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -16,8 +16,7 @@ import { DB_FILE, DB_PATCHES_DIR, PUBLIC_DIR, - UPLOAD_DIR, - UPLOAD_URL + UPLOAD_DIR } from './Dirs' import { GameSettings, ScoreMode } from '../common/GameCommon' import GameStorage from './GameStorage' From 7a7d6580fcf46d1ea994ec90e7909dec7a803d36 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 29 May 2021 11:44:55 +0200 Subject: [PATCH 02/78] enable replays --- src/common/GameCommon.ts | 15 +++++++-- src/common/Protocol.ts | 8 ++--- src/frontend/Communication.ts | 29 +++++++++++++---- src/frontend/components/GameTeaser.vue | 2 +- src/frontend/game.ts | 44 +++++++++++++++++++++++--- src/server/GameLog.ts | 40 +++++++++++++++++------ src/server/main.ts | 27 ++++++++++------ 7 files changed, 129 insertions(+), 36 deletions(-) diff --git a/src/common/GameCommon.ts b/src/common/GameCommon.ts index 8d48b70..f498009 100644 --- a/src/common/GameCommon.ts +++ b/src/common/GameCommon.ts @@ -170,8 +170,11 @@ function getPlayerIdByIndex(gameId: string, playerIndex: number): string|null { return null } -function getPlayer(gameId: string, playerId: string): Player { +function getPlayer(gameId: string, playerId: string): Player|null { const idx = getPlayerIndexById(gameId, playerId) + if (idx === -1) { + return null + } return Util.decodePlayer(GAMES[gameId].players[idx]) } @@ -299,6 +302,10 @@ function changePlayer( change: any ): void { const player = getPlayer(gameId, playerId) + if (player === null) { + return + } + for (let k of Object.keys(change)) { // @ts-ignore player[k] = change[k] @@ -647,9 +654,13 @@ function handleInput( } const _playerChange = (): void => { + const player = getPlayer(gameId, playerId) + if (!player) { + return + } changes.push([ Protocol.CHANGE_PLAYER, - Util.encodePlayer(getPlayer(gameId, playerId)), + Util.encodePlayer(player), ]) } diff --git a/src/common/Protocol.ts b/src/common/Protocol.ts index 868e4c9..baa0280 100644 --- a/src/common/Protocol.ts +++ b/src/common/Protocol.ts @@ -40,10 +40,10 @@ EV_SERVER_INIT: event sent to one client after that client */ const EV_SERVER_EVENT = 1 const EV_SERVER_INIT = 4 -const EV_SERVER_INIT_REPLAY = 5 +const EV_SERVER_REPLAY_DATA = 5 const EV_CLIENT_EVENT = 2 const EV_CLIENT_INIT = 3 -const EV_CLIENT_INIT_REPLAY = 6 +const EV_CLIENT_REPLAY_DATA = 6 const LOG_HEADER = 1 const LOG_ADD_PLAYER = 2 @@ -68,10 +68,10 @@ const CHANGE_PLAYER = 3 export default { EV_SERVER_EVENT, EV_SERVER_INIT, - EV_SERVER_INIT_REPLAY, + EV_SERVER_REPLAY_DATA, EV_CLIENT_EVENT, EV_CLIENT_INIT, - EV_CLIENT_INIT_REPLAY, + EV_CLIENT_REPLAY_DATA, LOG_HEADER, LOG_ADD_PLAYER, diff --git a/src/frontend/Communication.ts b/src/frontend/Communication.ts index b0843dc..e4d7d8f 100644 --- a/src/frontend/Communication.ts +++ b/src/frontend/Communication.ts @@ -96,6 +96,13 @@ function connect( }) } +function requestReplayData( + offset: number, + size: number +): void { + send([Protocol.EV_CLIENT_REPLAY_DATA, offset, size]) +} + // TOOD: change replay stuff function connectReplay( address: string, @@ -109,16 +116,25 @@ function connectReplay( ws = new WebSocket(address, clientId + '|' + gameId) ws.onopen = (e) => { setConnectionState(CONN_STATE_CONNECTED) - send([Protocol.EV_CLIENT_INIT_REPLAY]) + requestReplayData(0, 10000) } ws.onmessage = (e) => { const msg = JSON.parse(e.data) const msgType = msg[0] - if (msgType === Protocol.EV_SERVER_INIT_REPLAY) { - const game = msg[1] - const log = msg[2] - const replay: { game: any, log: Array } = { game, log } - resolve(replay) + if (msgType === Protocol.EV_SERVER_REPLAY_DATA) { + const log: any[] = msg[1] + const game = msg[2] // can be null or encoded game + if (game !== null) { + // this is the first/initial message + const replay: { + game: any, + log: any[] + } = { game, log } + resolve(replay) + } else { + // this is just the next batch of log entries + changesCallback(msg) + } } else { throw `[ 2021-05-09 invalid connectReplay msgType ${msgType} ]` } @@ -158,6 +174,7 @@ function sendClientEvent(evt: any): void { export default { connect, connectReplay, + requestReplayData, disconnect, sendClientEvent, onServerChange, diff --git a/src/frontend/components/GameTeaser.vue b/src/frontend/components/GameTeaser.vue index ce72bb9..94b01c3 100644 --- a/src/frontend/components/GameTeaser.vue +++ b/src/frontend/components/GameTeaser.vue @@ -7,7 +7,7 @@ {{time(game.started, game.finished)}}
- + ↪️ Watch replay diff --git a/src/frontend/game.ts b/src/frontend/game.ts index 4dbb127..50be60e 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -48,7 +48,10 @@ interface Hud { setReplayPaused?: (v: boolean) => void } interface Replay { + final: boolean + requesting: boolean log: Array + logPointer: number, logIdx: number speeds: Array speedIdx: number @@ -260,7 +263,10 @@ export async function main( // stuff only available in replay mode... // TODO: refactor const REPLAY: Replay = { + final: false, + requesting: true, log: [], + logPointer: 0, logIdx: 0, speeds: [0.5, 1, 2, 5, 10, 20, 50], speedIdx: 1, @@ -283,12 +289,26 @@ export async function main( TIME = () => Time.timestamp() } else if (MODE === MODE_REPLAY) { // TODO: change how replay connect is done... + Communication.onServerChange((msg) => { + const log = msg[1] + + // cut log that was already handled + REPLAY.log = REPLAY.log.slice(REPLAY.logPointer) + REPLAY.logPointer = 0 + + REPLAY.log.push(...msg[1]) + if (log.length < 10000) { + REPLAY.final = true + } + REPLAY.requesting = false + }) const replay: {game: any, log: Array} = await Communication.connectReplay(wsAddress, gameId, clientId) const gameObject = Util.decodeGame(replay.game) Game.setGame(gameObject.id, gameObject) + REPLAY.requesting = false REPLAY.log = replay.log REPLAY.lastRealTs = Time.timestamp() - REPLAY.gameStartTs = parseInt(REPLAY.log[0][REPLAY.log[0].length - 2], 10) + REPLAY.gameStartTs = parseInt(REPLAY.log[0][4], 10) REPLAY.lastGameTs = REPLAY.gameStartTs TIME = () => REPLAY.lastGameTs } else { @@ -426,6 +446,7 @@ export async function main( } if (MODE === MODE_PLAY) { + // TODO: register onServerChange function before connecting to server Communication.onServerChange((msg) => { const msgType = msg[0] const evClientId = msg[1] @@ -458,6 +479,18 @@ export async function main( // only the REPLAY.log is relevant let inter = setInterval(() => { const realTs = Time.timestamp() + if (REPLAY.requesting) { + REPLAY.lastRealTs = realTs + return + } + + if (REPLAY.logPointer + 1 >= REPLAY.log.length) { + REPLAY.lastRealTs = realTs + REPLAY.requesting = true + Communication.requestReplayData(REPLAY.logIdx, 10000) + return + } + if (REPLAY.paused) { REPLAY.lastRealTs = realTs return @@ -469,9 +502,11 @@ export async function main( if (REPLAY.paused) { break } - const nextIdx = REPLAY.logIdx + 1 + const nextIdx = REPLAY.logPointer + 1 if (nextIdx >= REPLAY.log.length) { - clearInterval(inter) + if (REPLAY.final) { + clearInterval(inter) + } break } @@ -502,7 +537,8 @@ export async function main( Game.handleInput(gameId, playerId, input, nextTs) RERENDER = true } - REPLAY.logIdx = nextIdx + REPLAY.logPointer = nextIdx + REPLAY.logIdx++ } while (true) REPLAY.lastRealTs = realTs REPLAY.lastGameTs = maxGameTs diff --git a/src/server/GameLog.ts b/src/server/GameLog.ts index 385c17a..7fb614a 100644 --- a/src/server/GameLog.ts +++ b/src/server/GameLog.ts @@ -1,4 +1,6 @@ import fs from 'fs' +import readline from 'readline' +import stream from 'stream' import { logger } from './../common/Util' import { DATA_DIR } from './../server/Dirs' @@ -27,19 +29,39 @@ const _log = (gameId: string, ...args: Array) => { fs.appendFileSync(file, str + "\n") } -const get = (gameId: string) => { +const get = async ( + gameId: string, + offset: number = 0, + size: number = 10000 +): Promise => { const file = filename(gameId) if (!fs.existsSync(file)) { return [] } - const lines = fs.readFileSync(file, 'utf-8').split("\n") - return lines.filter((line: string) => !!line).map((line: string) => { - try { - return JSON.parse(line) - } catch (e) { - log.log(line) - log.log(e) - } + 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) + }) }) } diff --git a/src/server/main.ts b/src/server/main.ts index c5c89c4..7314148 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -203,20 +203,27 @@ wss.on('message', async ({socket, data} : { socket: WebSocket, data: any }) => { const msg = JSON.parse(data) const msgType = msg[0] switch (msgType) { - case Protocol.EV_CLIENT_INIT_REPLAY: { + case Protocol.EV_CLIENT_REPLAY_DATA: { if (!GameLog.exists(gameId)) { throw `[gamelog ${gameId} does not exist... ]` } - const log = GameLog.get(gameId) - const game = await Game.createGameObject( - gameId, - log[0][2], - log[0][3], - log[0][4], - log[0][5] || ScoreMode.FINAL - ) + const offset = msg[1] + const size = msg[2] + + const log = await GameLog.get(gameId, offset, size) + 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 + ) + } notify( - [Protocol.EV_SERVER_INIT_REPLAY, Util.encodeGame(game), log], + [Protocol.EV_SERVER_REPLAY_DATA, log, game ? Util.encodeGame(game) : null], [socket] ) } break From 81ef9cd704833a1d50a28b7c46f871e88a47249c Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 29 May 2021 11:45:57 +0200 Subject: [PATCH 03/78] enable replay (built files) --- build/public/assets/index.1449524a.js | 1 + build/public/assets/index.50ee8245.js | 1 - build/public/index.html | 2 +- build/server/main.js | 78 +++++++++++++++++++-------- 4 files changed, 57 insertions(+), 25 deletions(-) create mode 100644 build/public/assets/index.1449524a.js delete mode 100644 build/public/assets/index.50ee8245.js diff --git a/build/public/assets/index.1449524a.js b/build/public/assets/index.1449524a.js new file mode 100644 index 0000000..0b1e4c9 --- /dev/null +++ b/build/public/assets/index.1449524a.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as l,b as i,r as o,o as a,e as s,t as r,F as d,f as c,g as u,h as g,v as p,i as h,j as m,k as y,l as f,m as w,n as v,p as b,q as x,s as C}from"./vendor.b622ee49.js";var k=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const A={id:"app"},T={key:0,class:"nav"},S=s("Index"),z=s("New game");k.render=function(e,s,r,d,c,u){const g=o("router-link"),p=o("router-view");return a(),t("div",A,[e.showNav?(a(),t("ul",T,[n("li",null,[n(g,{class:"btn",to:{name:"index"}},{default:l((()=>[S])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:l((()=>[z])),_:1})])])):i("",!0),n(p)])};const I=864e5,P=e=>{const t=Math.floor(e/I);e%=I;const n=Math.floor(e/36e5);e%=36e5;const l=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${l}m ${Math.floor(e/1e3)}s`};var _=1e3,D=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>P(t-e),E=P,O=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",l=e,i=t||D();return`${n} ${B(l,i)}`}}});const U={class:"game-info-text"},N=n("br",null,null,-1),M=n("br",null,null,-1),G=n("br",null,null,-1),R=s(" ↪️ Watch replay ");O.render=function(e,d,c,u,g,p){const h=o("router-link");return a(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:l((()=>[n("span",U,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),N,s(" 👥 "+r(e.game.players),1),M,s(" "+r(e.time(e.game.started,e.game.finished)),1),G])])),_:1},8,["to"]),e.game.hasReplay?(a(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:l((()=>[R])),_:1},8,["to"])):i("",!0)],4)};var $=e({components:{GameTeaser:O},data:()=>({gamesRunning:[],gamesFinished:[]}),async created(){const e=await fetch("/api/index-data"),t=await e.json();this.gamesRunning=t.gamesRunning,this.gamesFinished=t.gamesFinished}});const V=n("h1",null,"Running games",-1),j=n("h1",null,"Finished games",-1);$.render=function(e,l,i,s,r,u){const g=o("game-teaser");return a(),t("div",null,[V,(a(!0),t(d,null,c(e.gamesRunning,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128)),j,(a(!0),t(d,null,c(e.gamesFinished,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128))])};var F=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});F.render=function(e,l,i,o,s,r){return a(),t("div",{class:"imageteaser",style:e.style,onClick:l[2]||(l[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:l[1]||(l[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var L=e({name:"image-library",components:{ImageTeaser:F},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});L.render=function(e,n,l,i,s,r){const u=o("image-teaser");return a(),t("div",null,[(a(!0),t(d,null,c(e.images,((n,l)=>(a(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:l},null,8,["image","onClick","onEditClick"])))),128))])};const W={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};W.render=function(e,n,l,i,o,s){return a(),t("div",{style:s.style,title:l.title},null,12,["title"])};var q=e({name:"tags-input",props:{modelValue:{type:Array,required:!0}},emits:{"update:modelValue":null},data:()=>({input:"",values:[]}),created(){this.values=this.modelValue},methods:{onKeyUp(e){if(","===e.key)return this.add(),e.stopPropagation(),!1},add(){const e=this.input.replace(/,/g,"").trim();e&&(this.values.includes(e)||this.values.push(e),this.input="",this.$emit("update:modelValue",this.values))},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const H=m()(((e,l,i,o,s,u)=>(a(),t("div",null,[g(n("input",{class:"input",type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.input=t),placeholder:"Plants, People",onKeydown:l[2]||(l[2]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:l[3]||(l[3]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[p,e.input]]),(a(!0),t(d,null,c(e.values,((n,l)=>(a(),t("span",{key:l,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));q.render=H,q.__scopeId="data-v-771460ae";var Q=e({name:"new-image-dialog",components:{ResponsiveImage:W,TagsInput:q},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const l=new FileReader;l.readAsDataURL(n),l.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const Y={key:0,class:"has-image"},K={key:1},Z={class:"upload"},J=n("span",{class:"btn"},"Upload File",-1),X={class:"area-settings"},ee=n("td",null,[n("label",null,"Title")],-1),te=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ne=n("td",null,[n("label",null,"Tags")],-1),le={class:"area-buttons"},ie=s("🧩 Post to gallery "),oe=n("br",null,null,-1),ae=s(" + set up game");Q.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay new-image-dialog",onClick:l[8]||(l[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[7]||(l[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(a(),t("div",Y,[n("span",{class:"remove btn",onClick:l[1]||(l[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(a(),t("div",K,[n("label",Z,[n("input",{type:"file",style:{display:"none"},onChange:l[2]||(l[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),J])]))],2),n("div",X,[n("table",null,[n("tr",null,[ee,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[3]||(l[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),te,n("tr",null,[ne,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[4]||(l[4]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",le,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:l[5]||(l[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:l[6]||(l[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[ie,oe,ae],8,["disabled"])])])])};var se=e({name:"edit-image-dialog",components:{ResponsiveImage:W,TagsInput:q},props:{image:{type:Object,required:!0}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const re={class:"area-image"},de={class:"has-image"},ce={class:"area-settings"},ue=n("td",null,[n("label",null,"Title")],-1),ge=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),pe=n("td",null,[n("label",null,"Tags")],-1),he={class:"area-buttons"};function me(e,t){const n=e.x-t.x,l=e.y-t.y;return Math.sqrt(n*n+l*l)}function ye(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}se.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay edit-image-dialog",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("div",re,[n("div",de,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ce,[n("table",null,[n("tr",null,[ue,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ge,n("tr",null,[pe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[2]||(l[2]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",he,[n("button",{class:"btn",onClick:l[3]||(l[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var fe={pointSub:function(e,t){return{x:e.x-t.x,y:e.y-t.y}},pointAdd:function(e,t){return{x:e.x+t.x,y:e.y+t.y}},pointDistance:me,pointInBounds:function(e,t){return e.x>=t.x&&e.x<=t.x+t.w&&e.y>=t.y&&e.y<=t.y+t.h},rectCenter:ye,rectMoved:function(e,t,n){return{x:e.x+t,y:e.y+n,w:e.w,h:e.h}},rectCenterDistance:function(e,t){return me(ye(e),ye(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};var we=1,ve=4,be=5,xe=2,Ce=3,ke=6,Ae=2,Te=4,Se=3,ze=9,Ie=1,Pe=2,_e=3,De=4,Be=5,Ee=6,Oe=7,Ue=8,Ne=10,Me=1,Ge=2,Re=3;class $e{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){return this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295,e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),l=t[n];t[n]=t[e],t[e]=l}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new $e(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const Ve=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},je=(...e)=>{const t=t=>(...n)=>{const l=new Date,i=Ve(l.getHours(),"00"),o=Ve(l.getMinutes(),"00"),a=Ve(l.getSeconds(),"00");console[t](`${i}:${o}:${a}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var Fe,Le,We,qe,He={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}},encodeTile:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodeTile:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return Array.isArray(e)?e:[e.id,e.rng.type,$e.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode]},decodeGame:function(e){return Array.isArray(e)?{id:e[0],rng:{type:e[1],obj:$e.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}:e},coordByTileIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(let n in e){const l=[n,e[n]].map(encodeURIComponent);t.push(l.join("="))}return 0===t.length?"":`?${t.join("&")}`}};(Le=Fe||(Fe={}))[Le.Flat=0]="Flat",Le[Le.Out=1]="Out",Le[Le.In=-1]="In",(qe=We||(We={}))[qe.FINAL=0]="FINAL",qe[qe.ANY=1]="ANY";const Qe={};function Ye(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}function Ke(e,t){let n=0;for(let l of Qe[e].players){if(He.decodePlayer(l).id===t)return n;n++}return-1}function Ze(e,t){const n=Ke(e,t);return-1===n?null:He.decodePlayer(Qe[e].players[n])}function Je(e,t,n){const l=Ke(e,t);-1===l?Qe[e].players.push(He.encodePlayer(n)):Qe[e].players[l]=He.encodePlayer(n)}function Xe(e,t){return-1!==Ke(e,t)}function et(e){return Qe[e]?Qe[e].players.map(He.decodePlayer):[]}function tt(e){return Qe[e].puzzle.tiles.length}function nt(e){return Qe[e].scoreMode||0}function lt(e){return it(e)===tt(e)}function it(e){let t=0;for(let n of Qe[e].puzzle.tiles)-1===He.decodeTile(n).owner&&t++;return t}function ot(e,t,n){const l=Ze(e,t);if(null!==l){for(let e of Object.keys(n))l[e]=n[e];Je(e,t,l)}}function at(e,t){for(let n of Object.keys(t))Qe[e].puzzle.data[n]=t[n]}function st(e,t,n){for(let l of Object.keys(n)){const i=He.decodeTile(Qe[e].puzzle.tiles[t]);i[l]=n[l],Qe[e].puzzle.tiles[t]=He.encodeTile(i)}}const rt=(e,t)=>He.decodeTile(Qe[e].puzzle.tiles[t]),dt=(e,t)=>rt(e,t).group,ct=(e,t)=>{const n=Qe[e].puzzle.info,l={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},i=function(e,t){const n=Qe[e].puzzle.info,l=He.coordByTileIdx(n,t),i=l.x*n.tileSize,o=l.y*n.tileSize;return{x:i,y:o}}(e,t);return fe.pointAdd(l,i)},ut=(e,t)=>rt(e,t).pos,gt=e=>{const t=zt(e),n=It(e),l=Math.round(t/4),i=Math.round(n/4);return{x:0-l,y:0-i,w:t+2*l,h:n+2*i}},pt=(e,t)=>{const n=ft(e),l=rt(e,t);return{x:l.pos.x,y:l.pos.y,w:n,h:n}},ht=(e,t)=>rt(e,t).z,mt=(e,t)=>{for(let n of Qe[e].puzzle.tiles){const e=He.decodeTile(n);if(e.owner===t)return e.idx}return-1},yt=e=>Qe[e].puzzle.info.tileDrawSize,ft=e=>Qe[e].puzzle.info.tileSize,wt=e=>Qe[e].puzzle.data.maxGroup,vt=e=>Qe[e].puzzle.data.maxZ;function bt(e,t){const n=Qe[e].puzzle.info,l=He.coordByTileIdx(n,t);return[l.y>0?t-n.tilesX:-1,l.x0?t-1:-1]}const xt=(e,t,n)=>{for(let l of t)st(e,l,{z:n})},Ct=(e,t,n)=>{const l=ut(e,t);st(e,t,{pos:fe.pointAdd(l,n)})},kt=(e,t,n)=>{const l=yt(e),i=gt(e),o=n;for(let a of t){const t=rt(e,a);t.pos.x+n.xi.x+i.w&&(o.x=Math.min(i.x+i.w-t.pos.x+l,o.x)),t.pos.y+n.yi.y+i.h&&(o.y=Math.min(i.y+i.h-t.pos.y+l,o.y))}for(let a of t)Ct(e,a,o)},At=(e,t,n)=>{for(let l of t)st(e,l,{owner:n})};function Tt(e,t){const n=Qe[e].puzzle.tiles,l=He.decodeTile(n[t]),i=[];if(l.group)for(let o of n){const e=He.decodeTile(o);e.group===l.group&&i.push(e.idx)}else i.push(l.idx);return i}const St=(e,t)=>{const n=Ze(e,t);return n?n.points:0},zt=e=>Qe[e].puzzle.info.table.width,It=e=>Qe[e].puzzle.info.table.height;var Pt={__createPlayerObject:Ye,setGame:function(e,t){Qe[e]=t},exists:function(e){return!!Qe[e]||!1},playerExists:Xe,getActivePlayers:function(e,t){const n=t-30*_;return et(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return et(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Xe(e,t)?ot(e,t,{ts:n}):Je(e,t,Ye(t,n))},getFinishedTileCount:it,getTileCount:tt,getImageUrl:function(e){return Qe[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Qe[e].puzzle.info.imageUrl=t},get:function(e){return Qe[e]},getAllGames:function(){return Object.values(Qe).sort(((e,t)=>lt(e.id)===lt(t.id)?t.puzzle.data.started-e.puzzle.data.started:lt(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Ze(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Ze(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Ze(e,t);return n?n.name:null},getPlayerIndexById:Ke,getPlayerIdByIndex:function(e,t){return Qe[e].players.length>t?He.decodePlayer(Qe[e].players[t]).id:null},changePlayer:ot,setPlayer:Je,setTile:function(e,t,n){Qe[e].puzzle.tiles[t]=He.encodeTile(n)},setPuzzleData:function(e,t){Qe[e].puzzle.data=t},getTableWidth:zt,getTableHeight:It,getPuzzle:e=>Qe[e].puzzle,getRng:e=>Qe[e].rng.obj,getPuzzleWidth:e=>Qe[e].puzzle.info.width,getPuzzleHeight:e=>Qe[e].puzzle.info.height,getTilesSortedByZIndex:function(e){return Qe[e].puzzle.tiles.map(He.decodeTile).sort(((e,t)=>e.z-t.z))},getFirstOwnedTile:(e,t)=>{const n=mt(e,t);return n<0?null:Qe[e].puzzle.tiles[n]},getTileDrawOffset:e=>Qe[e].puzzle.info.tileDrawOffset,getTileDrawSize:yt,getFinalTilePos:ct,getStartTs:e=>Qe[e].puzzle.data.started,getFinishTs:e=>Qe[e].puzzle.data.finished,handleInput:function(e,t,n,l){const i=Qe[e].puzzle,o=function(e,t){return t in Qe[e].evtInfos?Qe[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),a=[],s=()=>{a.push([Me,i.data])},r=t=>{a.push([Ge,He.encodeTile(rt(e,t))])},d=e=>{for(const t of e)r(t)},c=()=>{const n=Ze(e,t);n&&a.push([Re,He.encodePlayer(n)])},u=n[0];if(u===Ee){const i=n[1];ot(e,t,{bgcolor:i,ts:l}),c()}else if(u===Oe){const i=n[1];ot(e,t,{color:i,ts:l}),c()}else if(u===Ue){const i=`${n[1]}`.substr(0,16);ot(e,t,{name:i,ts:l}),c()}else if(u===Ie){const i={x:n[1],y:n[2]};ot(e,t,{d:1,ts:l}),c(),o._last_mouse_down=i;const a=((e,t)=>{let n=Qe[e].puzzle.info,l=Qe[e].puzzle.tiles,i=-1,o=-1;for(let a=0;ai)&&(i=e.z,o=a)}return o})(e,i);if(a>=0){let n=vt(e)+1;at(e,{maxZ:n}),s();const l=Tt(e,a);xt(e,l,vt(e)),At(e,l,t),d(l)}o._last_mouse=i}else if(u===_e){const i=n[1],a=n[2],s={x:i,y:a};if(null===o._last_mouse_down)ot(e,t,{x:i,y:a,ts:l}),c();else{let n=mt(e,t);if(n>=0){ot(e,t,{x:i,y:a,ts:l}),c();const r=Tt(e,n);let u=fe.pointInBounds(s,gt(e))&&fe.pointInBounds(o._last_mouse_down,gt(e));for(let t of r){const n=pt(e,t);if(fe.pointInBounds(s,n)){u=!0;break}}if(u){const t=i-o._last_mouse_down.x,n=a-o._last_mouse_down.y;kt(e,r,{x:t,y:n}),d(r)}}else ot(e,t,{ts:l}),c();o._last_mouse_down=s}o._last_mouse=s}else if(u===Pe){const a={x:n[1],y:n[2]},u=0;o._last_mouse_down=null;let g=mt(e,t);if(g>=0){let n=Tt(e,g);At(e,n,0),d(n);let o=ut(e,g),a=ct(e,g);if(fe.pointDistance(a,o){for(let n of t)st(e,n,{owner:-1,z:1})})(e,n),d(n);let r=St(e,t);0===nt(e)?r+=n.length:1===nt(e)&&(r+=1),ot(e,t,{d:u,ts:l,points:r}),c(),it(e)===tt(e)&&(at(e,{finished:l}),s())}else{const n=(e,t,n,l)=>{let i=Qe[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const l=dt(e,t),i=dt(e,n);return!(!l||l!==i)})(e,t,n))return!1;const o=ut(e,t),a=fe.pointAdd(ut(e,n),{x:l[0]*i.tileSize,y:l[1]*i.tileSize});if(fe.pointDistance(o,a){const l=Qe[e].puzzle.tiles,i=dt(e,t),o=dt(e,n);let a;const d=[];i&&d.push(i),o&&d.push(o),i?a=i:o?a=o:(at(e,{maxGroup:wt(e)+1}),s(),a=wt(e));if(st(e,t,{group:a}),r(t),st(e,n,{group:a}),r(n),d.length>0)for(const s of l){const t=He.decodeTile(s);d.includes(t.group)&&(st(e,t.idx,{group:a}),r(t.idx))}})(e,t,n),i=Tt(e,t);const c=((e,t)=>{let n=0;for(let l of t){let t=ht(e,l);t>n&&(n=t)}return n})(e,i);return xt(e,i,c),d(i),!0}return!1};let i=!1;for(let t of Tt(e,g)){let l=bt(e,t);if(n(e,t,l[0],[0,1])||n(e,t,l[1],[-1,0])||n(e,t,l[2],[0,-1])||n(e,t,l[3],[1,0])){i=!0;break}}if(i&&1===nt(e)){const n=St(e,t)+1;ot(e,t,{d:u,ts:l,points:n}),c()}else ot(e,t,{d:u,ts:l}),c()}}else ot(e,t,{d:u,ts:l}),c();o._last_mouse=a}else if(u===De){const i=n[1],a=n[2];ot(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else if(u===Be){const i=n[1],a=n[2];ot(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else ot(e,t,{ts:l}),c();return function(e,t,n){Qe[e].evtInfos[t]=n}(e,t,o),a}},_t=e({name:"new-game-dialog",components:{ResponsiveImage:W},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:We.ANY}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Dt={class:"area-image"},Bt={class:"has-image"},Et={class:"area-settings"},Ot=n("td",null,[n("label",null,"Pieces")],-1),Ut=n("td",null,[n("label",null,"Scoring: ")],-1),Nt=s(" Any (Score when pieces are connected to each other or on final location)"),Mt=n("br",null,null,-1),Gt=s(" Final (Score when pieces are put to their final location)"),Rt={class:"area-buttons"};_t.render=function(e,l,i,s,r,d){const c=o("responsive-image");return a(),t("div",{class:"overlay new-game-dialog",onClick:l[6]||(l[6]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[5]||(l[5]=u((()=>{}),["stop"]))},[n("div",Dt,[n("div",Bt,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Et,[n("table",null,[n("tr",null,[Ot,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Ut,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[2]||(l[2]=t=>e.scoreMode=t),value:"1"},null,512),[[y,e.scoreMode]]),Nt]),Mt,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[3]||(l[3]=t=>e.scoreMode=t),value:"0"},null,512),[[y,e.scoreMode]]),Gt])])])])]),n("div",Rt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:l[4]||(l[4]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var $t=e({components:{ImageLibrary:L,NewImageDialog:Q,EditImageDialog:se,NewGameDialog:_t},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},methods:{toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${He.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const Vt={class:"upload-image-teaser"},jt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),Ft={key:0},Lt=s(" Tags: "),Wt=s(" Sort by: "),qt=n("option",{value:"date_desc"},"Newest first",-1),Ht=n("option",{value:"date_asc"},"Oldest first",-1),Qt=n("option",{value:"alpha_asc"},"A-Z",-1),Yt=n("option",{value:"alpha_desc"},"Z-A",-1);$t.render=function(e,l,s,u,p,h){const m=o("image-library"),y=o("new-image-dialog"),w=o("edit-image-dialog"),v=o("new-game-dialog");return a(),t("div",null,[n("div",Vt,[n("div",{class:"btn btn-big",onClick:l[1]||(l[1]=t=>e.dialog="new-image")},"Upload your image"),jt]),n("div",null,[e.tags.length>0?(a(),t("label",Ft,[Lt,(a(!0),t(d,null,c(e.tags,((n,l)=>(a(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:l,onClick:t=>e.toggleTag(n)},r(n.title),11,["onClick"])))),128))])):i("",!0),n("label",null,[Wt,g(n("select",{"onUpdate:modelValue":l[2]||(l[2]=t=>e.filters.sort=t),onChange:l[3]||(l[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[qt,Ht,Qt,Yt],544),[[f,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(a(),t(y,{key:0,onBgclick:l[4]||(l[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["onPostToGalleryClick","onSetupGameClick"])):i("",!0),"edit-image"===e.dialog?(a(),t(w,{key:1,onBgclick:l[5]||(l[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["onSaveClick","image"])):i("",!0),e.image&&"new-game"===e.dialog?(a(),t(v,{key:2,onBgclick:l[6]||(l[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):i("",!0)])};var Kt=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 Zt={class:"scores"},Jt=n("div",null,"Scores",-1),Xt=n("td",null,"⚡",-1),en=n("td",null,"💤",-1);Kt.render=function(e,l,i,o,s,u){return a(),t("div",Zt,[Jt,n("table",null,[(a(!0),t(d,null,c(e.actives,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[Xt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(a(!0),t(d,null,c(e.idles,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[en,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var tn=e({name:"puzzle-status",props:{finished:{type:Boolean,required:!0},duration:{type:Number,required:!0},piecesDone:{type:Number,required:!0},piecesTotal:{type:Number,required:!0}},computed:{icon(){return this.finished?"🏁":"⏳"},durationStr(){return E(this.duration)}}});const nn={class:"timer"};tn.render=function(e,l,i,o,s,d){return a(),t("div",nn,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),w(e.$slots,"default")])};var ln=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 on=n("td",null,[n("label",null,"Background: ")],-1),an=n("td",null,[n("label",null,"Color: ")],-1),sn=n("td",null,[n("label",null,"Name: ")],-1);ln.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("tr",null,[on,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[1]||(l[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[an,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[2]||(l[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[sn,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":l[3]||(l[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])])])])};var rn=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const dn={class:"preview"};rn.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay",onClick:l[1]||(l[1]=t=>e.$emit("bgclick"))},[n("div",dn,[n("div",{class:"img",style:e.previewStyle},null,4)])])};const cn=je("Communication.js");let un,gn=e=>{},pn=e=>{};let hn=0;const mn=e=>{hn!==e&&(hn=e,pn(e))};function yn(e){if(2===hn)try{un.send(JSON.stringify(e))}catch(t){cn.info("unable to send message.. maybe because ws is invalid?")}}let fn,wn;function vn(e,t){yn([ke,e,t])}var bn={connect:function(e,t,n){return fn=0,wn={},mn(3),new Promise((l=>{un=new WebSocket(e,n+"|"+t),un.onopen=e=>{mn(2),yn([Ce])},un.onmessage=e=>{const t=JSON.parse(e.data),i=t[0];if(i===ve){const e=t[1];l(e)}else{if(i!==we)throw`[ 2021-05-09 invalid connect msgType ${i} ]`;{const e=t[1],l=t[2];if(e===n&&wn[l])return void delete wn[l];gn(t)}}},un.onerror=e=>{throw mn(1),"[ 2021-05-15 onerror ]"},un.onclose=e=>{4e3===e.code||1001===e.code?mn(4):mn(1)}}))},connectReplay:function(e,t,n){return fn=0,wn={},mn(3),new Promise((l=>{un=new WebSocket(e,n+"|"+t),un.onopen=e=>{mn(2),vn(0,1e4)},un.onmessage=e=>{const t=JSON.parse(e.data),n=t[0];if(n!==be)throw`[ 2021-05-09 invalid connectReplay msgType ${n} ]`;{const e=t[1],n=t[2];if(null!==n){l({game:n,log:e})}else gn(t)}},un.onerror=e=>{throw mn(1),"[ 2021-05-15 onerror ]"},un.onclose=e=>{4e3===e.code||1001===e.code?mn(4):mn(1)}}))},requestReplayData:vn,disconnect:function(){un&&un.close(4e3),fn=0,wn={}},sendClientEvent:function(e){fn++,wn[fn]=e,yn([xe,fn,wn[fn]])},onServerChange:function(e){gn=e},onConnectionStateChange:function(e){pn=e},CODE_CUSTOM_DISCONNECT:4e3,CONN_STATE_NOT_CONNECTED:0,CONN_STATE_DISCONNECTED:1,CONN_STATE_CLOSED:4,CONN_STATE_CONNECTED:2,CONN_STATE_CONNECTING:3},xn=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 Cn={key:0,class:"overlay connection-lost"},kn={key:0,class:"overlay-content"},An=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Tn={key:1,class:"overlay-content"},Sn=n("div",null,"Connecting...",-1);xn.render=function(e,l,o,s,r,d){return e.show?(a(),t("div",Cn,[e.lostConnection?(a(),t("div",kn,[An,n("span",{class:"btn",onClick:l[1]||(l[1]=t=>e.$emit("reconnect"))},"Reconnect")])):i("",!0),e.connecting?(a(),t("div",Tn,[Sn])):i("",!0)])):i("",!0)};var zn=e({name:"help-overlay",emits:{bgclick:null}});const In=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),_n=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),Dn=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),En=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),On=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),Un=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Nn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Mn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1);zn.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[In,Pn,_n,Dn,Bn,En,On,Un,Nn,Mn])])};var Gn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Rn=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="}),Vn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function jn(){let e=0,t=0,n=1;const l=(l,i)=>{e+=l/n,t+=i/n},i=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},o=l=>({x:l.x/n-e,y:l.y/n-t}),a=l=>({x:(l.x+e)*n,y:(l.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n});return{move:l,canZoom:e=>n!=i(e),zoom:(e,t)=>((e,t)=>{if(n==e)return!1;const i=1-n/e;return l(-t.x*i,-t.y*i),n=e,!0})(i(e),t),worldToViewport:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:a,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=o(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:o}}function Fn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Ln={createCanvas:Fn,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=Fn(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=Fn(e.width,e.height),i=l.getContext("2d");return i.save(),i.drawImage(t,0,0),i.fillStyle=n,i.globalCompositeOperation="source-in",i.fillRect(0,0,t.width,t.height),i.restore(),i.save(),i.globalCompositeOperation="destination-over",i.drawImage(e,0,0),i.restore(),l}};const Wn=je("Debug.js");let qn=0,Hn=0;var Qn=e=>{qn=performance.now(),Hn=e},Yn=e=>{const t=performance.now(),n=t-qn;n>Hn&&Wn.log(e+": "+n),qn=t};const Kn=je("PuzzleGraphics.js");function Zn(e,t){const n=He.coordByTileIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Jn={loadPuzzleBitmaps:async function(e){const t=await Ln.loadImageToBitmap(e.info.imageUrl),n=await Ln.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Kn.log("start createPuzzleTileBitmaps");var l=n.tileSize,i=n.tileMarginWidth,o=n.tileDrawSize,a=l/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0];const r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,o={x:i,y:i},r=fe.pointAdd(o,{x:l,y:0}),c=fe.pointAdd(r,{x:0,y:l}),u=fe.pointSub(c,{x:l,y:0});if(n.moveTo(o.x,o.y),0!==e.top)for(let l=0;l=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;tl=t/2,nl=t-tl;const n=1/4*this.canvas.width/(t/2);Xn=-n,el=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new ll(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new ll(this.rng));const e=[];for(;this.explodedBombs.length>0;){const t=this.explodedBombs.shift();if(!t)break;t.update(),t.alive&&e.push(t)}this.explodedBombs=e;const t=[];for(;this.readyBombs.length>0;){const e=this.readyBombs.shift();if(!e)break;e.update(this.particles),e.hasExploded?this.explodedBombs.push(e):t.push(e)}this.readyBombs=t;const n=[];for(;this.particles.length>0;){const e=this.particles.shift();if(!e)break;e.update(),e.alive&&n.push(e)}this.particles=n}render(){this.ctx.beginPath(),this.ctx.fillStyle="rgba(0, 0, 0, 0.1)",this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height);for(let e=0;e{const t=e.color+" "+e.d;if(!h[t]){const n=e.d?a:s;if(e.color){const l=e.d?r:d;h[t]=await createImageBitmap(Ln.colorizedCanvas(n,l,e.color))}else h[t]=n}return h[t]},y=function(e,t){return t.width=window.innerWidth,t.height=window.innerHeight,e.appendChild(t),window.addEventListener("resize",(()=>{t.width=window.innerWidth,t.height=window.innerHeight,dl=!0})),t}(i,Ln.createCanvas()),f={final:!1,requesting:!0,log:[],logPointer:0,logIdx:0,speeds:[.5,1,2,5,10,20,50],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0};bn.onConnectionStateChange((e=>{o.setConnectionState(e)}));let w=()=>0;const v=async()=>{if("play"===l){const l=await bn.connect(n,e,t),i=He.decodeGame(l);Pt.setGame(i.id,i),w=()=>D()}else{if("replay"!==l)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{bn.onServerChange((e=>{const t=e[1];f.log=f.log.slice(f.logPointer),f.logPointer=0,f.log.push(...e[1]),t.length<1e4&&(f.final=!0),f.requesting=!1}));const l=await bn.connectReplay(n,e,t),i=He.decodeGame(l.game);Pt.setGame(i.id,i),f.requesting=!1,f.log=l.log,f.lastRealTs=D(),f.gameStartTs=parseInt(f.log[0][4],10),f.lastGameTs=f.gameStartTs,w=()=>f.lastGameTs}}dl=!0};await v();const b=Pt.getTileDrawOffset(e),x=Pt.getTileDrawSize(e),C=Pt.getPuzzleWidth(e),k=Pt.getPuzzleHeight(e),A=Pt.getTableWidth(e),T=Pt.getTableHeight(e),S={x:(A-C)/2,y:(T-k)/2},z={w:C,h:k},I={w:x,h:x},P=await Jn.loadPuzzleBitmaps(Pt.getPuzzle(e)),_=new ol(y,Pt.getRng(e));_.init();const B=y.getContext("2d");y.classList.add("loaded");const E=jn();E.move(-(A-y.width)/2,-(T-y.height)/2);const O=function(e,t,n){let l=[],i=!0,o=!1,a=!1,s=!1,r=!1,d=!1,c=!1,u=!1;const g=(e,t)=>{const l=n.viewportToWorld({x:e,y:t});return[l.x,l.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{i&&("Shift"===t.key?u=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?s=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?r=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?o=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?a=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};e.addEventListener("mousedown",(e=>{0===e.button&&y([Ie,...p(e)])})),e.addEventListener("mouseup",(e=>{0===e.button&&y([Pe,...p(e)])})),e.addEventListener("mousemove",(e=>{y([_e,...p(e)])})),e.addEventListener("wheel",(e=>{if(n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?De:Be;y([t,...p(e)])}})),t.addEventListener("keydown",(e=>m(!0,e))),t.addEventListener("keyup",(e=>m(!1,e))),t.addEventListener("keypress",(e=>{i&&(" "===e.key&&y([Ne]),"F"!==e.key&&"f"!==e.key||(sl=!sl,dl=!0),"G"!==e.key&&"g"!==e.key||(rl=!rl,dl=!0))}));const y=e=>{l.push(e)};return{addEvent:y,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=u?20:10,t=(o?e:0)-(a?e:0),l=(s?e:0)-(r?e:0);0===t&&0===l||y([ze,t,l]),d&&c||(d?n.canZoom("in")&&y([De,...h()]):c&&n.canZoom("out")&&y([Be,...h()]))},setHotkeys:e=>{i=e}}}(y,window,E),U=Pt.getImageUrl(e),N=()=>{const t=Pt.getStartTs(e),n=Pt.getFinishTs(e),l=w();o.setFinished(!!n),o.setDuration((n||l)-t)};N(),o.setPiecesDone(Pt.getFinishedTileCount(e)),o.setPiecesTotal(Pt.getTileCount(e));const M=w();o.setActivePlayers(Pt.getActivePlayers(e,M)),o.setIdlePlayers(Pt.getIdlePlayers(e,M));const G=!!Pt.getFinishTs(e);let R=G;const $=()=>R&&!G,V=()=>Pt.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",j=()=>Pt.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let F="",L="",W=!1;const q=e=>{W=e;const[t,n]=e?[F,"grab"]:[L,"default"];y.style.cursor=`url('${t}') ${u} ${p}, ${n}`},H=e=>{F=Ln.colorizedCanvas(a,r,e).toDataURL(),L=Ln.colorizedCanvas(s,d,e).toDataURL(),q(W)};H(j());const Q=()=>{o.setReplaySpeed&&o.setReplaySpeed(f.speeds[f.speedIdx]),o.setReplayPaused&&o.setReplayPaused(f.paused)};if("play"===l?setInterval(N,1e3):"replay"===l&&Q(),"play"===l)bn.onServerChange((n=>{n[0],n[1],n[2];const l=n[3];for(const[i,o]of l)switch(i){case Re:{const n=He.decodePlayer(o);n.id!==t&&(Pt.setPlayer(e,n.id,n),dl=!0)}break;case Ge:{const t=He.decodeTile(o);Pt.setTile(e,t.idx,t),dl=!0}break;case Me:Pt.setPuzzleData(e,o),dl=!0}R=!!Pt.getFinishTs(e)}));else if("replay"===l){let t=setInterval((()=>{const n=D();if(f.requesting)return void(f.lastRealTs=n);if(f.logPointer+1>=f.log.length)return f.lastRealTs=n,f.requesting=!0,void bn.requestReplayData(f.logIdx,1e4);if(f.paused)return void(f.lastRealTs=n);const l=(n-f.lastRealTs)*f.speeds[f.speedIdx],i=f.lastGameTs+l;for(;;){if(f.paused)break;const n=f.logPointer+1;if(n>=f.log.length){f.final&&clearInterval(t);break}const l=f.log[n],o=f.gameStartTs+l[l.length-1];if(o>i)break;const a=l.slice();if(a[0]===Ae){const t=a[1];Pt.addPlayer(e,t,o),dl=!0}else if(a[0]===Te){const t=Pt.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";Pt.addPlayer(e,t,o),dl=!0}else if(a[0]===Se){const t=Pt.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=a[2];Pt.handleInput(e,t,n,o),dl=!0}f.logPointer=n,f.logIdx++}f.lastRealTs=n,f.lastGameTs=i,N()}),50)}let Y=null;return(e=>{const t=e.fps||60,n=e.slow||1,l=e.update,i=e.render,o=window.requestAnimationFrame,a=1/t,s=n*a;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>s;)d-=s,l(a);i(d/n),c=r,o(u)};o(u)})({update:()=>{O.createKeyEvents();for(const n of O.consumeAll())if("play"===l){const l=n[0];if(l===ze){const e=n[1],t=n[2];dl=!0,E.move(e,t)}else if(l===_e){if(Y&&!Pt.getFirstOwnedTile(e,t)){const e={x:n[1],y:n[2]},t=E.worldToViewport(e),l=Math.round(t.x-Y.x),i=Math.round(t.y-Y.y);dl=!0,E.move(l,i),Y=t}}else if(l===Oe)H(n[1]);else if(l===Ie){const e={x:n[1],y:n[2]};Y=E.worldToViewport(e),q(!0)}else if(l===Pe)Y=null,q(!1);else if(l===De){const e={x:n[1],y:n[2]};dl=!0,E.zoom("in",E.worldToViewport(e))}else if(l===Be){const e={x:n[1],y:n[2]};dl=!0,E.zoom("out",E.worldToViewport(e))}else l===Ne&&o.togglePreview();const i=w();Pt.handleInput(e,t,n,i).length>0&&(dl=!0),bn.sendClientEvent(n)}else if("replay"===l){const e=n[0];if(e===ze){const e=n[1],t=n[2];dl=!0,E.move(e,t)}else if(e===_e){if(Y){const e={x:n[1],y:n[2]},t=E.worldToViewport(e),l=Math.round(t.x-Y.x),i=Math.round(t.y-Y.y);dl=!0,E.move(l,i),Y=t}}else if(e===Ie){const e={x:n[1],y:n[2]};Y=E.worldToViewport(e)}else if(e===Pe)Y=null;else if(e===De){const e={x:n[1],y:n[2]};dl=!0,E.zoom("in",E.worldToViewport(e))}else if(e===Be){const e={x:n[1],y:n[2]};dl=!0,E.zoom("out",E.worldToViewport(e))}else e===Ne&&o.togglePreview()}R=!!Pt.getFinishTs(e),$()&&(_.update(),dl=!0)},render:async()=>{if(!dl)return;const n=w();let i,a,s;window.DEBUG&&Qn(0),B.fillStyle=V(),B.fillRect(0,0,y.width,y.height),window.DEBUG&&Yn("clear done"),i=E.worldToViewportRaw(S),a=E.worldDimToViewportRaw(z),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(i.x,i.y,a.w,a.h),window.DEBUG&&Yn("board done");const r=Pt.getTilesSortedByZIndex(e);window.DEBUG&&Yn("get tiles done"),a=E.worldDimToViewportRaw(I);for(const e of r)(-1===e.owner?sl:rl)&&(s=P[e.idx],i=E.worldToViewportRaw({x:b+e.pos.x,y:b+e.pos.y}),B.drawImage(s,0,0,s.width,s.height,i.x,i.y,a.w,a.h));window.DEBUG&&Yn("tiles done");const d=[];for(const o of Pt.getActivePlayers(e,n))c=o,("replay"===l||c.id!==t)&&(s=await m(o),i=E.worldToViewport(o),B.drawImage(s,i.x-u,i.y-p),d.push([`${o.name} (${o.points})`,i.x,i.y+g]));var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,l]of d)B.fillText(e,t,l);window.DEBUG&&Yn("players done"),o.setActivePlayers(Pt.getActivePlayers(e,n)),o.setIdlePlayers(Pt.getIdlePlayers(e,n)),o.setPiecesDone(Pt.getFinishedTileCount(e)),window.DEBUG&&Yn("HUD done"),$()&&_.render(),dl=!1}}),{setHotkeys:e=>{O.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),O.addEvent([Ee,e])},onColorChange:e=>{localStorage.setItem("player_color",e),O.addEvent([Oe,e])},onNameChange:e=>{localStorage.setItem("player_name",e),O.addEvent([Ue,e])},replayOnSpeedUp:()=>{f.speedIdx+1{f.speedIdx>=1&&(f.speedIdx--,Q())},replayOnPauseToggle:()=>{f.paused=!f.paused,Q()},previewImageUrl:U,player:{background:V(),color:j(),name:Pt.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon"},disconnect:bn.disconnect,connect:v}}var ul=e({name:"game",components:{PuzzleStatus:tn,Scores:Kt,SettingsOverlay:ln,PreviewOverlay:rn,ConnectionOverlay:xn,HelpOverlay:zn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},disconnect:()=>{},connect:()=>{}}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await cl(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},setConnectionState:e=>{this.connectionState=e},togglePreview:()=>{this.toggle("preview",!1)}}))},unmounted(){this.g.disconnect()},methods:{reconnect(){this.g.connect()},toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}}});const gl={id:"game"},pl={class:"menu"},hl={class:"tabs"},ml=s("🧩 Puzzles");ul.render=function(e,i,s,r,d,c){const u=o("settings-overlay"),p=o("preview-overlay"),h=o("help-overlay"),m=o("connection-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",gl,[g(n(u,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(p,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(h,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",pl,[n("div",hl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[ml])),_:1}),n("div",{class:"opener",onClick:i[5]||(i[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var yl=e({name:"replay",components:{PuzzleStatus:tn,Scores:Kt,SettingsOverlay:ln,PreviewOverlay:rn,HelpOverlay:zn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},disconnect:()=>{}},replay:{speed:1,paused:!1}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await cl(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"replay",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},setReplaySpeed:e=>{this.replay.speed=e},setReplayPaused:e=>{this.replay.paused=e}}))},unmounted(){this.g.disconnect()},methods:{toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}},computed:{replayText(){return"Replay-Speed: "+this.replay.speed+"x"+(this.replay.paused?" Paused":"")}}});const fl={id:"replay"},wl={class:"menu"},vl={class:"tabs"},bl=s("🧩 Puzzles");yl.render=function(e,i,s,d,c,u){const p=o("settings-overlay"),h=o("preview-overlay"),m=o("help-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",fl,[g(n(p,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(m,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:l((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:i[5]||(i[5]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",wl,[n("div",vl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[bl])),_:1}),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=b({history:x(),routes:[{name:"index",path:"/",component:$},{name:"new-game",path:"/new-game",component:$t},{name:"game",path:"/g/:id",component:ul},{name:"replay",path:"/replay/:id",component:yl}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const l=C(k);l.config.globalProperties.$config=t,l.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=He.uniqId(),localStorage.setItem("ID",e)),e}(),l.use(n),l.mount("#app")})(); diff --git a/build/public/assets/index.50ee8245.js b/build/public/assets/index.50ee8245.js deleted file mode 100644 index ba9ec2e..0000000 --- a/build/public/assets/index.50ee8245.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as n,w as l,b as i,r as o,o as a,e as s,t as r,F as d,f as c,g as u,h as g,v as p,i as h,j as m,k as y,l as f,m as w,n as v,p as b,q as x,s as C}from"./vendor.b622ee49.js";var k=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const A={id:"app"},T={key:0,class:"nav"},S=s("Index"),z=s("New game");k.render=function(e,s,r,d,c,u){const g=o("router-link"),p=o("router-view");return a(),t("div",A,[e.showNav?(a(),t("ul",T,[n("li",null,[n(g,{class:"btn",to:{name:"index"}},{default:l((()=>[S])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:l((()=>[z])),_:1})])])):i("",!0),n(p)])};const I=864e5,P=e=>{const t=Math.floor(e/I);e%=I;const n=Math.floor(e/36e5);e%=36e5;const l=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${l}m ${Math.floor(e/1e3)}s`};var _=1e3,D=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>P(t-e),E=P,O=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",l=e,i=t||D();return`${n} ${B(l,i)}`}}});const U={class:"game-info-text"},N=n("br",null,null,-1),M=n("br",null,null,-1),G=n("br",null,null,-1);O.render=function(e,d,c,u,g,p){const h=o("router-link");return a(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:l((()=>[n("span",U,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),N,s(" 👥 "+r(e.game.players),1),M,s(" "+r(e.time(e.game.started,e.game.finished)),1),G])])),_:1},8,["to"]),i("",!0)],4)};var $=e({components:{GameTeaser:O},data:()=>({gamesRunning:[],gamesFinished:[]}),async created(){const e=await fetch("/api/index-data"),t=await e.json();this.gamesRunning=t.gamesRunning,this.gamesFinished=t.gamesFinished}});const R=n("h1",null,"Running games",-1),V=n("h1",null,"Finished games",-1);$.render=function(e,l,i,s,r,u){const g=o("game-teaser");return a(),t("div",null,[R,(a(!0),t(d,null,c(e.gamesRunning,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128)),V,(a(!0),t(d,null,c(e.gamesFinished,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128))])};var j=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")}}});j.render=function(e,l,i,o,s,r){return a(),t("div",{class:"imageteaser",style:e.style,onClick:l[2]||(l[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:l[1]||(l[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var F=e({name:"image-library",components:{ImageTeaser:j},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});F.render=function(e,n,l,i,s,r){const u=o("image-teaser");return a(),t("div",null,[(a(!0),t(d,null,c(e.images,((n,l)=>(a(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:l},null,8,["image","onClick","onEditClick"])))),128))])};const L={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}}}};L.render=function(e,n,l,i,o,s){return a(),t("div",{style:s.style,title:l.title},null,12,["title"])};var W=e({name:"tags-input",props:{modelValue:{type:Array,required:!0}},emits:{"update:modelValue":null},data:()=>({input:"",values:[]}),created(){this.values=this.modelValue},methods:{onKeyUp(e){if(","===e.key)return this.add(),e.stopPropagation(),!1},add(){const e=this.input.replace(/,/g,"").trim();e&&(this.values.includes(e)||this.values.push(e),this.input="",this.$emit("update:modelValue",this.values))},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const H=m()(((e,l,i,o,s,u)=>(a(),t("div",null,[g(n("input",{class:"input",type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.input=t),placeholder:"Plants, People",onKeydown:l[2]||(l[2]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:l[3]||(l[3]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[p,e.input]]),(a(!0),t(d,null,c(e.values,((n,l)=>(a(),t("span",{key:l,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));W.render=H,W.__scopeId="data-v-771460ae";var Q=e({name:"new-image-dialog",components:{ResponsiveImage:L,TagsInput:W},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const l=new FileReader;l.readAsDataURL(n),l.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const Y={key:0,class:"has-image"},q={key:1},K={class:"upload"},Z=n("span",{class:"btn"},"Upload File",-1),J={class:"area-settings"},X=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),te=n("td",null,[n("label",null,"Tags")],-1),ne={class:"area-buttons"},le=s("🧩 Post to gallery "),ie=n("br",null,null,-1),oe=s(" + set up game");Q.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay new-image-dialog",onClick:l[8]||(l[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[7]||(l[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(a(),t("div",Y,[n("span",{class:"remove btn",onClick:l[1]||(l[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(a(),t("div",q,[n("label",K,[n("input",{type:"file",style:{display:"none"},onChange:l[2]||(l[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),Z])]))],2),n("div",J,[n("table",null,[n("tr",null,[X,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[3]||(l[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ee,n("tr",null,[te,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[4]||(l[4]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",ne,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:l[5]||(l[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:l[6]||(l[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[le,ie,oe],8,["disabled"])])])])};var ae=e({name:"edit-image-dialog",components:{ResponsiveImage:L,TagsInput:W},props:{image:{type:Object,required:!0}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const se={class:"area-image"},re={class:"has-image"},de={class:"area-settings"},ce=n("td",null,[n("label",null,"Title")],-1),ue=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),ge=n("td",null,[n("label",null,"Tags")],-1),pe={class:"area-buttons"};function he(e,t){const n=e.x-t.x,l=e.y-t.y;return Math.sqrt(n*n+l*l)}function me(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}ae.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay edit-image-dialog",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("div",se,[n("div",re,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",de,[n("table",null,[n("tr",null,[ce,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ue,n("tr",null,[ge,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[2]||(l[2]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",pe,[n("button",{class:"btn",onClick:l[3]||(l[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var ye={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:he,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:me,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 he(me(e),me(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};var fe=1,we=4,ve=5,be=2,xe=3,Ce=6,ke=2,Ae=4,Te=3,Se=9,ze=1,Ie=2,Pe=3,_e=4,De=5,Be=6,Ee=7,Oe=8,Ue=10,Ne=1,Me=2,Ge=3;class $e{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){return this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295,e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),l=t[n];t[n]=t[e],t[e]=l}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new $e(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const Re=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},Ve=(...e)=>{const t=t=>(...n)=>{const l=new Date,i=Re(l.getHours(),"00"),o=Re(l.getMinutes(),"00"),a=Re(l.getSeconds(),"00");console[t](`${i}:${o}:${a}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var je,Fe,Le,We,He={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}},encodeTile:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodeTile:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return Array.isArray(e)?e:[e.id,e.rng.type,$e.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode]},decodeGame:function(e){return Array.isArray(e)?{id:e[0],rng:{type:e[1],obj:$e.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}:e},coordByTileIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(let n in e){const l=[n,e[n]].map(encodeURIComponent);t.push(l.join("="))}return 0===t.length?"":`?${t.join("&")}`}};(Fe=je||(je={}))[Fe.Flat=0]="Flat",Fe[Fe.Out=1]="Out",Fe[Fe.In=-1]="In",(We=Le||(Le={}))[We.FINAL=0]="FINAL",We[We.ANY=1]="ANY";const Qe={};function Ye(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}function qe(e,t){let n=0;for(let l of Qe[e].players){if(He.decodePlayer(l).id===t)return n;n++}return-1}function Ke(e,t){const n=qe(e,t);return He.decodePlayer(Qe[e].players[n])}function Ze(e,t,n){const l=qe(e,t);-1===l?Qe[e].players.push(He.encodePlayer(n)):Qe[e].players[l]=He.encodePlayer(n)}function Je(e,t){return-1!==qe(e,t)}function Xe(e){return Qe[e]?Qe[e].players.map(He.decodePlayer):[]}function et(e){return Qe[e].puzzle.tiles.length}function tt(e){return Qe[e].scoreMode||0}function nt(e){return lt(e)===et(e)}function lt(e){let t=0;for(let n of Qe[e].puzzle.tiles)-1===He.decodeTile(n).owner&&t++;return t}function it(e,t,n){const l=Ke(e,t);for(let i of Object.keys(n))l[i]=n[i];Ze(e,t,l)}function ot(e,t){for(let n of Object.keys(t))Qe[e].puzzle.data[n]=t[n]}function at(e,t,n){for(let l of Object.keys(n)){const i=He.decodeTile(Qe[e].puzzle.tiles[t]);i[l]=n[l],Qe[e].puzzle.tiles[t]=He.encodeTile(i)}}const st=(e,t)=>He.decodeTile(Qe[e].puzzle.tiles[t]),rt=(e,t)=>st(e,t).group,dt=(e,t)=>{const n=Qe[e].puzzle.info,l={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},i=function(e,t){const n=Qe[e].puzzle.info,l=He.coordByTileIdx(n,t),i=l.x*n.tileSize,o=l.y*n.tileSize;return{x:i,y:o}}(e,t);return ye.pointAdd(l,i)},ct=(e,t)=>st(e,t).pos,ut=e=>{const t=St(e),n=zt(e),l=Math.round(t/4),i=Math.round(n/4);return{x:0-l,y:0-i,w:t+2*l,h:n+2*i}},gt=(e,t)=>{const n=yt(e),l=st(e,t);return{x:l.pos.x,y:l.pos.y,w:n,h:n}},pt=(e,t)=>st(e,t).z,ht=(e,t)=>{for(let n of Qe[e].puzzle.tiles){const e=He.decodeTile(n);if(e.owner===t)return e.idx}return-1},mt=e=>Qe[e].puzzle.info.tileDrawSize,yt=e=>Qe[e].puzzle.info.tileSize,ft=e=>Qe[e].puzzle.data.maxGroup,wt=e=>Qe[e].puzzle.data.maxZ;function vt(e,t){const n=Qe[e].puzzle.info,l=He.coordByTileIdx(n,t);return[l.y>0?t-n.tilesX:-1,l.x0?t-1:-1]}const bt=(e,t,n)=>{for(let l of t)at(e,l,{z:n})},xt=(e,t,n)=>{const l=ct(e,t);at(e,t,{pos:ye.pointAdd(l,n)})},Ct=(e,t,n)=>{const l=mt(e),i=ut(e),o=n;for(let a of t){const t=st(e,a);t.pos.x+n.xi.x+i.w&&(o.x=Math.min(i.x+i.w-t.pos.x+l,o.x)),t.pos.y+n.yi.y+i.h&&(o.y=Math.min(i.y+i.h-t.pos.y+l,o.y))}for(let a of t)xt(e,a,o)},kt=(e,t,n)=>{for(let l of t)at(e,l,{owner:n})};function At(e,t){const n=Qe[e].puzzle.tiles,l=He.decodeTile(n[t]),i=[];if(l.group)for(let o of n){const e=He.decodeTile(o);e.group===l.group&&i.push(e.idx)}else i.push(l.idx);return i}const Tt=(e,t)=>{const n=Ke(e,t);return n?n.points:0},St=e=>Qe[e].puzzle.info.table.width,zt=e=>Qe[e].puzzle.info.table.height;var It={__createPlayerObject:Ye,setGame:function(e,t){Qe[e]=t},exists:function(e){return!!Qe[e]||!1},playerExists:Je,getActivePlayers:function(e,t){const n=t-30*_;return Xe(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return Xe(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Je(e,t)?it(e,t,{ts:n}):Ze(e,t,Ye(t,n))},getFinishedTileCount:lt,getTileCount:et,getImageUrl:function(e){return Qe[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Qe[e].puzzle.info.imageUrl=t},get:function(e){return Qe[e]},getAllGames:function(){return Object.values(Qe).sort(((e,t)=>nt(e.id)===nt(t.id)?t.puzzle.data.started-e.puzzle.data.started:nt(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Ke(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Ke(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Ke(e,t);return n?n.name:null},getPlayerIndexById:qe,getPlayerIdByIndex:function(e,t){return Qe[e].players.length>t?He.decodePlayer(Qe[e].players[t]).id:null},changePlayer:it,setPlayer:Ze,setTile:function(e,t,n){Qe[e].puzzle.tiles[t]=He.encodeTile(n)},setPuzzleData:function(e,t){Qe[e].puzzle.data=t},getTableWidth:St,getTableHeight:zt,getPuzzle:e=>Qe[e].puzzle,getRng:e=>Qe[e].rng.obj,getPuzzleWidth:e=>Qe[e].puzzle.info.width,getPuzzleHeight:e=>Qe[e].puzzle.info.height,getTilesSortedByZIndex:function(e){return Qe[e].puzzle.tiles.map(He.decodeTile).sort(((e,t)=>e.z-t.z))},getFirstOwnedTile:(e,t)=>{const n=ht(e,t);return n<0?null:Qe[e].puzzle.tiles[n]},getTileDrawOffset:e=>Qe[e].puzzle.info.tileDrawOffset,getTileDrawSize:mt,getFinalTilePos:dt,getStartTs:e=>Qe[e].puzzle.data.started,getFinishTs:e=>Qe[e].puzzle.data.finished,handleInput:function(e,t,n,l){const i=Qe[e].puzzle,o=function(e,t){return t in Qe[e].evtInfos?Qe[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),a=[],s=()=>{a.push([Ne,i.data])},r=t=>{a.push([Me,He.encodeTile(st(e,t))])},d=e=>{for(const t of e)r(t)},c=()=>{a.push([Ge,He.encodePlayer(Ke(e,t))])},u=n[0];if(u===Be){const i=n[1];it(e,t,{bgcolor:i,ts:l}),c()}else if(u===Ee){const i=n[1];it(e,t,{color:i,ts:l}),c()}else if(u===Oe){const i=`${n[1]}`.substr(0,16);it(e,t,{name:i,ts:l}),c()}else if(u===ze){const i={x:n[1],y:n[2]};it(e,t,{d:1,ts:l}),c(),o._last_mouse_down=i;const a=((e,t)=>{let n=Qe[e].puzzle.info,l=Qe[e].puzzle.tiles,i=-1,o=-1;for(let a=0;ai)&&(i=e.z,o=a)}return o})(e,i);if(a>=0){let n=wt(e)+1;ot(e,{maxZ:n}),s();const l=At(e,a);bt(e,l,wt(e)),kt(e,l,t),d(l)}o._last_mouse=i}else if(u===Pe){const i=n[1],a=n[2],s={x:i,y:a};if(null===o._last_mouse_down)it(e,t,{x:i,y:a,ts:l}),c();else{let n=ht(e,t);if(n>=0){it(e,t,{x:i,y:a,ts:l}),c();const r=At(e,n);let u=ye.pointInBounds(s,ut(e))&&ye.pointInBounds(o._last_mouse_down,ut(e));for(let t of r){const n=gt(e,t);if(ye.pointInBounds(s,n)){u=!0;break}}if(u){const t=i-o._last_mouse_down.x,n=a-o._last_mouse_down.y;Ct(e,r,{x:t,y:n}),d(r)}}else it(e,t,{ts:l}),c();o._last_mouse_down=s}o._last_mouse=s}else if(u===Ie){const a={x:n[1],y:n[2]},u=0;o._last_mouse_down=null;let g=ht(e,t);if(g>=0){let n=At(e,g);kt(e,n,0),d(n);let o=ct(e,g),a=dt(e,g);if(ye.pointDistance(a,o){for(let n of t)at(e,n,{owner:-1,z:1})})(e,n),d(n);let r=Tt(e,t);0===tt(e)?r+=n.length:1===tt(e)&&(r+=1),it(e,t,{d:u,ts:l,points:r}),c(),lt(e)===et(e)&&(ot(e,{finished:l}),s())}else{const n=(e,t,n,l)=>{let i=Qe[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const l=rt(e,t),i=rt(e,n);return!(!l||l!==i)})(e,t,n))return!1;const o=ct(e,t),a=ye.pointAdd(ct(e,n),{x:l[0]*i.tileSize,y:l[1]*i.tileSize});if(ye.pointDistance(o,a){const l=Qe[e].puzzle.tiles,i=rt(e,t),o=rt(e,n);let a;const d=[];i&&d.push(i),o&&d.push(o),i?a=i:o?a=o:(ot(e,{maxGroup:ft(e)+1}),s(),a=ft(e));if(at(e,t,{group:a}),r(t),at(e,n,{group:a}),r(n),d.length>0)for(const s of l){const t=He.decodeTile(s);d.includes(t.group)&&(at(e,t.idx,{group:a}),r(t.idx))}})(e,t,n),i=At(e,t);const c=((e,t)=>{let n=0;for(let l of t){let t=pt(e,l);t>n&&(n=t)}return n})(e,i);return bt(e,i,c),d(i),!0}return!1};let i=!1;for(let t of At(e,g)){let l=vt(e,t);if(n(e,t,l[0],[0,1])||n(e,t,l[1],[-1,0])||n(e,t,l[2],[0,-1])||n(e,t,l[3],[1,0])){i=!0;break}}if(i&&1===tt(e)){const n=Tt(e,t)+1;it(e,t,{d:u,ts:l,points:n}),c()}else it(e,t,{d:u,ts:l}),c()}}else it(e,t,{d:u,ts:l}),c();o._last_mouse=a}else if(u===_e){const i=n[1],a=n[2];it(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else if(u===De){const i=n[1],a=n[2];it(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else it(e,t,{ts:l}),c();return function(e,t,n){Qe[e].evtInfos[t]=n}(e,t,o),a}},Pt=e({name:"new-game-dialog",components:{ResponsiveImage:L},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Le.ANY}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const _t={class:"area-image"},Dt={class:"has-image"},Bt={class:"area-settings"},Et=n("td",null,[n("label",null,"Pieces")],-1),Ot=n("td",null,[n("label",null,"Scoring: ")],-1),Ut=s(" Any (Score when pieces are connected to each other or on final location)"),Nt=n("br",null,null,-1),Mt=s(" Final (Score when pieces are put to their final location)"),Gt={class:"area-buttons"};Pt.render=function(e,l,i,s,r,d){const c=o("responsive-image");return a(),t("div",{class:"overlay new-game-dialog",onClick:l[6]||(l[6]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[5]||(l[5]=u((()=>{}),["stop"]))},[n("div",_t,[n("div",Dt,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Bt,[n("table",null,[n("tr",null,[Et,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Ot,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[2]||(l[2]=t=>e.scoreMode=t),value:"1"},null,512),[[y,e.scoreMode]]),Ut]),Nt,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[3]||(l[3]=t=>e.scoreMode=t),value:"0"},null,512),[[y,e.scoreMode]]),Mt])])])])]),n("div",Gt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:l[4]||(l[4]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var $t=e({components:{ImageLibrary:F,NewImageDialog:Q,EditImageDialog:ae,NewGameDialog:Pt},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},methods:{toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${He.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const Rt={class:"upload-image-teaser"},Vt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),jt={key:0},Ft=s(" Tags: "),Lt=s(" Sort by: "),Wt=n("option",{value:"date_desc"},"Newest first",-1),Ht=n("option",{value:"date_asc"},"Oldest first",-1),Qt=n("option",{value:"alpha_asc"},"A-Z",-1),Yt=n("option",{value:"alpha_desc"},"Z-A",-1);$t.render=function(e,l,s,u,p,h){const m=o("image-library"),y=o("new-image-dialog"),w=o("edit-image-dialog"),v=o("new-game-dialog");return a(),t("div",null,[n("div",Rt,[n("div",{class:"btn btn-big",onClick:l[1]||(l[1]=t=>e.dialog="new-image")},"Upload your image"),Vt]),n("div",null,[e.tags.length>0?(a(),t("label",jt,[Ft,(a(!0),t(d,null,c(e.tags,((n,l)=>(a(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:l,onClick:t=>e.toggleTag(n)},r(n.title),11,["onClick"])))),128))])):i("",!0),n("label",null,[Lt,g(n("select",{"onUpdate:modelValue":l[2]||(l[2]=t=>e.filters.sort=t),onChange:l[3]||(l[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[Wt,Ht,Qt,Yt],544),[[f,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(a(),t(y,{key:0,onBgclick:l[4]||(l[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["onPostToGalleryClick","onSetupGameClick"])):i("",!0),"edit-image"===e.dialog?(a(),t(w,{key:1,onBgclick:l[5]||(l[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["onSaveClick","image"])):i("",!0),e.image&&"new-game"===e.dialog?(a(),t(v,{key:2,onBgclick:l[6]||(l[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):i("",!0)])};var qt=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"},Zt=n("div",null,"Scores",-1),Jt=n("td",null,"⚡",-1),Xt=n("td",null,"💤",-1);qt.render=function(e,l,i,o,s,u){return a(),t("div",Kt,[Zt,n("table",null,[(a(!0),t(d,null,c(e.actives,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[Jt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(a(!0),t(d,null,c(e.idles,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[Xt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var en=e({name:"puzzle-status",props:{finished:{type:Boolean,required:!0},duration:{type:Number,required:!0},piecesDone:{type:Number,required:!0},piecesTotal:{type:Number,required:!0}},computed:{icon(){return this.finished?"🏁":"⏳"},durationStr(){return E(this.duration)}}});const tn={class:"timer"};en.render=function(e,l,i,o,s,d){return a(),t("div",tn,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),w(e.$slots,"default")])};var nn=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 ln=n("td",null,[n("label",null,"Background: ")],-1),on=n("td",null,[n("label",null,"Color: ")],-1),an=n("td",null,[n("label",null,"Name: ")],-1);nn.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("tr",null,[ln,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[1]||(l[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[on,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[2]||(l[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[an,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":l[3]||(l[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])])])])};var sn=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const rn={class:"preview"};sn.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay",onClick:l[1]||(l[1]=t=>e.$emit("bgclick"))},[n("div",rn,[n("div",{class:"img",style:e.previewStyle},null,4)])])};const dn=Ve("Communication.js");let cn,un=e=>{},gn=e=>{};let pn=0;const hn=e=>{pn!==e&&(pn=e,gn(e))};function mn(e){if(2===pn)try{cn.send(JSON.stringify(e))}catch(t){dn.info("unable to send message.. maybe because ws is invalid?")}}let yn,fn;var wn={connect:function(e,t,n){return yn=0,fn={},hn(3),new Promise((l=>{cn=new WebSocket(e,n+"|"+t),cn.onopen=e=>{hn(2),mn([xe])},cn.onmessage=e=>{const t=JSON.parse(e.data),i=t[0];if(i===we){const e=t[1];l(e)}else{if(i!==fe)throw`[ 2021-05-09 invalid connect msgType ${i} ]`;{const e=t[1],l=t[2];if(e===n&&fn[l])return void delete fn[l];un(t)}}},cn.onerror=e=>{throw hn(1),"[ 2021-05-15 onerror ]"},cn.onclose=e=>{4e3===e.code||1001===e.code?hn(4):hn(1)}}))},connectReplay:function(e,t,n){return yn=0,fn={},hn(3),new Promise((l=>{cn=new WebSocket(e,n+"|"+t),cn.onopen=e=>{hn(2),mn([Ce])},cn.onmessage=e=>{const t=JSON.parse(e.data),n=t[0];if(n!==ve)throw`[ 2021-05-09 invalid connectReplay msgType ${n} ]`;{const e=t[1],n=t[2];l({game:e,log:n})}},cn.onerror=e=>{throw hn(1),"[ 2021-05-15 onerror ]"},cn.onclose=e=>{4e3===e.code||1001===e.code?hn(4):hn(1)}}))},disconnect:function(){cn&&cn.close(4e3),yn=0,fn={}},sendClientEvent:function(e){yn++,fn[yn]=e,mn([be,yn,fn[yn]])},onServerChange:function(e){un=e},onConnectionStateChange:function(e){gn=e},CODE_CUSTOM_DISCONNECT:4e3,CONN_STATE_NOT_CONNECTED:0,CONN_STATE_DISCONNECTED:1,CONN_STATE_CLOSED:4,CONN_STATE_CONNECTED:2,CONN_STATE_CONNECTING:3},vn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===wn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===wn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const bn={key:0,class:"overlay connection-lost"},xn={key:0,class:"overlay-content"},Cn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),kn={key:1,class:"overlay-content"},An=n("div",null,"Connecting...",-1);vn.render=function(e,l,o,s,r,d){return e.show?(a(),t("div",bn,[e.lostConnection?(a(),t("div",xn,[Cn,n("span",{class:"btn",onClick:l[1]||(l[1]=t=>e.$emit("reconnect"))},"Reconnect")])):i("",!0),e.connecting?(a(),t("div",kn,[An])):i("",!0)])):i("",!0)};var Tn=e({name:"help-overlay",emits:{bgclick:null}});const Sn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),zn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),In=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),Pn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),_n=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Dn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),Bn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),En=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,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Un=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1);Tn.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[Sn,zn,In,Pn,_n,Dn,Bn,En,On,Un])])};var Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Mn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Gn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),$n=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Rn(){let e=0,t=0,n=1;const l=(l,i)=>{e+=l/n,t+=i/n},i=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},o=l=>({x:l.x/n-e,y:l.y/n-t}),a=l=>({x:(l.x+e)*n,y:(l.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n});return{move:l,canZoom:e=>n!=i(e),zoom:(e,t)=>((e,t)=>{if(n==e)return!1;const i=1-n/e;return l(-t.x*i,-t.y*i),n=e,!0})(i(e),t),worldToViewport:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:a,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=o(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:o}}function Vn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var jn={createCanvas:Vn,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=Vn(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=Vn(e.width,e.height),i=l.getContext("2d");return i.save(),i.drawImage(t,0,0),i.fillStyle=n,i.globalCompositeOperation="source-in",i.fillRect(0,0,t.width,t.height),i.restore(),i.save(),i.globalCompositeOperation="destination-over",i.drawImage(e,0,0),i.restore(),l}};const Fn=Ve("Debug.js");let Ln=0,Wn=0;var Hn=e=>{Ln=performance.now(),Wn=e},Qn=e=>{const t=performance.now(),n=t-Ln;n>Wn&&Fn.log(e+": "+n),Ln=t};const Yn=Ve("PuzzleGraphics.js");function qn(e,t){const n=He.coordByTileIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Kn={loadPuzzleBitmaps:async function(e){const t=await jn.loadImageToBitmap(e.info.imageUrl),n=await jn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Yn.log("start createPuzzleTileBitmaps");var l=n.tileSize,i=n.tileMarginWidth,o=n.tileDrawSize,a=l/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0];const r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,o={x:i,y:i},r=ye.pointAdd(o,{x:l,y:0}),c=ye.pointAdd(r,{x:0,y:l}),u=ye.pointSub(c,{x:l,y:0});if(n.moveTo(o.x,o.y),0!==e.top)for(let l=0;l=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Xn=t/2,el=t-Xn;const n=1/4*this.canvas.width/(t/2);Zn=-n,Jn=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new tl(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new tl(this.rng));const e=[];for(;this.explodedBombs.length>0;){const t=this.explodedBombs.shift();if(!t)break;t.update(),t.alive&&e.push(t)}this.explodedBombs=e;const t=[];for(;this.readyBombs.length>0;){const e=this.readyBombs.shift();if(!e)break;e.update(this.particles),e.hasExploded?this.explodedBombs.push(e):t.push(e)}this.readyBombs=t;const n=[];for(;this.particles.length>0;){const e=this.particles.shift();if(!e)break;e.update(),e.alive&&n.push(e)}this.particles=n}render(){this.ctx.beginPath(),this.ctx.fillStyle="rgba(0, 0, 0, 0.1)",this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height);for(let e=0;e{const t=e.color+" "+e.d;if(!h[t]){const n=e.d?a:s;if(e.color){const l=e.d?r:d;h[t]=await createImageBitmap(jn.colorizedCanvas(n,l,e.color))}else h[t]=n}return h[t]},y=function(e,t){return t.width=window.innerWidth,t.height=window.innerHeight,e.appendChild(t),window.addEventListener("resize",(()=>{t.width=window.innerWidth,t.height=window.innerHeight,sl=!0})),t}(i,jn.createCanvas()),f={log:[],logIdx:0,speeds:[.5,1,2,5,10,20,50],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0};wn.onConnectionStateChange((e=>{o.setConnectionState(e)}));let w=()=>0;const v=async()=>{if("play"===l){const l=await wn.connect(n,e,t),i=He.decodeGame(l);It.setGame(i.id,i),w=()=>D()}else{if("replay"!==l)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const l=await wn.connectReplay(n,e,t),i=He.decodeGame(l.game);It.setGame(i.id,i),f.log=l.log,f.lastRealTs=D(),f.gameStartTs=parseInt(f.log[0][f.log[0].length-2],10),f.lastGameTs=f.gameStartTs,w=()=>f.lastGameTs}}sl=!0};await v();const b=It.getTileDrawOffset(e),x=It.getTileDrawSize(e),C=It.getPuzzleWidth(e),k=It.getPuzzleHeight(e),A=It.getTableWidth(e),T=It.getTableHeight(e),S={x:(A-C)/2,y:(T-k)/2},z={w:C,h:k},I={w:x,h:x},P=await Kn.loadPuzzleBitmaps(It.getPuzzle(e)),_=new ll(y,It.getRng(e));_.init();const B=y.getContext("2d");y.classList.add("loaded");const E=Rn();E.move(-(A-y.width)/2,-(T-y.height)/2);const O=function(e,t,n){let l=[],i=!0,o=!1,a=!1,s=!1,r=!1,d=!1,c=!1,u=!1;const g=(e,t)=>{const l=n.viewportToWorld({x:e,y:t});return[l.x,l.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{i&&("Shift"===t.key?u=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?s=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?r=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?o=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?a=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};e.addEventListener("mousedown",(e=>{0===e.button&&y([ze,...p(e)])})),e.addEventListener("mouseup",(e=>{0===e.button&&y([Ie,...p(e)])})),e.addEventListener("mousemove",(e=>{y([Pe,...p(e)])})),e.addEventListener("wheel",(e=>{if(n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?_e:De;y([t,...p(e)])}})),t.addEventListener("keydown",(e=>m(!0,e))),t.addEventListener("keyup",(e=>m(!1,e))),t.addEventListener("keypress",(e=>{i&&(" "===e.key&&y([Ue]),"F"!==e.key&&"f"!==e.key||(ol=!ol,sl=!0),"G"!==e.key&&"g"!==e.key||(al=!al,sl=!0))}));const y=e=>{l.push(e)};return{addEvent:y,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=u?20:10,t=(o?e:0)-(a?e:0),l=(s?e:0)-(r?e:0);0===t&&0===l||y([Se,t,l]),d&&c||(d?n.canZoom("in")&&y([_e,...h()]):c&&n.canZoom("out")&&y([De,...h()]))},setHotkeys:e=>{i=e}}}(y,window,E),U=It.getImageUrl(e),N=()=>{const t=It.getStartTs(e),n=It.getFinishTs(e),l=w();o.setFinished(!!n),o.setDuration((n||l)-t)};N(),o.setPiecesDone(It.getFinishedTileCount(e)),o.setPiecesTotal(It.getTileCount(e));const M=w();o.setActivePlayers(It.getActivePlayers(e,M)),o.setIdlePlayers(It.getIdlePlayers(e,M));const G=!!It.getFinishTs(e);let $=G;const R=()=>$&&!G,V=()=>It.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",j=()=>It.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let F="",L="",W=!1;const H=e=>{W=e;const[t,n]=e?[F,"grab"]:[L,"default"];y.style.cursor=`url('${t}') ${u} ${p}, ${n}`},Q=e=>{F=jn.colorizedCanvas(a,r,e).toDataURL(),L=jn.colorizedCanvas(s,d,e).toDataURL(),H(W)};Q(j());const Y=()=>{o.setReplaySpeed&&o.setReplaySpeed(f.speeds[f.speedIdx]),o.setReplayPaused&&o.setReplayPaused(f.paused)};if("play"===l?setInterval(N,1e3):"replay"===l&&Y(),"play"===l)wn.onServerChange((n=>{n[0],n[1],n[2];const l=n[3];for(const[i,o]of l)switch(i){case Ge:{const n=He.decodePlayer(o);n.id!==t&&(It.setPlayer(e,n.id,n),sl=!0)}break;case Me:{const t=He.decodeTile(o);It.setTile(e,t.idx,t),sl=!0}break;case Ne:It.setPuzzleData(e,o),sl=!0}$=!!It.getFinishTs(e)}));else if("replay"===l){let t=setInterval((()=>{const n=D();if(f.paused)return void(f.lastRealTs=n);const l=(n-f.lastRealTs)*f.speeds[f.speedIdx],i=f.lastGameTs+l;for(;;){if(f.paused)break;const n=f.logIdx+1;if(n>=f.log.length){clearInterval(t);break}const l=f.log[n],o=f.gameStartTs+l[l.length-1];if(o>i)break;const a=l.slice();if(a[0]===ke){const t=a[1];It.addPlayer(e,t,o),sl=!0}else if(a[0]===Ae){const t=It.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";It.addPlayer(e,t,o),sl=!0}else if(a[0]===Te){const t=It.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=a[2];It.handleInput(e,t,n,o),sl=!0}f.logIdx=n}f.lastRealTs=n,f.lastGameTs=i,N()}),50)}let q=null;return(e=>{const t=e.fps||60,n=e.slow||1,l=e.update,i=e.render,o=window.requestAnimationFrame,a=1/t,s=n*a;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>s;)d-=s,l(a);i(d/n),c=r,o(u)};o(u)})({update:()=>{O.createKeyEvents();for(const n of O.consumeAll())if("play"===l){const l=n[0];if(l===Se){const e=n[1],t=n[2];sl=!0,E.move(e,t)}else if(l===Pe){if(q&&!It.getFirstOwnedTile(e,t)){const e={x:n[1],y:n[2]},t=E.worldToViewport(e),l=Math.round(t.x-q.x),i=Math.round(t.y-q.y);sl=!0,E.move(l,i),q=t}}else if(l===Ee)Q(n[1]);else if(l===ze){const e={x:n[1],y:n[2]};q=E.worldToViewport(e),H(!0)}else if(l===Ie)q=null,H(!1);else if(l===_e){const e={x:n[1],y:n[2]};sl=!0,E.zoom("in",E.worldToViewport(e))}else if(l===De){const e={x:n[1],y:n[2]};sl=!0,E.zoom("out",E.worldToViewport(e))}else l===Ue&&o.togglePreview();const i=w();It.handleInput(e,t,n,i).length>0&&(sl=!0),wn.sendClientEvent(n)}else if("replay"===l){const e=n[0];if(e===Se){const e=n[1],t=n[2];sl=!0,E.move(e,t)}else if(e===Pe){if(q){const e={x:n[1],y:n[2]},t=E.worldToViewport(e),l=Math.round(t.x-q.x),i=Math.round(t.y-q.y);sl=!0,E.move(l,i),q=t}}else if(e===ze){const e={x:n[1],y:n[2]};q=E.worldToViewport(e)}else if(e===Ie)q=null;else if(e===_e){const e={x:n[1],y:n[2]};sl=!0,E.zoom("in",E.worldToViewport(e))}else if(e===De){const e={x:n[1],y:n[2]};sl=!0,E.zoom("out",E.worldToViewport(e))}else e===Ue&&o.togglePreview()}$=!!It.getFinishTs(e),R()&&(_.update(),sl=!0)},render:async()=>{if(!sl)return;const n=w();let i,a,s;window.DEBUG&&Hn(0),B.fillStyle=V(),B.fillRect(0,0,y.width,y.height),window.DEBUG&&Qn("clear done"),i=E.worldToViewportRaw(S),a=E.worldDimToViewportRaw(z),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(i.x,i.y,a.w,a.h),window.DEBUG&&Qn("board done");const r=It.getTilesSortedByZIndex(e);window.DEBUG&&Qn("get tiles done"),a=E.worldDimToViewportRaw(I);for(const e of r)(-1===e.owner?ol:al)&&(s=P[e.idx],i=E.worldToViewportRaw({x:b+e.pos.x,y:b+e.pos.y}),B.drawImage(s,0,0,s.width,s.height,i.x,i.y,a.w,a.h));window.DEBUG&&Qn("tiles done");const d=[];for(const o of It.getActivePlayers(e,n))c=o,("replay"===l||c.id!==t)&&(s=await m(o),i=E.worldToViewport(o),B.drawImage(s,i.x-u,i.y-p),d.push([`${o.name} (${o.points})`,i.x,i.y+g]));var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,l]of d)B.fillText(e,t,l);window.DEBUG&&Qn("players done"),o.setActivePlayers(It.getActivePlayers(e,n)),o.setIdlePlayers(It.getIdlePlayers(e,n)),o.setPiecesDone(It.getFinishedTileCount(e)),window.DEBUG&&Qn("HUD done"),R()&&_.render(),sl=!1}}),{setHotkeys:e=>{O.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),O.addEvent([Be,e])},onColorChange:e=>{localStorage.setItem("player_color",e),O.addEvent([Ee,e])},onNameChange:e=>{localStorage.setItem("player_name",e),O.addEvent([Oe,e])},replayOnSpeedUp:()=>{f.speedIdx+1{f.speedIdx>=1&&(f.speedIdx--,Y())},replayOnPauseToggle:()=>{f.paused=!f.paused,Y()},previewImageUrl:U,player:{background:V(),color:j(),name:It.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon"},disconnect:wn.disconnect,connect:v}}var dl=e({name:"game",components:{PuzzleStatus:en,Scores:qt,SettingsOverlay:nn,PreviewOverlay:sn,ConnectionOverlay:vn,HelpOverlay:Tn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},disconnect:()=>{},connect:()=>{}}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await rl(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},setConnectionState:e=>{this.connectionState=e},togglePreview:()=>{this.toggle("preview",!1)}}))},unmounted(){this.g.disconnect()},methods:{reconnect(){this.g.connect()},toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}}});const cl={id:"game"},ul={class:"menu"},gl={class:"tabs"},pl=s("🧩 Puzzles");dl.render=function(e,i,s,r,d,c){const u=o("settings-overlay"),p=o("preview-overlay"),h=o("help-overlay"),m=o("connection-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",cl,[g(n(u,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(p,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(h,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",ul,[n("div",gl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[pl])),_:1}),n("div",{class:"opener",onClick:i[5]||(i[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var hl=e({name:"replay",components:{PuzzleStatus:en,Scores:qt,SettingsOverlay:nn,PreviewOverlay:sn,HelpOverlay:Tn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},disconnect:()=>{}},replay:{speed:1,paused:!1}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await rl(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"replay",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},setReplaySpeed:e=>{this.replay.speed=e},setReplayPaused:e=>{this.replay.paused=e}}))},unmounted(){this.g.disconnect()},methods:{toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}},computed:{replayText(){return"Replay-Speed: "+this.replay.speed+"x"+(this.replay.paused?" Paused":"")}}});const ml={id:"replay"},yl={class:"menu"},fl={class:"tabs"},wl=s("🧩 Puzzles");hl.render=function(e,i,s,d,c,u){const p=o("settings-overlay"),h=o("preview-overlay"),m=o("help-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",ml,[g(n(p,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(m,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:l((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:i[5]||(i[5]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",yl,[n("div",fl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[wl])),_:1}),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=b({history:x(),routes:[{name:"index",path:"/",component:$},{name:"new-game",path:"/new-game",component:$t},{name:"game",path:"/g/:id",component:dl},{name:"replay",path:"/replay/:id",component:hl}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const l=C(k);l.config.globalProperties.$config=t,l.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=He.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 39e5a46..5deb455 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 098ff8d..c1ad089 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -2,6 +2,8 @@ import WebSocket from 'ws'; import express from 'express'; 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'; @@ -215,7 +217,7 @@ var Util = { asQueryArgs, }; -const log$5 = logger('WebSocketServer.js'); +const log$4 = logger('WebSocketServer.js'); /* Example config @@ -253,12 +255,12 @@ class WebSocketServer { this._websocketserver.on('connection', (socket, request) => { const pathname = new URL(this.config.connectstring).pathname; if (request.url.indexOf(pathname) !== 0) { - log$5.log('bad request url: ', request.url); + log$4.log('bad request url: ', request.url); socket.close(); return; } socket.on('message', (data) => { - log$5.log(`ws`, socket.protocol, data); + log$4.log(`ws`, socket.protocol, data); this.evt.dispatch('message', { socket, data }); }); socket.on('close', () => { @@ -318,10 +320,10 @@ EV_SERVER_INIT: event sent to one client after that client */ const EV_SERVER_EVENT = 1; const EV_SERVER_INIT = 4; -const EV_SERVER_INIT_REPLAY = 5; +const EV_SERVER_REPLAY_DATA = 5; const EV_CLIENT_EVENT = 2; const EV_CLIENT_INIT = 3; -const EV_CLIENT_INIT_REPLAY = 6; +const EV_CLIENT_REPLAY_DATA = 6; const LOG_HEADER = 1; const LOG_ADD_PLAYER = 2; const LOG_UPDATE_PLAYER = 4; @@ -342,10 +344,10 @@ const CHANGE_PLAYER = 3; var Protocol = { EV_SERVER_EVENT, EV_SERVER_INIT, - EV_SERVER_INIT_REPLAY, + EV_SERVER_REPLAY_DATA, EV_CLIENT_EVENT, EV_CLIENT_INIT, - EV_CLIENT_INIT_REPLAY, + EV_CLIENT_REPLAY_DATA, LOG_HEADER, LOG_ADD_PLAYER, LOG_UPDATE_PLAYER, @@ -514,6 +516,9 @@ function getPlayerIdByIndex(gameId, playerIndex) { } function getPlayer(gameId, playerId) { const idx = getPlayerIndexById(gameId, playerId); + if (idx === -1) { + return null; + } return Util.decodePlayer(GAMES[gameId].players[idx]); } function setPlayer(gameId, playerId, player) { @@ -611,6 +616,9 @@ function getTilesSortedByZIndex(gameId) { } function changePlayer(gameId, playerId, change) { const player = getPlayer(gameId, playerId); + if (player === null) { + return; + } for (let k of Object.keys(change)) { // @ts-ignore player[k] = change[k]; @@ -890,9 +898,13 @@ function handleInput$1(gameId, playerId, input, ts) { } }; const _playerChange = () => { + const player = getPlayer(gameId, playerId); + if (!player) { + return; + } changes.push([ Protocol.CHANGE_PLAYER, - Util.encodePlayer(getPlayer(gameId, playerId)), + Util.encodePlayer(player), ]); }; // put both tiles (and their grouped tiles) in the same group @@ -1175,7 +1187,6 @@ const PUBLIC_DIR = `${BASE_DIR}/build/public/`; const DB_PATCHES_DIR = `${BASE_DIR}/src/dbpatches`; const DB_FILE = `${BASE_DIR}/data/db.sqlite`; -const log$4 = logger('GameLog.js'); const filename = (gameId) => `${DATA_DIR}/log_${gameId}.log`; const create = (gameId) => { const file = filename(gameId); @@ -1195,20 +1206,35 @@ const _log = (gameId, ...args) => { const str = JSON.stringify(args); fs.appendFileSync(file, str + "\n"); }; -const get = (gameId) => { +const get = async (gameId, offset = 0, size = 10000) => { const file = filename(gameId); if (!fs.existsSync(file)) { return []; } - const lines = fs.readFileSync(file, 'utf-8').split("\n"); - return lines.filter((line) => !!line).map((line) => { - try { - return JSON.parse(line); - } - catch (e) { - log$4.log(line); - log$4.log(e); - } + 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); + }); }); }; var GameLog = { @@ -2021,14 +2047,20 @@ wss.on('message', async ({ socket, data }) => { const msg = JSON.parse(data); const msgType = msg[0]; switch (msgType) { - case Protocol.EV_CLIENT_INIT_REPLAY: + case Protocol.EV_CLIENT_REPLAY_DATA: { if (!GameLog.exists(gameId)) { throw `[gamelog ${gameId} does not exist... ]`; } - const log = GameLog.get(gameId); - const game = await Game.createGameObject(gameId, log[0][2], log[0][3], log[0][4], log[0][5] || ScoreMode.FINAL); - notify([Protocol.EV_SERVER_INIT_REPLAY, Util.encodeGame(game), log], [socket]); + const offset = msg[1]; + const size = msg[2]; + const log = await GameLog.get(gameId, offset, size); + 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); + } + notify([Protocol.EV_SERVER_REPLAY_DATA, log, game ? Util.encodeGame(game) : null], [socket]); } break; case Protocol.EV_CLIENT_INIT: From b8946ef6a846987c33847d4b33823ca7095b13c7 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 29 May 2021 12:40:46 +0200 Subject: [PATCH 04/78] change replay to not use WS --- build/public/assets/index.1449524a.js | 1 - build/public/assets/index.382b265e.js | 1 + build/public/index.html | 2 +- build/server/main.js | 36 +++++++--------- src/common/Protocol.ts | 4 -- src/frontend/Communication.ts | 62 +++------------------------ src/frontend/game.ts | 40 ++++++++++------- src/server/main.ts | 48 ++++++++++----------- 8 files changed, 71 insertions(+), 123 deletions(-) delete mode 100644 build/public/assets/index.1449524a.js create mode 100644 build/public/assets/index.382b265e.js diff --git a/build/public/assets/index.1449524a.js b/build/public/assets/index.1449524a.js deleted file mode 100644 index 0b1e4c9..0000000 --- a/build/public/assets/index.1449524a.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as n,w as l,b as i,r as o,o as a,e as s,t as r,F as d,f as c,g as u,h as g,v as p,i as h,j as m,k as y,l as f,m as w,n as v,p as b,q as x,s as C}from"./vendor.b622ee49.js";var k=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const A={id:"app"},T={key:0,class:"nav"},S=s("Index"),z=s("New game");k.render=function(e,s,r,d,c,u){const g=o("router-link"),p=o("router-view");return a(),t("div",A,[e.showNav?(a(),t("ul",T,[n("li",null,[n(g,{class:"btn",to:{name:"index"}},{default:l((()=>[S])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:l((()=>[z])),_:1})])])):i("",!0),n(p)])};const I=864e5,P=e=>{const t=Math.floor(e/I);e%=I;const n=Math.floor(e/36e5);e%=36e5;const l=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${l}m ${Math.floor(e/1e3)}s`};var _=1e3,D=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>P(t-e),E=P,O=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",l=e,i=t||D();return`${n} ${B(l,i)}`}}});const U={class:"game-info-text"},N=n("br",null,null,-1),M=n("br",null,null,-1),G=n("br",null,null,-1),R=s(" ↪️ Watch replay ");O.render=function(e,d,c,u,g,p){const h=o("router-link");return a(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:l((()=>[n("span",U,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),N,s(" 👥 "+r(e.game.players),1),M,s(" "+r(e.time(e.game.started,e.game.finished)),1),G])])),_:1},8,["to"]),e.game.hasReplay?(a(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:l((()=>[R])),_:1},8,["to"])):i("",!0)],4)};var $=e({components:{GameTeaser:O},data:()=>({gamesRunning:[],gamesFinished:[]}),async created(){const e=await fetch("/api/index-data"),t=await e.json();this.gamesRunning=t.gamesRunning,this.gamesFinished=t.gamesFinished}});const V=n("h1",null,"Running games",-1),j=n("h1",null,"Finished games",-1);$.render=function(e,l,i,s,r,u){const g=o("game-teaser");return a(),t("div",null,[V,(a(!0),t(d,null,c(e.gamesRunning,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128)),j,(a(!0),t(d,null,c(e.gamesFinished,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128))])};var F=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});F.render=function(e,l,i,o,s,r){return a(),t("div",{class:"imageteaser",style:e.style,onClick:l[2]||(l[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:l[1]||(l[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var L=e({name:"image-library",components:{ImageTeaser:F},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});L.render=function(e,n,l,i,s,r){const u=o("image-teaser");return a(),t("div",null,[(a(!0),t(d,null,c(e.images,((n,l)=>(a(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:l},null,8,["image","onClick","onEditClick"])))),128))])};const W={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};W.render=function(e,n,l,i,o,s){return a(),t("div",{style:s.style,title:l.title},null,12,["title"])};var q=e({name:"tags-input",props:{modelValue:{type:Array,required:!0}},emits:{"update:modelValue":null},data:()=>({input:"",values:[]}),created(){this.values=this.modelValue},methods:{onKeyUp(e){if(","===e.key)return this.add(),e.stopPropagation(),!1},add(){const e=this.input.replace(/,/g,"").trim();e&&(this.values.includes(e)||this.values.push(e),this.input="",this.$emit("update:modelValue",this.values))},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const H=m()(((e,l,i,o,s,u)=>(a(),t("div",null,[g(n("input",{class:"input",type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.input=t),placeholder:"Plants, People",onKeydown:l[2]||(l[2]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:l[3]||(l[3]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[p,e.input]]),(a(!0),t(d,null,c(e.values,((n,l)=>(a(),t("span",{key:l,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));q.render=H,q.__scopeId="data-v-771460ae";var Q=e({name:"new-image-dialog",components:{ResponsiveImage:W,TagsInput:q},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const l=new FileReader;l.readAsDataURL(n),l.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const Y={key:0,class:"has-image"},K={key:1},Z={class:"upload"},J=n("span",{class:"btn"},"Upload File",-1),X={class:"area-settings"},ee=n("td",null,[n("label",null,"Title")],-1),te=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ne=n("td",null,[n("label",null,"Tags")],-1),le={class:"area-buttons"},ie=s("🧩 Post to gallery "),oe=n("br",null,null,-1),ae=s(" + set up game");Q.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay new-image-dialog",onClick:l[8]||(l[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[7]||(l[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(a(),t("div",Y,[n("span",{class:"remove btn",onClick:l[1]||(l[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(a(),t("div",K,[n("label",Z,[n("input",{type:"file",style:{display:"none"},onChange:l[2]||(l[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),J])]))],2),n("div",X,[n("table",null,[n("tr",null,[ee,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[3]||(l[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),te,n("tr",null,[ne,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[4]||(l[4]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",le,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:l[5]||(l[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:l[6]||(l[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[ie,oe,ae],8,["disabled"])])])])};var se=e({name:"edit-image-dialog",components:{ResponsiveImage:W,TagsInput:q},props:{image:{type:Object,required:!0}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const re={class:"area-image"},de={class:"has-image"},ce={class:"area-settings"},ue=n("td",null,[n("label",null,"Title")],-1),ge=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),pe=n("td",null,[n("label",null,"Tags")],-1),he={class:"area-buttons"};function me(e,t){const n=e.x-t.x,l=e.y-t.y;return Math.sqrt(n*n+l*l)}function ye(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}se.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay edit-image-dialog",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("div",re,[n("div",de,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ce,[n("table",null,[n("tr",null,[ue,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ge,n("tr",null,[pe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[2]||(l[2]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",he,[n("button",{class:"btn",onClick:l[3]||(l[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var fe={pointSub:function(e,t){return{x:e.x-t.x,y:e.y-t.y}},pointAdd:function(e,t){return{x:e.x+t.x,y:e.y+t.y}},pointDistance:me,pointInBounds:function(e,t){return e.x>=t.x&&e.x<=t.x+t.w&&e.y>=t.y&&e.y<=t.y+t.h},rectCenter:ye,rectMoved:function(e,t,n){return{x:e.x+t,y:e.y+n,w:e.w,h:e.h}},rectCenterDistance:function(e,t){return me(ye(e),ye(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};var we=1,ve=4,be=5,xe=2,Ce=3,ke=6,Ae=2,Te=4,Se=3,ze=9,Ie=1,Pe=2,_e=3,De=4,Be=5,Ee=6,Oe=7,Ue=8,Ne=10,Me=1,Ge=2,Re=3;class $e{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){return this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295,e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),l=t[n];t[n]=t[e],t[e]=l}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new $e(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const Ve=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},je=(...e)=>{const t=t=>(...n)=>{const l=new Date,i=Ve(l.getHours(),"00"),o=Ve(l.getMinutes(),"00"),a=Ve(l.getSeconds(),"00");console[t](`${i}:${o}:${a}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var Fe,Le,We,qe,He={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}},encodeTile:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodeTile:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return Array.isArray(e)?e:[e.id,e.rng.type,$e.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode]},decodeGame:function(e){return Array.isArray(e)?{id:e[0],rng:{type:e[1],obj:$e.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}:e},coordByTileIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(let n in e){const l=[n,e[n]].map(encodeURIComponent);t.push(l.join("="))}return 0===t.length?"":`?${t.join("&")}`}};(Le=Fe||(Fe={}))[Le.Flat=0]="Flat",Le[Le.Out=1]="Out",Le[Le.In=-1]="In",(qe=We||(We={}))[qe.FINAL=0]="FINAL",qe[qe.ANY=1]="ANY";const Qe={};function Ye(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}function Ke(e,t){let n=0;for(let l of Qe[e].players){if(He.decodePlayer(l).id===t)return n;n++}return-1}function Ze(e,t){const n=Ke(e,t);return-1===n?null:He.decodePlayer(Qe[e].players[n])}function Je(e,t,n){const l=Ke(e,t);-1===l?Qe[e].players.push(He.encodePlayer(n)):Qe[e].players[l]=He.encodePlayer(n)}function Xe(e,t){return-1!==Ke(e,t)}function et(e){return Qe[e]?Qe[e].players.map(He.decodePlayer):[]}function tt(e){return Qe[e].puzzle.tiles.length}function nt(e){return Qe[e].scoreMode||0}function lt(e){return it(e)===tt(e)}function it(e){let t=0;for(let n of Qe[e].puzzle.tiles)-1===He.decodeTile(n).owner&&t++;return t}function ot(e,t,n){const l=Ze(e,t);if(null!==l){for(let e of Object.keys(n))l[e]=n[e];Je(e,t,l)}}function at(e,t){for(let n of Object.keys(t))Qe[e].puzzle.data[n]=t[n]}function st(e,t,n){for(let l of Object.keys(n)){const i=He.decodeTile(Qe[e].puzzle.tiles[t]);i[l]=n[l],Qe[e].puzzle.tiles[t]=He.encodeTile(i)}}const rt=(e,t)=>He.decodeTile(Qe[e].puzzle.tiles[t]),dt=(e,t)=>rt(e,t).group,ct=(e,t)=>{const n=Qe[e].puzzle.info,l={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},i=function(e,t){const n=Qe[e].puzzle.info,l=He.coordByTileIdx(n,t),i=l.x*n.tileSize,o=l.y*n.tileSize;return{x:i,y:o}}(e,t);return fe.pointAdd(l,i)},ut=(e,t)=>rt(e,t).pos,gt=e=>{const t=zt(e),n=It(e),l=Math.round(t/4),i=Math.round(n/4);return{x:0-l,y:0-i,w:t+2*l,h:n+2*i}},pt=(e,t)=>{const n=ft(e),l=rt(e,t);return{x:l.pos.x,y:l.pos.y,w:n,h:n}},ht=(e,t)=>rt(e,t).z,mt=(e,t)=>{for(let n of Qe[e].puzzle.tiles){const e=He.decodeTile(n);if(e.owner===t)return e.idx}return-1},yt=e=>Qe[e].puzzle.info.tileDrawSize,ft=e=>Qe[e].puzzle.info.tileSize,wt=e=>Qe[e].puzzle.data.maxGroup,vt=e=>Qe[e].puzzle.data.maxZ;function bt(e,t){const n=Qe[e].puzzle.info,l=He.coordByTileIdx(n,t);return[l.y>0?t-n.tilesX:-1,l.x0?t-1:-1]}const xt=(e,t,n)=>{for(let l of t)st(e,l,{z:n})},Ct=(e,t,n)=>{const l=ut(e,t);st(e,t,{pos:fe.pointAdd(l,n)})},kt=(e,t,n)=>{const l=yt(e),i=gt(e),o=n;for(let a of t){const t=rt(e,a);t.pos.x+n.xi.x+i.w&&(o.x=Math.min(i.x+i.w-t.pos.x+l,o.x)),t.pos.y+n.yi.y+i.h&&(o.y=Math.min(i.y+i.h-t.pos.y+l,o.y))}for(let a of t)Ct(e,a,o)},At=(e,t,n)=>{for(let l of t)st(e,l,{owner:n})};function Tt(e,t){const n=Qe[e].puzzle.tiles,l=He.decodeTile(n[t]),i=[];if(l.group)for(let o of n){const e=He.decodeTile(o);e.group===l.group&&i.push(e.idx)}else i.push(l.idx);return i}const St=(e,t)=>{const n=Ze(e,t);return n?n.points:0},zt=e=>Qe[e].puzzle.info.table.width,It=e=>Qe[e].puzzle.info.table.height;var Pt={__createPlayerObject:Ye,setGame:function(e,t){Qe[e]=t},exists:function(e){return!!Qe[e]||!1},playerExists:Xe,getActivePlayers:function(e,t){const n=t-30*_;return et(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return et(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Xe(e,t)?ot(e,t,{ts:n}):Je(e,t,Ye(t,n))},getFinishedTileCount:it,getTileCount:tt,getImageUrl:function(e){return Qe[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Qe[e].puzzle.info.imageUrl=t},get:function(e){return Qe[e]},getAllGames:function(){return Object.values(Qe).sort(((e,t)=>lt(e.id)===lt(t.id)?t.puzzle.data.started-e.puzzle.data.started:lt(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Ze(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Ze(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Ze(e,t);return n?n.name:null},getPlayerIndexById:Ke,getPlayerIdByIndex:function(e,t){return Qe[e].players.length>t?He.decodePlayer(Qe[e].players[t]).id:null},changePlayer:ot,setPlayer:Je,setTile:function(e,t,n){Qe[e].puzzle.tiles[t]=He.encodeTile(n)},setPuzzleData:function(e,t){Qe[e].puzzle.data=t},getTableWidth:zt,getTableHeight:It,getPuzzle:e=>Qe[e].puzzle,getRng:e=>Qe[e].rng.obj,getPuzzleWidth:e=>Qe[e].puzzle.info.width,getPuzzleHeight:e=>Qe[e].puzzle.info.height,getTilesSortedByZIndex:function(e){return Qe[e].puzzle.tiles.map(He.decodeTile).sort(((e,t)=>e.z-t.z))},getFirstOwnedTile:(e,t)=>{const n=mt(e,t);return n<0?null:Qe[e].puzzle.tiles[n]},getTileDrawOffset:e=>Qe[e].puzzle.info.tileDrawOffset,getTileDrawSize:yt,getFinalTilePos:ct,getStartTs:e=>Qe[e].puzzle.data.started,getFinishTs:e=>Qe[e].puzzle.data.finished,handleInput:function(e,t,n,l){const i=Qe[e].puzzle,o=function(e,t){return t in Qe[e].evtInfos?Qe[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),a=[],s=()=>{a.push([Me,i.data])},r=t=>{a.push([Ge,He.encodeTile(rt(e,t))])},d=e=>{for(const t of e)r(t)},c=()=>{const n=Ze(e,t);n&&a.push([Re,He.encodePlayer(n)])},u=n[0];if(u===Ee){const i=n[1];ot(e,t,{bgcolor:i,ts:l}),c()}else if(u===Oe){const i=n[1];ot(e,t,{color:i,ts:l}),c()}else if(u===Ue){const i=`${n[1]}`.substr(0,16);ot(e,t,{name:i,ts:l}),c()}else if(u===Ie){const i={x:n[1],y:n[2]};ot(e,t,{d:1,ts:l}),c(),o._last_mouse_down=i;const a=((e,t)=>{let n=Qe[e].puzzle.info,l=Qe[e].puzzle.tiles,i=-1,o=-1;for(let a=0;ai)&&(i=e.z,o=a)}return o})(e,i);if(a>=0){let n=vt(e)+1;at(e,{maxZ:n}),s();const l=Tt(e,a);xt(e,l,vt(e)),At(e,l,t),d(l)}o._last_mouse=i}else if(u===_e){const i=n[1],a=n[2],s={x:i,y:a};if(null===o._last_mouse_down)ot(e,t,{x:i,y:a,ts:l}),c();else{let n=mt(e,t);if(n>=0){ot(e,t,{x:i,y:a,ts:l}),c();const r=Tt(e,n);let u=fe.pointInBounds(s,gt(e))&&fe.pointInBounds(o._last_mouse_down,gt(e));for(let t of r){const n=pt(e,t);if(fe.pointInBounds(s,n)){u=!0;break}}if(u){const t=i-o._last_mouse_down.x,n=a-o._last_mouse_down.y;kt(e,r,{x:t,y:n}),d(r)}}else ot(e,t,{ts:l}),c();o._last_mouse_down=s}o._last_mouse=s}else if(u===Pe){const a={x:n[1],y:n[2]},u=0;o._last_mouse_down=null;let g=mt(e,t);if(g>=0){let n=Tt(e,g);At(e,n,0),d(n);let o=ut(e,g),a=ct(e,g);if(fe.pointDistance(a,o){for(let n of t)st(e,n,{owner:-1,z:1})})(e,n),d(n);let r=St(e,t);0===nt(e)?r+=n.length:1===nt(e)&&(r+=1),ot(e,t,{d:u,ts:l,points:r}),c(),it(e)===tt(e)&&(at(e,{finished:l}),s())}else{const n=(e,t,n,l)=>{let i=Qe[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const l=dt(e,t),i=dt(e,n);return!(!l||l!==i)})(e,t,n))return!1;const o=ut(e,t),a=fe.pointAdd(ut(e,n),{x:l[0]*i.tileSize,y:l[1]*i.tileSize});if(fe.pointDistance(o,a){const l=Qe[e].puzzle.tiles,i=dt(e,t),o=dt(e,n);let a;const d=[];i&&d.push(i),o&&d.push(o),i?a=i:o?a=o:(at(e,{maxGroup:wt(e)+1}),s(),a=wt(e));if(st(e,t,{group:a}),r(t),st(e,n,{group:a}),r(n),d.length>0)for(const s of l){const t=He.decodeTile(s);d.includes(t.group)&&(st(e,t.idx,{group:a}),r(t.idx))}})(e,t,n),i=Tt(e,t);const c=((e,t)=>{let n=0;for(let l of t){let t=ht(e,l);t>n&&(n=t)}return n})(e,i);return xt(e,i,c),d(i),!0}return!1};let i=!1;for(let t of Tt(e,g)){let l=bt(e,t);if(n(e,t,l[0],[0,1])||n(e,t,l[1],[-1,0])||n(e,t,l[2],[0,-1])||n(e,t,l[3],[1,0])){i=!0;break}}if(i&&1===nt(e)){const n=St(e,t)+1;ot(e,t,{d:u,ts:l,points:n}),c()}else ot(e,t,{d:u,ts:l}),c()}}else ot(e,t,{d:u,ts:l}),c();o._last_mouse=a}else if(u===De){const i=n[1],a=n[2];ot(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else if(u===Be){const i=n[1],a=n[2];ot(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else ot(e,t,{ts:l}),c();return function(e,t,n){Qe[e].evtInfos[t]=n}(e,t,o),a}},_t=e({name:"new-game-dialog",components:{ResponsiveImage:W},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:We.ANY}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Dt={class:"area-image"},Bt={class:"has-image"},Et={class:"area-settings"},Ot=n("td",null,[n("label",null,"Pieces")],-1),Ut=n("td",null,[n("label",null,"Scoring: ")],-1),Nt=s(" Any (Score when pieces are connected to each other or on final location)"),Mt=n("br",null,null,-1),Gt=s(" Final (Score when pieces are put to their final location)"),Rt={class:"area-buttons"};_t.render=function(e,l,i,s,r,d){const c=o("responsive-image");return a(),t("div",{class:"overlay new-game-dialog",onClick:l[6]||(l[6]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[5]||(l[5]=u((()=>{}),["stop"]))},[n("div",Dt,[n("div",Bt,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Et,[n("table",null,[n("tr",null,[Ot,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Ut,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[2]||(l[2]=t=>e.scoreMode=t),value:"1"},null,512),[[y,e.scoreMode]]),Nt]),Mt,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[3]||(l[3]=t=>e.scoreMode=t),value:"0"},null,512),[[y,e.scoreMode]]),Gt])])])])]),n("div",Rt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:l[4]||(l[4]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var $t=e({components:{ImageLibrary:L,NewImageDialog:Q,EditImageDialog:se,NewGameDialog:_t},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},methods:{toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${He.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const Vt={class:"upload-image-teaser"},jt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),Ft={key:0},Lt=s(" Tags: "),Wt=s(" Sort by: "),qt=n("option",{value:"date_desc"},"Newest first",-1),Ht=n("option",{value:"date_asc"},"Oldest first",-1),Qt=n("option",{value:"alpha_asc"},"A-Z",-1),Yt=n("option",{value:"alpha_desc"},"Z-A",-1);$t.render=function(e,l,s,u,p,h){const m=o("image-library"),y=o("new-image-dialog"),w=o("edit-image-dialog"),v=o("new-game-dialog");return a(),t("div",null,[n("div",Vt,[n("div",{class:"btn btn-big",onClick:l[1]||(l[1]=t=>e.dialog="new-image")},"Upload your image"),jt]),n("div",null,[e.tags.length>0?(a(),t("label",Ft,[Lt,(a(!0),t(d,null,c(e.tags,((n,l)=>(a(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:l,onClick:t=>e.toggleTag(n)},r(n.title),11,["onClick"])))),128))])):i("",!0),n("label",null,[Wt,g(n("select",{"onUpdate:modelValue":l[2]||(l[2]=t=>e.filters.sort=t),onChange:l[3]||(l[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[qt,Ht,Qt,Yt],544),[[f,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(a(),t(y,{key:0,onBgclick:l[4]||(l[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["onPostToGalleryClick","onSetupGameClick"])):i("",!0),"edit-image"===e.dialog?(a(),t(w,{key:1,onBgclick:l[5]||(l[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["onSaveClick","image"])):i("",!0),e.image&&"new-game"===e.dialog?(a(),t(v,{key:2,onBgclick:l[6]||(l[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):i("",!0)])};var Kt=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 Zt={class:"scores"},Jt=n("div",null,"Scores",-1),Xt=n("td",null,"⚡",-1),en=n("td",null,"💤",-1);Kt.render=function(e,l,i,o,s,u){return a(),t("div",Zt,[Jt,n("table",null,[(a(!0),t(d,null,c(e.actives,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[Xt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(a(!0),t(d,null,c(e.idles,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[en,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var tn=e({name:"puzzle-status",props:{finished:{type:Boolean,required:!0},duration:{type:Number,required:!0},piecesDone:{type:Number,required:!0},piecesTotal:{type:Number,required:!0}},computed:{icon(){return this.finished?"🏁":"⏳"},durationStr(){return E(this.duration)}}});const nn={class:"timer"};tn.render=function(e,l,i,o,s,d){return a(),t("div",nn,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),w(e.$slots,"default")])};var ln=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 on=n("td",null,[n("label",null,"Background: ")],-1),an=n("td",null,[n("label",null,"Color: ")],-1),sn=n("td",null,[n("label",null,"Name: ")],-1);ln.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("tr",null,[on,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[1]||(l[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[an,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[2]||(l[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[sn,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":l[3]||(l[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])])])])};var rn=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const dn={class:"preview"};rn.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay",onClick:l[1]||(l[1]=t=>e.$emit("bgclick"))},[n("div",dn,[n("div",{class:"img",style:e.previewStyle},null,4)])])};const cn=je("Communication.js");let un,gn=e=>{},pn=e=>{};let hn=0;const mn=e=>{hn!==e&&(hn=e,pn(e))};function yn(e){if(2===hn)try{un.send(JSON.stringify(e))}catch(t){cn.info("unable to send message.. maybe because ws is invalid?")}}let fn,wn;function vn(e,t){yn([ke,e,t])}var bn={connect:function(e,t,n){return fn=0,wn={},mn(3),new Promise((l=>{un=new WebSocket(e,n+"|"+t),un.onopen=e=>{mn(2),yn([Ce])},un.onmessage=e=>{const t=JSON.parse(e.data),i=t[0];if(i===ve){const e=t[1];l(e)}else{if(i!==we)throw`[ 2021-05-09 invalid connect msgType ${i} ]`;{const e=t[1],l=t[2];if(e===n&&wn[l])return void delete wn[l];gn(t)}}},un.onerror=e=>{throw mn(1),"[ 2021-05-15 onerror ]"},un.onclose=e=>{4e3===e.code||1001===e.code?mn(4):mn(1)}}))},connectReplay:function(e,t,n){return fn=0,wn={},mn(3),new Promise((l=>{un=new WebSocket(e,n+"|"+t),un.onopen=e=>{mn(2),vn(0,1e4)},un.onmessage=e=>{const t=JSON.parse(e.data),n=t[0];if(n!==be)throw`[ 2021-05-09 invalid connectReplay msgType ${n} ]`;{const e=t[1],n=t[2];if(null!==n){l({game:n,log:e})}else gn(t)}},un.onerror=e=>{throw mn(1),"[ 2021-05-15 onerror ]"},un.onclose=e=>{4e3===e.code||1001===e.code?mn(4):mn(1)}}))},requestReplayData:vn,disconnect:function(){un&&un.close(4e3),fn=0,wn={}},sendClientEvent:function(e){fn++,wn[fn]=e,yn([xe,fn,wn[fn]])},onServerChange:function(e){gn=e},onConnectionStateChange:function(e){pn=e},CODE_CUSTOM_DISCONNECT:4e3,CONN_STATE_NOT_CONNECTED:0,CONN_STATE_DISCONNECTED:1,CONN_STATE_CLOSED:4,CONN_STATE_CONNECTED:2,CONN_STATE_CONNECTING:3},xn=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 Cn={key:0,class:"overlay connection-lost"},kn={key:0,class:"overlay-content"},An=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Tn={key:1,class:"overlay-content"},Sn=n("div",null,"Connecting...",-1);xn.render=function(e,l,o,s,r,d){return e.show?(a(),t("div",Cn,[e.lostConnection?(a(),t("div",kn,[An,n("span",{class:"btn",onClick:l[1]||(l[1]=t=>e.$emit("reconnect"))},"Reconnect")])):i("",!0),e.connecting?(a(),t("div",Tn,[Sn])):i("",!0)])):i("",!0)};var zn=e({name:"help-overlay",emits:{bgclick:null}});const In=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),_n=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),Dn=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),En=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),On=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),Un=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Nn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Mn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1);zn.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[In,Pn,_n,Dn,Bn,En,On,Un,Nn,Mn])])};var Gn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Rn=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="}),Vn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function jn(){let e=0,t=0,n=1;const l=(l,i)=>{e+=l/n,t+=i/n},i=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},o=l=>({x:l.x/n-e,y:l.y/n-t}),a=l=>({x:(l.x+e)*n,y:(l.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n});return{move:l,canZoom:e=>n!=i(e),zoom:(e,t)=>((e,t)=>{if(n==e)return!1;const i=1-n/e;return l(-t.x*i,-t.y*i),n=e,!0})(i(e),t),worldToViewport:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:a,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=o(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:o}}function Fn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Ln={createCanvas:Fn,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=Fn(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=Fn(e.width,e.height),i=l.getContext("2d");return i.save(),i.drawImage(t,0,0),i.fillStyle=n,i.globalCompositeOperation="source-in",i.fillRect(0,0,t.width,t.height),i.restore(),i.save(),i.globalCompositeOperation="destination-over",i.drawImage(e,0,0),i.restore(),l}};const Wn=je("Debug.js");let qn=0,Hn=0;var Qn=e=>{qn=performance.now(),Hn=e},Yn=e=>{const t=performance.now(),n=t-qn;n>Hn&&Wn.log(e+": "+n),qn=t};const Kn=je("PuzzleGraphics.js");function Zn(e,t){const n=He.coordByTileIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Jn={loadPuzzleBitmaps:async function(e){const t=await Ln.loadImageToBitmap(e.info.imageUrl),n=await Ln.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Kn.log("start createPuzzleTileBitmaps");var l=n.tileSize,i=n.tileMarginWidth,o=n.tileDrawSize,a=l/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0];const r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,o={x:i,y:i},r=fe.pointAdd(o,{x:l,y:0}),c=fe.pointAdd(r,{x:0,y:l}),u=fe.pointSub(c,{x:l,y:0});if(n.moveTo(o.x,o.y),0!==e.top)for(let l=0;l=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;tl=t/2,nl=t-tl;const n=1/4*this.canvas.width/(t/2);Xn=-n,el=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new ll(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new ll(this.rng));const e=[];for(;this.explodedBombs.length>0;){const t=this.explodedBombs.shift();if(!t)break;t.update(),t.alive&&e.push(t)}this.explodedBombs=e;const t=[];for(;this.readyBombs.length>0;){const e=this.readyBombs.shift();if(!e)break;e.update(this.particles),e.hasExploded?this.explodedBombs.push(e):t.push(e)}this.readyBombs=t;const n=[];for(;this.particles.length>0;){const e=this.particles.shift();if(!e)break;e.update(),e.alive&&n.push(e)}this.particles=n}render(){this.ctx.beginPath(),this.ctx.fillStyle="rgba(0, 0, 0, 0.1)",this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height);for(let e=0;e{const t=e.color+" "+e.d;if(!h[t]){const n=e.d?a:s;if(e.color){const l=e.d?r:d;h[t]=await createImageBitmap(Ln.colorizedCanvas(n,l,e.color))}else h[t]=n}return h[t]},y=function(e,t){return t.width=window.innerWidth,t.height=window.innerHeight,e.appendChild(t),window.addEventListener("resize",(()=>{t.width=window.innerWidth,t.height=window.innerHeight,dl=!0})),t}(i,Ln.createCanvas()),f={final:!1,requesting:!0,log:[],logPointer:0,logIdx:0,speeds:[.5,1,2,5,10,20,50],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0};bn.onConnectionStateChange((e=>{o.setConnectionState(e)}));let w=()=>0;const v=async()=>{if("play"===l){const l=await bn.connect(n,e,t),i=He.decodeGame(l);Pt.setGame(i.id,i),w=()=>D()}else{if("replay"!==l)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{bn.onServerChange((e=>{const t=e[1];f.log=f.log.slice(f.logPointer),f.logPointer=0,f.log.push(...e[1]),t.length<1e4&&(f.final=!0),f.requesting=!1}));const l=await bn.connectReplay(n,e,t),i=He.decodeGame(l.game);Pt.setGame(i.id,i),f.requesting=!1,f.log=l.log,f.lastRealTs=D(),f.gameStartTs=parseInt(f.log[0][4],10),f.lastGameTs=f.gameStartTs,w=()=>f.lastGameTs}}dl=!0};await v();const b=Pt.getTileDrawOffset(e),x=Pt.getTileDrawSize(e),C=Pt.getPuzzleWidth(e),k=Pt.getPuzzleHeight(e),A=Pt.getTableWidth(e),T=Pt.getTableHeight(e),S={x:(A-C)/2,y:(T-k)/2},z={w:C,h:k},I={w:x,h:x},P=await Jn.loadPuzzleBitmaps(Pt.getPuzzle(e)),_=new ol(y,Pt.getRng(e));_.init();const B=y.getContext("2d");y.classList.add("loaded");const E=jn();E.move(-(A-y.width)/2,-(T-y.height)/2);const O=function(e,t,n){let l=[],i=!0,o=!1,a=!1,s=!1,r=!1,d=!1,c=!1,u=!1;const g=(e,t)=>{const l=n.viewportToWorld({x:e,y:t});return[l.x,l.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{i&&("Shift"===t.key?u=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?s=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?r=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?o=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?a=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};e.addEventListener("mousedown",(e=>{0===e.button&&y([Ie,...p(e)])})),e.addEventListener("mouseup",(e=>{0===e.button&&y([Pe,...p(e)])})),e.addEventListener("mousemove",(e=>{y([_e,...p(e)])})),e.addEventListener("wheel",(e=>{if(n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?De:Be;y([t,...p(e)])}})),t.addEventListener("keydown",(e=>m(!0,e))),t.addEventListener("keyup",(e=>m(!1,e))),t.addEventListener("keypress",(e=>{i&&(" "===e.key&&y([Ne]),"F"!==e.key&&"f"!==e.key||(sl=!sl,dl=!0),"G"!==e.key&&"g"!==e.key||(rl=!rl,dl=!0))}));const y=e=>{l.push(e)};return{addEvent:y,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=u?20:10,t=(o?e:0)-(a?e:0),l=(s?e:0)-(r?e:0);0===t&&0===l||y([ze,t,l]),d&&c||(d?n.canZoom("in")&&y([De,...h()]):c&&n.canZoom("out")&&y([Be,...h()]))},setHotkeys:e=>{i=e}}}(y,window,E),U=Pt.getImageUrl(e),N=()=>{const t=Pt.getStartTs(e),n=Pt.getFinishTs(e),l=w();o.setFinished(!!n),o.setDuration((n||l)-t)};N(),o.setPiecesDone(Pt.getFinishedTileCount(e)),o.setPiecesTotal(Pt.getTileCount(e));const M=w();o.setActivePlayers(Pt.getActivePlayers(e,M)),o.setIdlePlayers(Pt.getIdlePlayers(e,M));const G=!!Pt.getFinishTs(e);let R=G;const $=()=>R&&!G,V=()=>Pt.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",j=()=>Pt.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let F="",L="",W=!1;const q=e=>{W=e;const[t,n]=e?[F,"grab"]:[L,"default"];y.style.cursor=`url('${t}') ${u} ${p}, ${n}`},H=e=>{F=Ln.colorizedCanvas(a,r,e).toDataURL(),L=Ln.colorizedCanvas(s,d,e).toDataURL(),q(W)};H(j());const Q=()=>{o.setReplaySpeed&&o.setReplaySpeed(f.speeds[f.speedIdx]),o.setReplayPaused&&o.setReplayPaused(f.paused)};if("play"===l?setInterval(N,1e3):"replay"===l&&Q(),"play"===l)bn.onServerChange((n=>{n[0],n[1],n[2];const l=n[3];for(const[i,o]of l)switch(i){case Re:{const n=He.decodePlayer(o);n.id!==t&&(Pt.setPlayer(e,n.id,n),dl=!0)}break;case Ge:{const t=He.decodeTile(o);Pt.setTile(e,t.idx,t),dl=!0}break;case Me:Pt.setPuzzleData(e,o),dl=!0}R=!!Pt.getFinishTs(e)}));else if("replay"===l){let t=setInterval((()=>{const n=D();if(f.requesting)return void(f.lastRealTs=n);if(f.logPointer+1>=f.log.length)return f.lastRealTs=n,f.requesting=!0,void bn.requestReplayData(f.logIdx,1e4);if(f.paused)return void(f.lastRealTs=n);const l=(n-f.lastRealTs)*f.speeds[f.speedIdx],i=f.lastGameTs+l;for(;;){if(f.paused)break;const n=f.logPointer+1;if(n>=f.log.length){f.final&&clearInterval(t);break}const l=f.log[n],o=f.gameStartTs+l[l.length-1];if(o>i)break;const a=l.slice();if(a[0]===Ae){const t=a[1];Pt.addPlayer(e,t,o),dl=!0}else if(a[0]===Te){const t=Pt.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";Pt.addPlayer(e,t,o),dl=!0}else if(a[0]===Se){const t=Pt.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=a[2];Pt.handleInput(e,t,n,o),dl=!0}f.logPointer=n,f.logIdx++}f.lastRealTs=n,f.lastGameTs=i,N()}),50)}let Y=null;return(e=>{const t=e.fps||60,n=e.slow||1,l=e.update,i=e.render,o=window.requestAnimationFrame,a=1/t,s=n*a;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>s;)d-=s,l(a);i(d/n),c=r,o(u)};o(u)})({update:()=>{O.createKeyEvents();for(const n of O.consumeAll())if("play"===l){const l=n[0];if(l===ze){const e=n[1],t=n[2];dl=!0,E.move(e,t)}else if(l===_e){if(Y&&!Pt.getFirstOwnedTile(e,t)){const e={x:n[1],y:n[2]},t=E.worldToViewport(e),l=Math.round(t.x-Y.x),i=Math.round(t.y-Y.y);dl=!0,E.move(l,i),Y=t}}else if(l===Oe)H(n[1]);else if(l===Ie){const e={x:n[1],y:n[2]};Y=E.worldToViewport(e),q(!0)}else if(l===Pe)Y=null,q(!1);else if(l===De){const e={x:n[1],y:n[2]};dl=!0,E.zoom("in",E.worldToViewport(e))}else if(l===Be){const e={x:n[1],y:n[2]};dl=!0,E.zoom("out",E.worldToViewport(e))}else l===Ne&&o.togglePreview();const i=w();Pt.handleInput(e,t,n,i).length>0&&(dl=!0),bn.sendClientEvent(n)}else if("replay"===l){const e=n[0];if(e===ze){const e=n[1],t=n[2];dl=!0,E.move(e,t)}else if(e===_e){if(Y){const e={x:n[1],y:n[2]},t=E.worldToViewport(e),l=Math.round(t.x-Y.x),i=Math.round(t.y-Y.y);dl=!0,E.move(l,i),Y=t}}else if(e===Ie){const e={x:n[1],y:n[2]};Y=E.worldToViewport(e)}else if(e===Pe)Y=null;else if(e===De){const e={x:n[1],y:n[2]};dl=!0,E.zoom("in",E.worldToViewport(e))}else if(e===Be){const e={x:n[1],y:n[2]};dl=!0,E.zoom("out",E.worldToViewport(e))}else e===Ne&&o.togglePreview()}R=!!Pt.getFinishTs(e),$()&&(_.update(),dl=!0)},render:async()=>{if(!dl)return;const n=w();let i,a,s;window.DEBUG&&Qn(0),B.fillStyle=V(),B.fillRect(0,0,y.width,y.height),window.DEBUG&&Yn("clear done"),i=E.worldToViewportRaw(S),a=E.worldDimToViewportRaw(z),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(i.x,i.y,a.w,a.h),window.DEBUG&&Yn("board done");const r=Pt.getTilesSortedByZIndex(e);window.DEBUG&&Yn("get tiles done"),a=E.worldDimToViewportRaw(I);for(const e of r)(-1===e.owner?sl:rl)&&(s=P[e.idx],i=E.worldToViewportRaw({x:b+e.pos.x,y:b+e.pos.y}),B.drawImage(s,0,0,s.width,s.height,i.x,i.y,a.w,a.h));window.DEBUG&&Yn("tiles done");const d=[];for(const o of Pt.getActivePlayers(e,n))c=o,("replay"===l||c.id!==t)&&(s=await m(o),i=E.worldToViewport(o),B.drawImage(s,i.x-u,i.y-p),d.push([`${o.name} (${o.points})`,i.x,i.y+g]));var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,l]of d)B.fillText(e,t,l);window.DEBUG&&Yn("players done"),o.setActivePlayers(Pt.getActivePlayers(e,n)),o.setIdlePlayers(Pt.getIdlePlayers(e,n)),o.setPiecesDone(Pt.getFinishedTileCount(e)),window.DEBUG&&Yn("HUD done"),$()&&_.render(),dl=!1}}),{setHotkeys:e=>{O.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),O.addEvent([Ee,e])},onColorChange:e=>{localStorage.setItem("player_color",e),O.addEvent([Oe,e])},onNameChange:e=>{localStorage.setItem("player_name",e),O.addEvent([Ue,e])},replayOnSpeedUp:()=>{f.speedIdx+1{f.speedIdx>=1&&(f.speedIdx--,Q())},replayOnPauseToggle:()=>{f.paused=!f.paused,Q()},previewImageUrl:U,player:{background:V(),color:j(),name:Pt.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon"},disconnect:bn.disconnect,connect:v}}var ul=e({name:"game",components:{PuzzleStatus:tn,Scores:Kt,SettingsOverlay:ln,PreviewOverlay:rn,ConnectionOverlay:xn,HelpOverlay:zn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},disconnect:()=>{},connect:()=>{}}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await cl(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},setConnectionState:e=>{this.connectionState=e},togglePreview:()=>{this.toggle("preview",!1)}}))},unmounted(){this.g.disconnect()},methods:{reconnect(){this.g.connect()},toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}}});const gl={id:"game"},pl={class:"menu"},hl={class:"tabs"},ml=s("🧩 Puzzles");ul.render=function(e,i,s,r,d,c){const u=o("settings-overlay"),p=o("preview-overlay"),h=o("help-overlay"),m=o("connection-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",gl,[g(n(u,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(p,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(h,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",pl,[n("div",hl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[ml])),_:1}),n("div",{class:"opener",onClick:i[5]||(i[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var yl=e({name:"replay",components:{PuzzleStatus:tn,Scores:Kt,SettingsOverlay:ln,PreviewOverlay:rn,HelpOverlay:zn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},disconnect:()=>{}},replay:{speed:1,paused:!1}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await cl(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"replay",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},setReplaySpeed:e=>{this.replay.speed=e},setReplayPaused:e=>{this.replay.paused=e}}))},unmounted(){this.g.disconnect()},methods:{toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}},computed:{replayText(){return"Replay-Speed: "+this.replay.speed+"x"+(this.replay.paused?" Paused":"")}}});const fl={id:"replay"},wl={class:"menu"},vl={class:"tabs"},bl=s("🧩 Puzzles");yl.render=function(e,i,s,d,c,u){const p=o("settings-overlay"),h=o("preview-overlay"),m=o("help-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",fl,[g(n(p,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(m,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:l((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:i[5]||(i[5]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",wl,[n("div",vl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[bl])),_:1}),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=b({history:x(),routes:[{name:"index",path:"/",component:$},{name:"new-game",path:"/new-game",component:$t},{name:"game",path:"/g/:id",component:ul},{name:"replay",path:"/replay/:id",component:yl}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const l=C(k);l.config.globalProperties.$config=t,l.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=He.uniqId(),localStorage.setItem("ID",e)),e}(),l.use(n),l.mount("#app")})(); diff --git a/build/public/assets/index.382b265e.js b/build/public/assets/index.382b265e.js new file mode 100644 index 0000000..86fdeb8 --- /dev/null +++ b/build/public/assets/index.382b265e.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as l,b as i,r as o,o as a,e as s,t as r,F as d,f as c,g as u,h as g,v as p,i as h,j as m,k as y,l as f,m as w,n as v,p as b,q as x,s as C}from"./vendor.b622ee49.js";var k=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const A={id:"app"},T={key:0,class:"nav"},z=s("Index"),S=s("New game");k.render=function(e,s,r,d,c,u){const g=o("router-link"),p=o("router-view");return a(),t("div",A,[e.showNav?(a(),t("ul",T,[n("li",null,[n(g,{class:"btn",to:{name:"index"}},{default:l((()=>[z])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:l((()=>[S])),_:1})])])):i("",!0),n(p)])};const I=864e5,P=e=>{const t=Math.floor(e/I);e%=I;const n=Math.floor(e/36e5);e%=36e5;const l=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${l}m ${Math.floor(e/1e3)}s`};var _=1e3,D=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>P(t-e),E=P,O=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",l=e,i=t||D();return`${n} ${B(l,i)}`}}});const U={class:"game-info-text"},M=n("br",null,null,-1),N=n("br",null,null,-1),G=n("br",null,null,-1),$=s(" ↪️ Watch replay ");O.render=function(e,d,c,u,g,p){const h=o("router-link");return a(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:l((()=>[n("span",U,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),M,s(" 👥 "+r(e.game.players),1),N,s(" "+r(e.time(e.game.started,e.game.finished)),1),G])])),_:1},8,["to"]),e.game.hasReplay?(a(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:l((()=>[$])),_:1},8,["to"])):i("",!0)],4)};var R=e({components:{GameTeaser:O},data:()=>({gamesRunning:[],gamesFinished:[]}),async created(){const e=await fetch("/api/index-data"),t=await e.json();this.gamesRunning=t.gamesRunning,this.gamesFinished=t.gamesFinished}});const V=n("h1",null,"Running games",-1),j=n("h1",null,"Finished games",-1);R.render=function(e,l,i,s,r,u){const g=o("game-teaser");return a(),t("div",null,[V,(a(!0),t(d,null,c(e.gamesRunning,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128)),j,(a(!0),t(d,null,c(e.gamesFinished,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128))])};var F=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});F.render=function(e,l,i,o,s,r){return a(),t("div",{class:"imageteaser",style:e.style,onClick:l[2]||(l[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:l[1]||(l[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var L=e({name:"image-library",components:{ImageTeaser:F},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});L.render=function(e,n,l,i,s,r){const u=o("image-teaser");return a(),t("div",null,[(a(!0),t(d,null,c(e.images,((n,l)=>(a(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:l},null,8,["image","onClick","onEditClick"])))),128))])};const W={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};W.render=function(e,n,l,i,o,s){return a(),t("div",{style:s.style,title:l.title},null,12,["title"])};var q=e({name:"tags-input",props:{modelValue:{type:Array,required:!0}},emits:{"update:modelValue":null},data:()=>({input:"",values:[]}),created(){this.values=this.modelValue},methods:{onKeyUp(e){if(","===e.key)return this.add(),e.stopPropagation(),!1},add(){const e=this.input.replace(/,/g,"").trim();e&&(this.values.includes(e)||this.values.push(e),this.input="",this.$emit("update:modelValue",this.values))},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const H=m()(((e,l,i,o,s,u)=>(a(),t("div",null,[g(n("input",{class:"input",type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.input=t),placeholder:"Plants, People",onKeydown:l[2]||(l[2]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:l[3]||(l[3]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[p,e.input]]),(a(!0),t(d,null,c(e.values,((n,l)=>(a(),t("span",{key:l,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));q.render=H,q.__scopeId="data-v-771460ae";var Q=e({name:"new-image-dialog",components:{ResponsiveImage:W,TagsInput:q},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const l=new FileReader;l.readAsDataURL(n),l.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const Y={key:0,class:"has-image"},K={key:1},Z={class:"upload"},J=n("span",{class:"btn"},"Upload File",-1),X={class:"area-settings"},ee=n("td",null,[n("label",null,"Title")],-1),te=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ne=n("td",null,[n("label",null,"Tags")],-1),le={class:"area-buttons"},ie=s("🧩 Post to gallery "),oe=n("br",null,null,-1),ae=s(" + set up game");Q.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay new-image-dialog",onClick:l[8]||(l[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[7]||(l[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(a(),t("div",Y,[n("span",{class:"remove btn",onClick:l[1]||(l[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(a(),t("div",K,[n("label",Z,[n("input",{type:"file",style:{display:"none"},onChange:l[2]||(l[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),J])]))],2),n("div",X,[n("table",null,[n("tr",null,[ee,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[3]||(l[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),te,n("tr",null,[ne,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[4]||(l[4]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",le,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:l[5]||(l[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:l[6]||(l[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[ie,oe,ae],8,["disabled"])])])])};var se=e({name:"edit-image-dialog",components:{ResponsiveImage:W,TagsInput:q},props:{image:{type:Object,required:!0}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const re={class:"area-image"},de={class:"has-image"},ce={class:"area-settings"},ue=n("td",null,[n("label",null,"Title")],-1),ge=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),pe=n("td",null,[n("label",null,"Tags")],-1),he={class:"area-buttons"};function me(e,t){const n=e.x-t.x,l=e.y-t.y;return Math.sqrt(n*n+l*l)}function ye(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}se.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay edit-image-dialog",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("div",re,[n("div",de,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ce,[n("table",null,[n("tr",null,[ue,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ge,n("tr",null,[pe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[2]||(l[2]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",he,[n("button",{class:"btn",onClick:l[3]||(l[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var fe={pointSub:function(e,t){return{x:e.x-t.x,y:e.y-t.y}},pointAdd:function(e,t){return{x:e.x+t.x,y:e.y+t.y}},pointDistance:me,pointInBounds:function(e,t){return e.x>=t.x&&e.x<=t.x+t.w&&e.y>=t.y&&e.y<=t.y+t.h},rectCenter:ye,rectMoved:function(e,t,n){return{x:e.x+t,y:e.y+n,w:e.w,h:e.h}},rectCenterDistance:function(e,t){return me(ye(e),ye(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};var we=1,ve=4,be=2,xe=3,Ce=2,ke=4,Ae=3,Te=9,ze=1,Se=2,Ie=3,Pe=4,_e=5,De=6,Be=7,Ee=8,Oe=10,Ue=1,Me=2,Ne=3;class Ge{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){return this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295,e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),l=t[n];t[n]=t[e],t[e]=l}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new Ge(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const $e=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},Re=(...e)=>{const t=t=>(...n)=>{const l=new Date,i=$e(l.getHours(),"00"),o=$e(l.getMinutes(),"00"),a=$e(l.getSeconds(),"00");console[t](`${i}:${o}:${a}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var Ve,je,Fe,Le,We={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodeTile:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodeTile:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return Array.isArray(e)?e:[e.id,e.rng.type,Ge.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode]},decodeGame:function(e){return Array.isArray(e)?{id:e[0],rng:{type:e[1],obj:Ge.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}:e},coordByTileIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(let n in e){const l=[n,e[n]].map(encodeURIComponent);t.push(l.join("="))}return 0===t.length?"":`?${t.join("&")}`}};(je=Ve||(Ve={}))[je.Flat=0]="Flat",je[je.Out=1]="Out",je[je.In=-1]="In",(Le=Fe||(Fe={}))[Le.FINAL=0]="FINAL",Le[Le.ANY=1]="ANY";const qe={};function He(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}function Qe(e,t){let n=0;for(let l of qe[e].players){if(We.decodePlayer(l).id===t)return n;n++}return-1}function Ye(e,t){const n=Qe(e,t);return-1===n?null:We.decodePlayer(qe[e].players[n])}function Ke(e,t,n){const l=Qe(e,t);-1===l?qe[e].players.push(We.encodePlayer(n)):qe[e].players[l]=We.encodePlayer(n)}function Ze(e,t){return-1!==Qe(e,t)}function Je(e){return qe[e]?qe[e].players.map(We.decodePlayer):[]}function Xe(e){return qe[e].puzzle.tiles.length}function et(e){return qe[e].scoreMode||0}function tt(e){return nt(e)===Xe(e)}function nt(e){let t=0;for(let n of qe[e].puzzle.tiles)-1===We.decodeTile(n).owner&&t++;return t}function lt(e,t,n){const l=Ye(e,t);if(null!==l){for(let e of Object.keys(n))l[e]=n[e];Ke(e,t,l)}}function it(e,t){for(let n of Object.keys(t))qe[e].puzzle.data[n]=t[n]}function ot(e,t,n){for(let l of Object.keys(n)){const i=We.decodeTile(qe[e].puzzle.tiles[t]);i[l]=n[l],qe[e].puzzle.tiles[t]=We.encodeTile(i)}}const at=(e,t)=>We.decodeTile(qe[e].puzzle.tiles[t]),st=(e,t)=>at(e,t).group,rt=(e,t)=>{const n=qe[e].puzzle.info,l={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},i=function(e,t){const n=qe[e].puzzle.info,l=We.coordByTileIdx(n,t),i=l.x*n.tileSize,o=l.y*n.tileSize;return{x:i,y:o}}(e,t);return fe.pointAdd(l,i)},dt=(e,t)=>at(e,t).pos,ct=e=>{const t=Tt(e),n=zt(e),l=Math.round(t/4),i=Math.round(n/4);return{x:0-l,y:0-i,w:t+2*l,h:n+2*i}},ut=(e,t)=>{const n=mt(e),l=at(e,t);return{x:l.pos.x,y:l.pos.y,w:n,h:n}},gt=(e,t)=>at(e,t).z,pt=(e,t)=>{for(let n of qe[e].puzzle.tiles){const e=We.decodeTile(n);if(e.owner===t)return e.idx}return-1},ht=e=>qe[e].puzzle.info.tileDrawSize,mt=e=>qe[e].puzzle.info.tileSize,yt=e=>qe[e].puzzle.data.maxGroup,ft=e=>qe[e].puzzle.data.maxZ;function wt(e,t){const n=qe[e].puzzle.info,l=We.coordByTileIdx(n,t);return[l.y>0?t-n.tilesX:-1,l.x0?t-1:-1]}const vt=(e,t,n)=>{for(let l of t)ot(e,l,{z:n})},bt=(e,t,n)=>{const l=dt(e,t);ot(e,t,{pos:fe.pointAdd(l,n)})},xt=(e,t,n)=>{const l=ht(e),i=ct(e),o=n;for(let a of t){const t=at(e,a);t.pos.x+n.xi.x+i.w&&(o.x=Math.min(i.x+i.w-t.pos.x+l,o.x)),t.pos.y+n.yi.y+i.h&&(o.y=Math.min(i.y+i.h-t.pos.y+l,o.y))}for(let a of t)bt(e,a,o)},Ct=(e,t,n)=>{for(let l of t)ot(e,l,{owner:n})};function kt(e,t){const n=qe[e].puzzle.tiles,l=We.decodeTile(n[t]),i=[];if(l.group)for(let o of n){const e=We.decodeTile(o);e.group===l.group&&i.push(e.idx)}else i.push(l.idx);return i}const At=(e,t)=>{const n=Ye(e,t);return n?n.points:0},Tt=e=>qe[e].puzzle.info.table.width,zt=e=>qe[e].puzzle.info.table.height;var St={__createPlayerObject:He,setGame:function(e,t){qe[e]=t},exists:function(e){return!!qe[e]||!1},playerExists:Ze,getActivePlayers:function(e,t){const n=t-30*_;return Je(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return Je(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Ze(e,t)?lt(e,t,{ts:n}):Ke(e,t,He(t,n))},getFinishedTileCount:nt,getTileCount:Xe,getImageUrl:function(e){return qe[e].puzzle.info.imageUrl},setImageUrl:function(e,t){qe[e].puzzle.info.imageUrl=t},get:function(e){return qe[e]},getAllGames:function(){return Object.values(qe).sort(((e,t)=>tt(e.id)===tt(t.id)?t.puzzle.data.started-e.puzzle.data.started:tt(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Ye(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Ye(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Ye(e,t);return n?n.name:null},getPlayerIndexById:Qe,getPlayerIdByIndex:function(e,t){return qe[e].players.length>t?We.decodePlayer(qe[e].players[t]).id:null},changePlayer:lt,setPlayer:Ke,setTile:function(e,t,n){qe[e].puzzle.tiles[t]=We.encodeTile(n)},setPuzzleData:function(e,t){qe[e].puzzle.data=t},getTableWidth:Tt,getTableHeight:zt,getPuzzle:e=>qe[e].puzzle,getRng:e=>qe[e].rng.obj,getPuzzleWidth:e=>qe[e].puzzle.info.width,getPuzzleHeight:e=>qe[e].puzzle.info.height,getTilesSortedByZIndex:function(e){return qe[e].puzzle.tiles.map(We.decodeTile).sort(((e,t)=>e.z-t.z))},getFirstOwnedTile:(e,t)=>{const n=pt(e,t);return n<0?null:qe[e].puzzle.tiles[n]},getTileDrawOffset:e=>qe[e].puzzle.info.tileDrawOffset,getTileDrawSize:ht,getFinalTilePos:rt,getStartTs:e=>qe[e].puzzle.data.started,getFinishTs:e=>qe[e].puzzle.data.finished,handleInput:function(e,t,n,l){const i=qe[e].puzzle,o=function(e,t){return t in qe[e].evtInfos?qe[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),a=[],s=()=>{a.push([Ue,i.data])},r=t=>{a.push([Me,We.encodeTile(at(e,t))])},d=e=>{for(const t of e)r(t)},c=()=>{const n=Ye(e,t);n&&a.push([Ne,We.encodePlayer(n)])},u=n[0];if(u===De){const i=n[1];lt(e,t,{bgcolor:i,ts:l}),c()}else if(u===Be){const i=n[1];lt(e,t,{color:i,ts:l}),c()}else if(u===Ee){const i=`${n[1]}`.substr(0,16);lt(e,t,{name:i,ts:l}),c()}else if(u===ze){const i={x:n[1],y:n[2]};lt(e,t,{d:1,ts:l}),c(),o._last_mouse_down=i;const a=((e,t)=>{let n=qe[e].puzzle.info,l=qe[e].puzzle.tiles,i=-1,o=-1;for(let a=0;ai)&&(i=e.z,o=a)}return o})(e,i);if(a>=0){let n=ft(e)+1;it(e,{maxZ:n}),s();const l=kt(e,a);vt(e,l,ft(e)),Ct(e,l,t),d(l)}o._last_mouse=i}else if(u===Ie){const i=n[1],a=n[2],s={x:i,y:a};if(null===o._last_mouse_down)lt(e,t,{x:i,y:a,ts:l}),c();else{let n=pt(e,t);if(n>=0){lt(e,t,{x:i,y:a,ts:l}),c();const r=kt(e,n);let u=fe.pointInBounds(s,ct(e))&&fe.pointInBounds(o._last_mouse_down,ct(e));for(let t of r){const n=ut(e,t);if(fe.pointInBounds(s,n)){u=!0;break}}if(u){const t=i-o._last_mouse_down.x,n=a-o._last_mouse_down.y;xt(e,r,{x:t,y:n}),d(r)}}else lt(e,t,{ts:l}),c();o._last_mouse_down=s}o._last_mouse=s}else if(u===Se){const a={x:n[1],y:n[2]},u=0;o._last_mouse_down=null;let g=pt(e,t);if(g>=0){let n=kt(e,g);Ct(e,n,0),d(n);let o=dt(e,g),a=rt(e,g);if(fe.pointDistance(a,o){for(let n of t)ot(e,n,{owner:-1,z:1})})(e,n),d(n);let r=At(e,t);0===et(e)?r+=n.length:1===et(e)&&(r+=1),lt(e,t,{d:u,ts:l,points:r}),c(),nt(e)===Xe(e)&&(it(e,{finished:l}),s())}else{const n=(e,t,n,l)=>{let i=qe[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const l=st(e,t),i=st(e,n);return!(!l||l!==i)})(e,t,n))return!1;const o=dt(e,t),a=fe.pointAdd(dt(e,n),{x:l[0]*i.tileSize,y:l[1]*i.tileSize});if(fe.pointDistance(o,a){const l=qe[e].puzzle.tiles,i=st(e,t),o=st(e,n);let a;const d=[];i&&d.push(i),o&&d.push(o),i?a=i:o?a=o:(it(e,{maxGroup:yt(e)+1}),s(),a=yt(e));if(ot(e,t,{group:a}),r(t),ot(e,n,{group:a}),r(n),d.length>0)for(const s of l){const t=We.decodeTile(s);d.includes(t.group)&&(ot(e,t.idx,{group:a}),r(t.idx))}})(e,t,n),i=kt(e,t);const c=((e,t)=>{let n=0;for(let l of t){let t=gt(e,l);t>n&&(n=t)}return n})(e,i);return vt(e,i,c),d(i),!0}return!1};let i=!1;for(let t of kt(e,g)){let l=wt(e,t);if(n(e,t,l[0],[0,1])||n(e,t,l[1],[-1,0])||n(e,t,l[2],[0,-1])||n(e,t,l[3],[1,0])){i=!0;break}}if(i&&1===et(e)){const n=At(e,t)+1;lt(e,t,{d:u,ts:l,points:n}),c()}else lt(e,t,{d:u,ts:l}),c()}}else lt(e,t,{d:u,ts:l}),c();o._last_mouse=a}else if(u===Pe){const i=n[1],a=n[2];lt(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else if(u===_e){const i=n[1],a=n[2];lt(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else lt(e,t,{ts:l}),c();return function(e,t,n){qe[e].evtInfos[t]=n}(e,t,o),a}},It=e({name:"new-game-dialog",components:{ResponsiveImage:W},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Fe.ANY}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Pt={class:"area-image"},_t={class:"has-image"},Dt={class:"area-settings"},Bt=n("td",null,[n("label",null,"Pieces")],-1),Et=n("td",null,[n("label",null,"Scoring: ")],-1),Ot=s(" Any (Score when pieces are connected to each other or on final location)"),Ut=n("br",null,null,-1),Mt=s(" Final (Score when pieces are put to their final location)"),Nt={class:"area-buttons"};It.render=function(e,l,i,s,r,d){const c=o("responsive-image");return a(),t("div",{class:"overlay new-game-dialog",onClick:l[6]||(l[6]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[5]||(l[5]=u((()=>{}),["stop"]))},[n("div",Pt,[n("div",_t,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Dt,[n("table",null,[n("tr",null,[Bt,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Et,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[2]||(l[2]=t=>e.scoreMode=t),value:"1"},null,512),[[y,e.scoreMode]]),Ot]),Ut,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[3]||(l[3]=t=>e.scoreMode=t),value:"0"},null,512),[[y,e.scoreMode]]),Mt])])])])]),n("div",Nt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:l[4]||(l[4]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var Gt=e({components:{ImageLibrary:L,NewImageDialog:Q,EditImageDialog:se,NewGameDialog:It},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},methods:{toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${We.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const $t={class:"upload-image-teaser"},Rt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),Vt={key:0},jt=s(" Tags: "),Ft=s(" Sort by: "),Lt=n("option",{value:"date_desc"},"Newest first",-1),Wt=n("option",{value:"date_asc"},"Oldest first",-1),qt=n("option",{value:"alpha_asc"},"A-Z",-1),Ht=n("option",{value:"alpha_desc"},"Z-A",-1);Gt.render=function(e,l,s,u,p,h){const m=o("image-library"),y=o("new-image-dialog"),w=o("edit-image-dialog"),v=o("new-game-dialog");return a(),t("div",null,[n("div",$t,[n("div",{class:"btn btn-big",onClick:l[1]||(l[1]=t=>e.dialog="new-image")},"Upload your image"),Rt]),n("div",null,[e.tags.length>0?(a(),t("label",Vt,[jt,(a(!0),t(d,null,c(e.tags,((n,l)=>(a(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:l,onClick:t=>e.toggleTag(n)},r(n.title),11,["onClick"])))),128))])):i("",!0),n("label",null,[Ft,g(n("select",{"onUpdate:modelValue":l[2]||(l[2]=t=>e.filters.sort=t),onChange:l[3]||(l[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[Lt,Wt,qt,Ht],544),[[f,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(a(),t(y,{key:0,onBgclick:l[4]||(l[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["onPostToGalleryClick","onSetupGameClick"])):i("",!0),"edit-image"===e.dialog?(a(),t(w,{key:1,onBgclick:l[5]||(l[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["onSaveClick","image"])):i("",!0),e.image&&"new-game"===e.dialog?(a(),t(v,{key:2,onBgclick:l[6]||(l[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):i("",!0)])};var Qt=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"},Kt=n("div",null,"Scores",-1),Zt=n("td",null,"⚡",-1),Jt=n("td",null,"💤",-1);Qt.render=function(e,l,i,o,s,u){return a(),t("div",Yt,[Kt,n("table",null,[(a(!0),t(d,null,c(e.actives,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[Zt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(a(!0),t(d,null,c(e.idles,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[Jt,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 E(this.duration)}}});const en={class:"timer"};Xt.render=function(e,l,i,o,s,d){return a(),t("div",en,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),w(e.$slots,"default")])};var tn=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 nn=n("td",null,[n("label",null,"Background: ")],-1),ln=n("td",null,[n("label",null,"Color: ")],-1),on=n("td",null,[n("label",null,"Name: ")],-1);tn.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("tr",null,[nn,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[1]||(l[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[ln,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[2]||(l[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[on,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":l[3]||(l[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])])])])};var an=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const sn={class:"preview"};an.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay",onClick:l[1]||(l[1]=t=>e.$emit("bgclick"))},[n("div",sn,[n("div",{class:"img",style:e.previewStyle},null,4)])])};const rn=Re("Communication.js");let dn,cn=e=>{},un=e=>{};let gn=0;const pn=e=>{gn!==e&&(gn=e,un(e))};function hn(e){if(2===gn)try{dn.send(JSON.stringify(e))}catch(t){rn.info("unable to send message.. maybe because ws is invalid?")}}let mn,yn;var fn={connect:function(e,t,n){return mn=0,yn={},pn(3),new Promise((l=>{dn=new WebSocket(e,n+"|"+t),dn.onopen=e=>{pn(2),hn([xe])},dn.onmessage=e=>{const t=JSON.parse(e.data),i=t[0];if(i===ve){const e=t[1];l(e)}else{if(i!==we)throw`[ 2021-05-09 invalid connect msgType ${i} ]`;{const e=t[1],l=t[2];if(e===n&&yn[l])return void delete yn[l];cn(t)}}},dn.onerror=e=>{throw pn(1),"[ 2021-05-15 onerror ]"},dn.onclose=e=>{4e3===e.code||1001===e.code?pn(4):pn(1)}}))},requestReplayData:async function(e,t,n){const l=await fetch(`/api/replay-data?gameId=${e}&offset=${t}&size=${n}`);return await l.json()},disconnect:function(){dn&&dn.close(4e3),mn=0,yn={}},sendClientEvent:function(e){mn++,yn[mn]=e,hn([be,mn,yn[mn]])},onServerChange:function(e){cn=e},onConnectionStateChange:function(e){un=e},CODE_CUSTOM_DISCONNECT:4e3,CONN_STATE_NOT_CONNECTED:0,CONN_STATE_DISCONNECTED:1,CONN_STATE_CLOSED:4,CONN_STATE_CONNECTED:2,CONN_STATE_CONNECTING:3},wn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===fn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===fn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const vn={key:0,class:"overlay connection-lost"},bn={key:0,class:"overlay-content"},xn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Cn={key:1,class:"overlay-content"},kn=n("div",null,"Connecting...",-1);wn.render=function(e,l,o,s,r,d){return e.show?(a(),t("div",vn,[e.lostConnection?(a(),t("div",bn,[xn,n("span",{class:"btn",onClick:l[1]||(l[1]=t=>e.$emit("reconnect"))},"Reconnect")])):i("",!0),e.connecting?(a(),t("div",Cn,[kn])):i("",!0)])):i("",!0)};var An=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"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),zn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),Sn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),In=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),Pn=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),_n=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),Dn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),Bn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),En=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);An.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[Tn,zn,Sn,In,Pn,_n,Dn,Bn,En,On])])};var Un=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Mn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Gn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function $n(){let e=0,t=0,n=1;const l=(l,i)=>{e+=l/n,t+=i/n},i=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},o=l=>({x:l.x/n-e,y:l.y/n-t}),a=l=>({x:(l.x+e)*n,y:(l.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n});return{move:l,canZoom:e=>n!=i(e),zoom:(e,t)=>((e,t)=>{if(n==e)return!1;const i=1-n/e;return l(-t.x*i,-t.y*i),n=e,!0})(i(e),t),worldToViewport:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:a,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=o(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:o}}function Rn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Vn={createCanvas:Rn,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=Rn(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=Rn(e.width,e.height),i=l.getContext("2d");return i.save(),i.drawImage(t,0,0),i.fillStyle=n,i.globalCompositeOperation="source-in",i.fillRect(0,0,t.width,t.height),i.restore(),i.save(),i.globalCompositeOperation="destination-over",i.drawImage(e,0,0),i.restore(),l}};const jn=Re("Debug.js");let Fn=0,Ln=0;var Wn=e=>{Fn=performance.now(),Ln=e},qn=e=>{const t=performance.now(),n=t-Fn;n>Ln&&jn.log(e+": "+n),Fn=t};const Hn=Re("PuzzleGraphics.js");function Qn(e,t){const n=We.coordByTileIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Yn={loadPuzzleBitmaps:async function(e){const t=await Vn.loadImageToBitmap(e.info.imageUrl),n=await Vn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Hn.log("start createPuzzleTileBitmaps");var l=n.tileSize,i=n.tileMarginWidth,o=n.tileDrawSize,a=l/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0];const r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,o={x:i,y:i},r=fe.pointAdd(o,{x:l,y:0}),c=fe.pointAdd(r,{x:0,y:l}),u=fe.pointSub(c,{x:l,y:0});if(n.moveTo(o.x,o.y),0!==e.top)for(let l=0;l=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Jn=t/2,Xn=t-Jn;const n=1/4*this.canvas.width/(t/2);Kn=-n,Zn=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new el(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new el(this.rng));const e=[];for(;this.explodedBombs.length>0;){const t=this.explodedBombs.shift();if(!t)break;t.update(),t.alive&&e.push(t)}this.explodedBombs=e;const t=[];for(;this.readyBombs.length>0;){const e=this.readyBombs.shift();if(!e)break;e.update(this.particles),e.hasExploded?this.explodedBombs.push(e):t.push(e)}this.readyBombs=t;const n=[];for(;this.particles.length>0;){const e=this.particles.shift();if(!e)break;e.update(),e.alive&&n.push(e)}this.particles=n}render(){this.ctx.beginPath(),this.ctx.fillStyle="rgba(0, 0, 0, 0.1)",this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height);for(let e=0;e{const t=e.color+" "+e.d;if(!h[t]){const n=e.d?a:s;if(e.color){const l=e.d?r:d;h[t]=await createImageBitmap(Vn.colorizedCanvas(n,l,e.color))}else h[t]=n}return h[t]},y=function(e,t){return t.width=window.innerWidth,t.height=window.innerHeight,e.appendChild(t),window.addEventListener("resize",(()=>{t.width=window.innerWidth,t.height=window.innerHeight,al=!0})),t}(i,Vn.createCanvas()),f={final:!1,requesting:!0,log:[],logPointer:0,logIdx:0,speeds:[.5,1,2,5,10,20,50],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0};fn.onConnectionStateChange((e=>{o.setConnectionState(e)}));let w=()=>0;const v=async()=>{if("play"===l){const l=await fn.connect(n,e,t),i=We.decodeGame(l);St.setGame(i.id,i),w=()=>D()}else{if("replay"!==l)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await fn.requestReplayData(e,0,1e4),n=We.decodeGame(t.game);St.setGame(n.id,n),f.requesting=!1,f.log=t.log,f.lastRealTs=D(),f.gameStartTs=parseInt(f.log[0][4],10),f.lastGameTs=f.gameStartTs,w=()=>f.lastGameTs}}al=!0};await v();const b=St.getTileDrawOffset(e),x=St.getTileDrawSize(e),C=St.getPuzzleWidth(e),k=St.getPuzzleHeight(e),A=St.getTableWidth(e),T=St.getTableHeight(e),z={x:(A-C)/2,y:(T-k)/2},S={w:C,h:k},I={w:x,h:x},P=await Yn.loadPuzzleBitmaps(St.getPuzzle(e)),_=new nl(y,St.getRng(e));_.init();const B=y.getContext("2d");y.classList.add("loaded");const E=$n();E.move(-(A-y.width)/2,-(T-y.height)/2);const O=function(e,t,n){let l=[],i=!0,o=!1,a=!1,s=!1,r=!1,d=!1,c=!1,u=!1;const g=(e,t)=>{const l=n.viewportToWorld({x:e,y:t});return[l.x,l.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{i&&("Shift"===t.key?u=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?s=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?r=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?o=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?a=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};e.addEventListener("mousedown",(e=>{0===e.button&&y([ze,...p(e)])})),e.addEventListener("mouseup",(e=>{0===e.button&&y([Se,...p(e)])})),e.addEventListener("mousemove",(e=>{y([Ie,...p(e)])})),e.addEventListener("wheel",(e=>{if(n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Pe:_e;y([t,...p(e)])}})),t.addEventListener("keydown",(e=>m(!0,e))),t.addEventListener("keyup",(e=>m(!1,e))),t.addEventListener("keypress",(e=>{i&&(" "===e.key&&y([Oe]),"F"!==e.key&&"f"!==e.key||(il=!il,al=!0),"G"!==e.key&&"g"!==e.key||(ol=!ol,al=!0))}));const y=e=>{l.push(e)};return{addEvent:y,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=u?20:10,t=(o?e:0)-(a?e:0),l=(s?e:0)-(r?e:0);0===t&&0===l||y([Te,t,l]),d&&c||(d?n.canZoom("in")&&y([Pe,...h()]):c&&n.canZoom("out")&&y([_e,...h()]))},setHotkeys:e=>{i=e}}}(y,window,E),U=St.getImageUrl(e),M=()=>{const t=St.getStartTs(e),n=St.getFinishTs(e),l=w();o.setFinished(!!n),o.setDuration((n||l)-t)};M(),o.setPiecesDone(St.getFinishedTileCount(e)),o.setPiecesTotal(St.getTileCount(e));const N=w();o.setActivePlayers(St.getActivePlayers(e,N)),o.setIdlePlayers(St.getIdlePlayers(e,N));const G=!!St.getFinishTs(e);let $=G;const R=()=>$&&!G,V=()=>St.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",j=()=>St.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let F="",L="",W=!1;const q=e=>{W=e;const[t,n]=e?[F,"grab"]:[L,"default"];y.style.cursor=`url('${t}') ${u} ${p}, ${n}`},H=e=>{F=Vn.colorizedCanvas(a,r,e).toDataURL(),L=Vn.colorizedCanvas(s,d,e).toDataURL(),q(W)};H(j());const Q=()=>{o.setReplaySpeed&&o.setReplaySpeed(f.speeds[f.speedIdx]),o.setReplayPaused&&o.setReplayPaused(f.paused)};if("play"===l?setInterval(M,1e3):"replay"===l&&Q(),"play"===l)fn.onServerChange((n=>{n[0],n[1],n[2];const l=n[3];for(const[i,o]of l)switch(i){case Ne:{const n=We.decodePlayer(o);n.id!==t&&(St.setPlayer(e,n.id,n),al=!0)}break;case Me:{const t=We.decodeTile(o);St.setTile(e,t.idx,t),al=!0}break;case Ue:St.setPuzzleData(e,o),al=!0}$=!!St.getFinishTs(e)}));else if("replay"===l){let t=setInterval((()=>{const n=D();if(f.requesting)return void(f.lastRealTs=n);if(f.logPointer+1>=f.log.length)return f.lastRealTs=n,f.requesting=!0,void(async(e,t,n)=>{const l=await fn.requestReplayData(e,t,n);f.log=f.log.slice(f.logPointer),f.logPointer=0,f.log.push(...l.log),l.log.length<1e4&&(f.final=!0),f.requesting=!1})(e,f.logIdx,1e4);if(f.paused)return void(f.lastRealTs=n);const l=(n-f.lastRealTs)*f.speeds[f.speedIdx],i=f.lastGameTs+l;for(;;){if(f.paused)break;const n=f.logPointer+1;if(n>=f.log.length){f.final&&clearInterval(t);break}const l=f.log[n],o=f.gameStartTs+l[l.length-1];if(o>i)break;const a=l.slice();if(a[0]===Ce){const t=a[1];St.addPlayer(e,t,o),al=!0}else if(a[0]===ke){const t=St.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";St.addPlayer(e,t,o),al=!0}else if(a[0]===Ae){const t=St.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=a[2];St.handleInput(e,t,n,o),al=!0}f.logPointer=n,f.logIdx++}f.lastRealTs=n,f.lastGameTs=i,M()}),50)}let Y=null;return(e=>{const t=e.fps||60,n=e.slow||1,l=e.update,i=e.render,o=window.requestAnimationFrame,a=1/t,s=n*a;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>s;)d-=s,l(a);i(d/n),c=r,o(u)};o(u)})({update:()=>{O.createKeyEvents();for(const n of O.consumeAll())if("play"===l){const l=n[0];if(l===Te){const e=n[1],t=n[2];al=!0,E.move(e,t)}else if(l===Ie){if(Y&&!St.getFirstOwnedTile(e,t)){const e={x:n[1],y:n[2]},t=E.worldToViewport(e),l=Math.round(t.x-Y.x),i=Math.round(t.y-Y.y);al=!0,E.move(l,i),Y=t}}else if(l===Be)H(n[1]);else if(l===ze){const e={x:n[1],y:n[2]};Y=E.worldToViewport(e),q(!0)}else if(l===Se)Y=null,q(!1);else if(l===Pe){const e={x:n[1],y:n[2]};al=!0,E.zoom("in",E.worldToViewport(e))}else if(l===_e){const e={x:n[1],y:n[2]};al=!0,E.zoom("out",E.worldToViewport(e))}else l===Oe&&o.togglePreview();const i=w();St.handleInput(e,t,n,i).length>0&&(al=!0),fn.sendClientEvent(n)}else if("replay"===l){const e=n[0];if(e===Te){const e=n[1],t=n[2];al=!0,E.move(e,t)}else if(e===Ie){if(Y){const e={x:n[1],y:n[2]},t=E.worldToViewport(e),l=Math.round(t.x-Y.x),i=Math.round(t.y-Y.y);al=!0,E.move(l,i),Y=t}}else if(e===ze){const e={x:n[1],y:n[2]};Y=E.worldToViewport(e)}else if(e===Se)Y=null;else if(e===Pe){const e={x:n[1],y:n[2]};al=!0,E.zoom("in",E.worldToViewport(e))}else if(e===_e){const e={x:n[1],y:n[2]};al=!0,E.zoom("out",E.worldToViewport(e))}else e===Oe&&o.togglePreview()}$=!!St.getFinishTs(e),R()&&(_.update(),al=!0)},render:async()=>{if(!al)return;const n=w();let i,a,s;window.DEBUG&&Wn(0),B.fillStyle=V(),B.fillRect(0,0,y.width,y.height),window.DEBUG&&qn("clear done"),i=E.worldToViewportRaw(z),a=E.worldDimToViewportRaw(S),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(i.x,i.y,a.w,a.h),window.DEBUG&&qn("board done");const r=St.getTilesSortedByZIndex(e);window.DEBUG&&qn("get tiles done"),a=E.worldDimToViewportRaw(I);for(const e of r)(-1===e.owner?il:ol)&&(s=P[e.idx],i=E.worldToViewportRaw({x:b+e.pos.x,y:b+e.pos.y}),B.drawImage(s,0,0,s.width,s.height,i.x,i.y,a.w,a.h));window.DEBUG&&qn("tiles done");const d=[];for(const o of St.getActivePlayers(e,n))c=o,("replay"===l||c.id!==t)&&(s=await m(o),i=E.worldToViewport(o),B.drawImage(s,i.x-u,i.y-p),d.push([`${o.name} (${o.points})`,i.x,i.y+g]));var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,l]of d)B.fillText(e,t,l);window.DEBUG&&qn("players done"),o.setActivePlayers(St.getActivePlayers(e,n)),o.setIdlePlayers(St.getIdlePlayers(e,n)),o.setPiecesDone(St.getFinishedTileCount(e)),window.DEBUG&&qn("HUD done"),R()&&_.render(),al=!1}}),{setHotkeys:e=>{O.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),O.addEvent([De,e])},onColorChange:e=>{localStorage.setItem("player_color",e),O.addEvent([Be,e])},onNameChange:e=>{localStorage.setItem("player_name",e),O.addEvent([Ee,e])},replayOnSpeedUp:()=>{f.speedIdx+1{f.speedIdx>=1&&(f.speedIdx--,Q())},replayOnPauseToggle:()=>{f.paused=!f.paused,Q()},previewImageUrl:U,player:{background:V(),color:j(),name:St.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon"},disconnect:fn.disconnect,connect:v}}var rl=e({name:"game",components:{PuzzleStatus:Xt,Scores:Qt,SettingsOverlay:tn,PreviewOverlay:an,ConnectionOverlay:wn,HelpOverlay:An},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},disconnect:()=>{},connect:()=>{}}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await sl(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},setConnectionState:e=>{this.connectionState=e},togglePreview:()=>{this.toggle("preview",!1)}}))},unmounted(){this.g.disconnect()},methods:{reconnect(){this.g.connect()},toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}}});const dl={id:"game"},cl={class:"menu"},ul={class:"tabs"},gl=s("🧩 Puzzles");rl.render=function(e,i,s,r,d,c){const u=o("settings-overlay"),p=o("preview-overlay"),h=o("help-overlay"),m=o("connection-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",dl,[g(n(u,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(p,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(h,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",cl,[n("div",ul,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[gl])),_:1}),n("div",{class:"opener",onClick:i[5]||(i[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var pl=e({name:"replay",components:{PuzzleStatus:Xt,Scores:Qt,SettingsOverlay:tn,PreviewOverlay:an,HelpOverlay:An},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},disconnect:()=>{}},replay:{speed:1,paused:!1}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await sl(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"replay",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},setReplaySpeed:e=>{this.replay.speed=e},setReplayPaused:e=>{this.replay.paused=e}}))},unmounted(){this.g.disconnect()},methods:{toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}},computed:{replayText(){return"Replay-Speed: "+this.replay.speed+"x"+(this.replay.paused?" Paused":"")}}});const hl={id:"replay"},ml={class:"menu"},yl={class:"tabs"},fl=s("🧩 Puzzles");pl.render=function(e,i,s,d,c,u){const p=o("settings-overlay"),h=o("preview-overlay"),m=o("help-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",hl,[g(n(p,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(m,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:l((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:i[5]||(i[5]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",ml,[n("div",yl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[fl])),_:1}),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=b({history:x(),routes:[{name:"index",path:"/",component:R},{name:"new-game",path:"/new-game",component:Gt},{name:"game",path:"/g/:id",component:rl},{name:"replay",path:"/replay/:id",component:pl}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const l=C(k);l.config.globalProperties.$config=t,l.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=We.uniqId(),localStorage.setItem("ID",e)),e}(),l.use(n),l.mount("#app")})(); diff --git a/build/public/index.html b/build/public/index.html index 5deb455..24e70df 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 c1ad089..65aee8e 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -320,10 +320,8 @@ EV_SERVER_INIT: event sent to one client after that client */ const EV_SERVER_EVENT = 1; const EV_SERVER_INIT = 4; -const EV_SERVER_REPLAY_DATA = 5; const EV_CLIENT_EVENT = 2; const EV_CLIENT_INIT = 3; -const EV_CLIENT_REPLAY_DATA = 6; const LOG_HEADER = 1; const LOG_ADD_PLAYER = 2; const LOG_UPDATE_PLAYER = 4; @@ -344,10 +342,8 @@ const CHANGE_PLAYER = 3; var Protocol = { EV_SERVER_EVENT, EV_SERVER_INIT, - EV_SERVER_REPLAY_DATA, EV_CLIENT_EVENT, EV_CLIENT_INIT, - EV_CLIENT_REPLAY_DATA, LOG_HEADER, LOG_ADD_PLAYER, LOG_UPDATE_PLAYER, @@ -1932,6 +1928,22 @@ app.get('/api/conf', (req, res) => { WS_ADDRESS: config.ws.connectstring, }); }); +app.get('/api/replay-data', async (req, res) => { + const q = req.query; + const gameId = q.gameId || ''; + if (!GameLog.exists(q.gameId)) { + throw `[gamelog ${gameId} does not exist... ]`; + } + const offset = parseInt(q.offset, 10) || 0; + const size = parseInt(q.size, 10) || 10000; + const log = await GameLog.get(gameId, offset, size); + 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); + } + res.send({ log, game: game ? Util.encodeGame(game) : null }); +}); app.get('/api/newgame-data', (req, res) => { const q = req.query; const tagSlugs = q.tags ? q.tags.split(',') : []; @@ -2047,22 +2059,6 @@ wss.on('message', async ({ socket, data }) => { const msg = JSON.parse(data); const msgType = msg[0]; switch (msgType) { - case Protocol.EV_CLIENT_REPLAY_DATA: - { - if (!GameLog.exists(gameId)) { - throw `[gamelog ${gameId} does not exist... ]`; - } - const offset = msg[1]; - const size = msg[2]; - const log = await GameLog.get(gameId, offset, size); - 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); - } - notify([Protocol.EV_SERVER_REPLAY_DATA, log, game ? Util.encodeGame(game) : null], [socket]); - } - break; case Protocol.EV_CLIENT_INIT: { if (!Game.exists(gameId)) { diff --git a/src/common/Protocol.ts b/src/common/Protocol.ts index baa0280..421b80a 100644 --- a/src/common/Protocol.ts +++ b/src/common/Protocol.ts @@ -40,10 +40,8 @@ EV_SERVER_INIT: event sent to one client after that client */ const EV_SERVER_EVENT = 1 const EV_SERVER_INIT = 4 -const EV_SERVER_REPLAY_DATA = 5 const EV_CLIENT_EVENT = 2 const EV_CLIENT_INIT = 3 -const EV_CLIENT_REPLAY_DATA = 6 const LOG_HEADER = 1 const LOG_ADD_PLAYER = 2 @@ -68,10 +66,8 @@ const CHANGE_PLAYER = 3 export default { EV_SERVER_EVENT, EV_SERVER_INIT, - EV_SERVER_REPLAY_DATA, EV_CLIENT_EVENT, EV_CLIENT_INIT, - EV_CLIENT_REPLAY_DATA, LOG_HEADER, LOG_ADD_PLAYER, diff --git a/src/frontend/Communication.ts b/src/frontend/Communication.ts index e4d7d8f..7b62fff 100644 --- a/src/frontend/Communication.ts +++ b/src/frontend/Communication.ts @@ -96,63 +96,14 @@ function connect( }) } -function requestReplayData( +async function requestReplayData( + gameId: string, offset: number, size: number -): void { - send([Protocol.EV_CLIENT_REPLAY_DATA, offset, size]) -} - -// TOOD: change replay stuff -function connectReplay( - address: string, - gameId: string, - clientId: string -): Promise<{ game: any, log: Array }> { - clientSeq = 0 - events = {} - setConnectionState(CONN_STATE_CONNECTING) - return new Promise(resolve => { - ws = new WebSocket(address, clientId + '|' + gameId) - ws.onopen = (e) => { - setConnectionState(CONN_STATE_CONNECTED) - requestReplayData(0, 10000) - } - ws.onmessage = (e) => { - const msg = JSON.parse(e.data) - const msgType = msg[0] - if (msgType === Protocol.EV_SERVER_REPLAY_DATA) { - const log: any[] = msg[1] - const game = msg[2] // can be null or encoded game - if (game !== null) { - // this is the first/initial message - const replay: { - game: any, - log: any[] - } = { game, log } - resolve(replay) - } else { - // this is just the next batch of log entries - changesCallback(msg) - } - } else { - throw `[ 2021-05-09 invalid connectReplay msgType ${msgType} ]` - } - } - - ws.onerror = (e) => { - setConnectionState(CONN_STATE_DISCONNECTED) - throw `[ 2021-05-15 onerror ]` - } - - ws.onclose = (e) => { - if (e.code === CODE_CUSTOM_DISCONNECT || e.code === CODE_GOING_AWAY) { - setConnectionState(CONN_STATE_CLOSED) - } else { - setConnectionState(CONN_STATE_DISCONNECTED) - } - } - }) +): Promise<{ log: Array, game: any }> { + const res = await fetch(`/api/replay-data?gameId=${gameId}&offset=${offset}&size=${size}`) + const json: { log: Array, game: any } = await res.json() + return json } function disconnect(): void { @@ -173,7 +124,6 @@ function sendClientEvent(evt: any): void { export default { connect, - connectReplay, requestReplayData, disconnect, sendClientEvent, diff --git a/src/frontend/game.ts b/src/frontend/game.ts index 50be60e..06da228 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -280,6 +280,25 @@ export async function main( HUD.setConnectionState(state) }) + const getNextReplayBatch = async ( + gameId: string, + offset: number, + size: number + ) => { + const replay: { + game: any, + log: Array + } = await Communication.requestReplayData(gameId, offset, size) + // 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 < 10000) { + REPLAY.final = true + } + REPLAY.requesting = false + } let TIME: () => number = () => 0 const connect = async () => { if (MODE === MODE_PLAY) { @@ -288,21 +307,10 @@ export async function main( Game.setGame(gameObject.id, gameObject) TIME = () => Time.timestamp() } else if (MODE === MODE_REPLAY) { - // TODO: change how replay connect is done... - Communication.onServerChange((msg) => { - const log = msg[1] - - // cut log that was already handled - REPLAY.log = REPLAY.log.slice(REPLAY.logPointer) - REPLAY.logPointer = 0 - - REPLAY.log.push(...msg[1]) - if (log.length < 10000) { - REPLAY.final = true - } - REPLAY.requesting = false - }) - const replay: {game: any, log: Array} = await Communication.connectReplay(wsAddress, gameId, clientId) + const replay: { + game: any, + log: Array + } = await Communication.requestReplayData(gameId, 0, 10000) const gameObject = Util.decodeGame(replay.game) Game.setGame(gameObject.id, gameObject) REPLAY.requesting = false @@ -487,7 +495,7 @@ export async function main( if (REPLAY.logPointer + 1 >= REPLAY.log.length) { REPLAY.lastRealTs = realTs REPLAY.requesting = true - Communication.requestReplayData(REPLAY.logIdx, 10000) + getNextReplayBatch(gameId, REPLAY.logIdx, 10000) return } diff --git a/src/server/main.ts b/src/server/main.ts index 7314148..4af0a02 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -61,6 +61,29 @@ app.get('/api/conf', (req, res) => { }) }) +app.get('/api/replay-data', async (req, res) => { + const q = req.query as any + const gameId = q.gameId || '' + if (!GameLog.exists(q.gameId)) { + throw `[gamelog ${gameId} does not exist... ]` + } + const offset = parseInt(q.offset, 10) || 0 + const size = parseInt(q.size, 10) || 10000 + const log = await GameLog.get(gameId, offset, size) + 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 + ) + } + res.send({ log, game: game ? Util.encodeGame(game) : null }) +}) + app.get('/api/newgame-data', (req, res) => { const q = req.query as any const tagSlugs: string[] = q.tags ? q.tags.split(',') : [] @@ -203,31 +226,6 @@ wss.on('message', async ({socket, data} : { socket: WebSocket, data: any }) => { const msg = JSON.parse(data) const msgType = msg[0] switch (msgType) { - case Protocol.EV_CLIENT_REPLAY_DATA: { - if (!GameLog.exists(gameId)) { - throw `[gamelog ${gameId} does not exist... ]` - } - const offset = msg[1] - const size = msg[2] - - const log = await GameLog.get(gameId, offset, size) - 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 - ) - } - notify( - [Protocol.EV_SERVER_REPLAY_DATA, log, game ? Util.encodeGame(game) : null], - [socket] - ) - } break - case Protocol.EV_CLIENT_INIT: { if (!Game.exists(gameId)) { throw `[game ${gameId} does not exist... ]` From ede95ff16c5db3e988f715695d07584c8b6f2fc5 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 29 May 2021 12:49:43 +0200 Subject: [PATCH 05/78] some checks on the replay data request params --- build/server/main.js | 15 ++++++++++++--- src/server/main.ts | 15 ++++++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/build/server/main.js b/build/server/main.js index 65aee8e..51e96ab 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -1930,12 +1930,21 @@ app.get('/api/conf', (req, res) => { }); app.get('/api/replay-data', async (req, res) => { const q = req.query; + const offset = parseInt(q.offset, 10) || 0; + if (offset < 0) { + res.status(400).send({ reason: 'bad offset' }); + return; + } + const size = parseInt(q.size, 10) || 10000; + if (size < 0 || size > 10000) { + res.status(400).send({ reason: 'bad size' }); + return; + } const gameId = q.gameId || ''; if (!GameLog.exists(q.gameId)) { - throw `[gamelog ${gameId} does not exist... ]`; + res.status(404).send({ reason: 'no log found' }); + return; } - const offset = parseInt(q.offset, 10) || 0; - const size = parseInt(q.size, 10) || 10000; const log = await GameLog.get(gameId, offset, size); let game = null; if (offset === 0) { diff --git a/src/server/main.ts b/src/server/main.ts index 4af0a02..bd04709 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -63,12 +63,21 @@ app.get('/api/conf', (req, res) => { app.get('/api/replay-data', async (req, res) => { const q = req.query as any + const offset = parseInt(q.offset, 10) || 0 + if (offset < 0) { + res.status(400).send({ reason: 'bad offset' }) + return + } + const size = parseInt(q.size, 10) || 10000 + if (size < 0 || size > 10000) { + res.status(400).send({ reason: 'bad size' }) + return + } const gameId = q.gameId || '' if (!GameLog.exists(q.gameId)) { - throw `[gamelog ${gameId} does not exist... ]` + res.status(404).send({ reason: 'no log found' }) + return } - const offset = parseInt(q.offset, 10) || 0 - const size = parseInt(q.size, 10) || 10000 const log = await GameLog.get(gameId, offset, size) let game = null if (offset === 0) { From e803945d23bf969208c5af616893b2fe2eb49246 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 29 May 2021 13:08:42 +0200 Subject: [PATCH 06/78] add compression for stuff served via express --- .../{index.382b265e.js => index.8f906b9e.js} | 2 +- build/public/index.html | 2 +- build/server/main.js | 2 + package-lock.json | 136 ++++++++++++++++++ package.json | 2 + src/frontend/game.ts | 2 +- src/server/main.ts | 4 +- 7 files changed, 146 insertions(+), 4 deletions(-) rename build/public/assets/{index.382b265e.js => index.8f906b9e.js} (74%) diff --git a/build/public/assets/index.382b265e.js b/build/public/assets/index.8f906b9e.js similarity index 74% rename from build/public/assets/index.382b265e.js rename to build/public/assets/index.8f906b9e.js index 86fdeb8..df0bb1e 100644 --- a/build/public/assets/index.382b265e.js +++ b/build/public/assets/index.8f906b9e.js @@ -1 +1 @@ -import{d as e,c as t,a as n,w as l,b as i,r as o,o as a,e as s,t as r,F as d,f as c,g as u,h as g,v as p,i as h,j as m,k as y,l as f,m as w,n as v,p as b,q as x,s as C}from"./vendor.b622ee49.js";var k=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const A={id:"app"},T={key:0,class:"nav"},z=s("Index"),S=s("New game");k.render=function(e,s,r,d,c,u){const g=o("router-link"),p=o("router-view");return a(),t("div",A,[e.showNav?(a(),t("ul",T,[n("li",null,[n(g,{class:"btn",to:{name:"index"}},{default:l((()=>[z])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:l((()=>[S])),_:1})])])):i("",!0),n(p)])};const I=864e5,P=e=>{const t=Math.floor(e/I);e%=I;const n=Math.floor(e/36e5);e%=36e5;const l=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${l}m ${Math.floor(e/1e3)}s`};var _=1e3,D=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>P(t-e),E=P,O=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",l=e,i=t||D();return`${n} ${B(l,i)}`}}});const U={class:"game-info-text"},M=n("br",null,null,-1),N=n("br",null,null,-1),G=n("br",null,null,-1),$=s(" ↪️ Watch replay ");O.render=function(e,d,c,u,g,p){const h=o("router-link");return a(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:l((()=>[n("span",U,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),M,s(" 👥 "+r(e.game.players),1),N,s(" "+r(e.time(e.game.started,e.game.finished)),1),G])])),_:1},8,["to"]),e.game.hasReplay?(a(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:l((()=>[$])),_:1},8,["to"])):i("",!0)],4)};var R=e({components:{GameTeaser:O},data:()=>({gamesRunning:[],gamesFinished:[]}),async created(){const e=await fetch("/api/index-data"),t=await e.json();this.gamesRunning=t.gamesRunning,this.gamesFinished=t.gamesFinished}});const V=n("h1",null,"Running games",-1),j=n("h1",null,"Finished games",-1);R.render=function(e,l,i,s,r,u){const g=o("game-teaser");return a(),t("div",null,[V,(a(!0),t(d,null,c(e.gamesRunning,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128)),j,(a(!0),t(d,null,c(e.gamesFinished,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128))])};var F=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});F.render=function(e,l,i,o,s,r){return a(),t("div",{class:"imageteaser",style:e.style,onClick:l[2]||(l[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:l[1]||(l[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var L=e({name:"image-library",components:{ImageTeaser:F},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});L.render=function(e,n,l,i,s,r){const u=o("image-teaser");return a(),t("div",null,[(a(!0),t(d,null,c(e.images,((n,l)=>(a(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:l},null,8,["image","onClick","onEditClick"])))),128))])};const W={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};W.render=function(e,n,l,i,o,s){return a(),t("div",{style:s.style,title:l.title},null,12,["title"])};var q=e({name:"tags-input",props:{modelValue:{type:Array,required:!0}},emits:{"update:modelValue":null},data:()=>({input:"",values:[]}),created(){this.values=this.modelValue},methods:{onKeyUp(e){if(","===e.key)return this.add(),e.stopPropagation(),!1},add(){const e=this.input.replace(/,/g,"").trim();e&&(this.values.includes(e)||this.values.push(e),this.input="",this.$emit("update:modelValue",this.values))},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const H=m()(((e,l,i,o,s,u)=>(a(),t("div",null,[g(n("input",{class:"input",type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.input=t),placeholder:"Plants, People",onKeydown:l[2]||(l[2]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:l[3]||(l[3]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[p,e.input]]),(a(!0),t(d,null,c(e.values,((n,l)=>(a(),t("span",{key:l,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));q.render=H,q.__scopeId="data-v-771460ae";var Q=e({name:"new-image-dialog",components:{ResponsiveImage:W,TagsInput:q},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const l=new FileReader;l.readAsDataURL(n),l.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const Y={key:0,class:"has-image"},K={key:1},Z={class:"upload"},J=n("span",{class:"btn"},"Upload File",-1),X={class:"area-settings"},ee=n("td",null,[n("label",null,"Title")],-1),te=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ne=n("td",null,[n("label",null,"Tags")],-1),le={class:"area-buttons"},ie=s("🧩 Post to gallery "),oe=n("br",null,null,-1),ae=s(" + set up game");Q.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay new-image-dialog",onClick:l[8]||(l[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[7]||(l[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(a(),t("div",Y,[n("span",{class:"remove btn",onClick:l[1]||(l[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(a(),t("div",K,[n("label",Z,[n("input",{type:"file",style:{display:"none"},onChange:l[2]||(l[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),J])]))],2),n("div",X,[n("table",null,[n("tr",null,[ee,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[3]||(l[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),te,n("tr",null,[ne,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[4]||(l[4]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",le,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:l[5]||(l[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:l[6]||(l[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[ie,oe,ae],8,["disabled"])])])])};var se=e({name:"edit-image-dialog",components:{ResponsiveImage:W,TagsInput:q},props:{image:{type:Object,required:!0}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const re={class:"area-image"},de={class:"has-image"},ce={class:"area-settings"},ue=n("td",null,[n("label",null,"Title")],-1),ge=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),pe=n("td",null,[n("label",null,"Tags")],-1),he={class:"area-buttons"};function me(e,t){const n=e.x-t.x,l=e.y-t.y;return Math.sqrt(n*n+l*l)}function ye(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}se.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay edit-image-dialog",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("div",re,[n("div",de,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ce,[n("table",null,[n("tr",null,[ue,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ge,n("tr",null,[pe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[2]||(l[2]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",he,[n("button",{class:"btn",onClick:l[3]||(l[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var fe={pointSub:function(e,t){return{x:e.x-t.x,y:e.y-t.y}},pointAdd:function(e,t){return{x:e.x+t.x,y:e.y+t.y}},pointDistance:me,pointInBounds:function(e,t){return e.x>=t.x&&e.x<=t.x+t.w&&e.y>=t.y&&e.y<=t.y+t.h},rectCenter:ye,rectMoved:function(e,t,n){return{x:e.x+t,y:e.y+n,w:e.w,h:e.h}},rectCenterDistance:function(e,t){return me(ye(e),ye(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};var we=1,ve=4,be=2,xe=3,Ce=2,ke=4,Ae=3,Te=9,ze=1,Se=2,Ie=3,Pe=4,_e=5,De=6,Be=7,Ee=8,Oe=10,Ue=1,Me=2,Ne=3;class Ge{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){return this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295,e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),l=t[n];t[n]=t[e],t[e]=l}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new Ge(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const $e=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},Re=(...e)=>{const t=t=>(...n)=>{const l=new Date,i=$e(l.getHours(),"00"),o=$e(l.getMinutes(),"00"),a=$e(l.getSeconds(),"00");console[t](`${i}:${o}:${a}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var Ve,je,Fe,Le,We={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodeTile:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodeTile:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return Array.isArray(e)?e:[e.id,e.rng.type,Ge.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode]},decodeGame:function(e){return Array.isArray(e)?{id:e[0],rng:{type:e[1],obj:Ge.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}:e},coordByTileIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(let n in e){const l=[n,e[n]].map(encodeURIComponent);t.push(l.join("="))}return 0===t.length?"":`?${t.join("&")}`}};(je=Ve||(Ve={}))[je.Flat=0]="Flat",je[je.Out=1]="Out",je[je.In=-1]="In",(Le=Fe||(Fe={}))[Le.FINAL=0]="FINAL",Le[Le.ANY=1]="ANY";const qe={};function He(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}function Qe(e,t){let n=0;for(let l of qe[e].players){if(We.decodePlayer(l).id===t)return n;n++}return-1}function Ye(e,t){const n=Qe(e,t);return-1===n?null:We.decodePlayer(qe[e].players[n])}function Ke(e,t,n){const l=Qe(e,t);-1===l?qe[e].players.push(We.encodePlayer(n)):qe[e].players[l]=We.encodePlayer(n)}function Ze(e,t){return-1!==Qe(e,t)}function Je(e){return qe[e]?qe[e].players.map(We.decodePlayer):[]}function Xe(e){return qe[e].puzzle.tiles.length}function et(e){return qe[e].scoreMode||0}function tt(e){return nt(e)===Xe(e)}function nt(e){let t=0;for(let n of qe[e].puzzle.tiles)-1===We.decodeTile(n).owner&&t++;return t}function lt(e,t,n){const l=Ye(e,t);if(null!==l){for(let e of Object.keys(n))l[e]=n[e];Ke(e,t,l)}}function it(e,t){for(let n of Object.keys(t))qe[e].puzzle.data[n]=t[n]}function ot(e,t,n){for(let l of Object.keys(n)){const i=We.decodeTile(qe[e].puzzle.tiles[t]);i[l]=n[l],qe[e].puzzle.tiles[t]=We.encodeTile(i)}}const at=(e,t)=>We.decodeTile(qe[e].puzzle.tiles[t]),st=(e,t)=>at(e,t).group,rt=(e,t)=>{const n=qe[e].puzzle.info,l={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},i=function(e,t){const n=qe[e].puzzle.info,l=We.coordByTileIdx(n,t),i=l.x*n.tileSize,o=l.y*n.tileSize;return{x:i,y:o}}(e,t);return fe.pointAdd(l,i)},dt=(e,t)=>at(e,t).pos,ct=e=>{const t=Tt(e),n=zt(e),l=Math.round(t/4),i=Math.round(n/4);return{x:0-l,y:0-i,w:t+2*l,h:n+2*i}},ut=(e,t)=>{const n=mt(e),l=at(e,t);return{x:l.pos.x,y:l.pos.y,w:n,h:n}},gt=(e,t)=>at(e,t).z,pt=(e,t)=>{for(let n of qe[e].puzzle.tiles){const e=We.decodeTile(n);if(e.owner===t)return e.idx}return-1},ht=e=>qe[e].puzzle.info.tileDrawSize,mt=e=>qe[e].puzzle.info.tileSize,yt=e=>qe[e].puzzle.data.maxGroup,ft=e=>qe[e].puzzle.data.maxZ;function wt(e,t){const n=qe[e].puzzle.info,l=We.coordByTileIdx(n,t);return[l.y>0?t-n.tilesX:-1,l.x0?t-1:-1]}const vt=(e,t,n)=>{for(let l of t)ot(e,l,{z:n})},bt=(e,t,n)=>{const l=dt(e,t);ot(e,t,{pos:fe.pointAdd(l,n)})},xt=(e,t,n)=>{const l=ht(e),i=ct(e),o=n;for(let a of t){const t=at(e,a);t.pos.x+n.xi.x+i.w&&(o.x=Math.min(i.x+i.w-t.pos.x+l,o.x)),t.pos.y+n.yi.y+i.h&&(o.y=Math.min(i.y+i.h-t.pos.y+l,o.y))}for(let a of t)bt(e,a,o)},Ct=(e,t,n)=>{for(let l of t)ot(e,l,{owner:n})};function kt(e,t){const n=qe[e].puzzle.tiles,l=We.decodeTile(n[t]),i=[];if(l.group)for(let o of n){const e=We.decodeTile(o);e.group===l.group&&i.push(e.idx)}else i.push(l.idx);return i}const At=(e,t)=>{const n=Ye(e,t);return n?n.points:0},Tt=e=>qe[e].puzzle.info.table.width,zt=e=>qe[e].puzzle.info.table.height;var St={__createPlayerObject:He,setGame:function(e,t){qe[e]=t},exists:function(e){return!!qe[e]||!1},playerExists:Ze,getActivePlayers:function(e,t){const n=t-30*_;return Je(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return Je(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Ze(e,t)?lt(e,t,{ts:n}):Ke(e,t,He(t,n))},getFinishedTileCount:nt,getTileCount:Xe,getImageUrl:function(e){return qe[e].puzzle.info.imageUrl},setImageUrl:function(e,t){qe[e].puzzle.info.imageUrl=t},get:function(e){return qe[e]},getAllGames:function(){return Object.values(qe).sort(((e,t)=>tt(e.id)===tt(t.id)?t.puzzle.data.started-e.puzzle.data.started:tt(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Ye(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Ye(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Ye(e,t);return n?n.name:null},getPlayerIndexById:Qe,getPlayerIdByIndex:function(e,t){return qe[e].players.length>t?We.decodePlayer(qe[e].players[t]).id:null},changePlayer:lt,setPlayer:Ke,setTile:function(e,t,n){qe[e].puzzle.tiles[t]=We.encodeTile(n)},setPuzzleData:function(e,t){qe[e].puzzle.data=t},getTableWidth:Tt,getTableHeight:zt,getPuzzle:e=>qe[e].puzzle,getRng:e=>qe[e].rng.obj,getPuzzleWidth:e=>qe[e].puzzle.info.width,getPuzzleHeight:e=>qe[e].puzzle.info.height,getTilesSortedByZIndex:function(e){return qe[e].puzzle.tiles.map(We.decodeTile).sort(((e,t)=>e.z-t.z))},getFirstOwnedTile:(e,t)=>{const n=pt(e,t);return n<0?null:qe[e].puzzle.tiles[n]},getTileDrawOffset:e=>qe[e].puzzle.info.tileDrawOffset,getTileDrawSize:ht,getFinalTilePos:rt,getStartTs:e=>qe[e].puzzle.data.started,getFinishTs:e=>qe[e].puzzle.data.finished,handleInput:function(e,t,n,l){const i=qe[e].puzzle,o=function(e,t){return t in qe[e].evtInfos?qe[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),a=[],s=()=>{a.push([Ue,i.data])},r=t=>{a.push([Me,We.encodeTile(at(e,t))])},d=e=>{for(const t of e)r(t)},c=()=>{const n=Ye(e,t);n&&a.push([Ne,We.encodePlayer(n)])},u=n[0];if(u===De){const i=n[1];lt(e,t,{bgcolor:i,ts:l}),c()}else if(u===Be){const i=n[1];lt(e,t,{color:i,ts:l}),c()}else if(u===Ee){const i=`${n[1]}`.substr(0,16);lt(e,t,{name:i,ts:l}),c()}else if(u===ze){const i={x:n[1],y:n[2]};lt(e,t,{d:1,ts:l}),c(),o._last_mouse_down=i;const a=((e,t)=>{let n=qe[e].puzzle.info,l=qe[e].puzzle.tiles,i=-1,o=-1;for(let a=0;ai)&&(i=e.z,o=a)}return o})(e,i);if(a>=0){let n=ft(e)+1;it(e,{maxZ:n}),s();const l=kt(e,a);vt(e,l,ft(e)),Ct(e,l,t),d(l)}o._last_mouse=i}else if(u===Ie){const i=n[1],a=n[2],s={x:i,y:a};if(null===o._last_mouse_down)lt(e,t,{x:i,y:a,ts:l}),c();else{let n=pt(e,t);if(n>=0){lt(e,t,{x:i,y:a,ts:l}),c();const r=kt(e,n);let u=fe.pointInBounds(s,ct(e))&&fe.pointInBounds(o._last_mouse_down,ct(e));for(let t of r){const n=ut(e,t);if(fe.pointInBounds(s,n)){u=!0;break}}if(u){const t=i-o._last_mouse_down.x,n=a-o._last_mouse_down.y;xt(e,r,{x:t,y:n}),d(r)}}else lt(e,t,{ts:l}),c();o._last_mouse_down=s}o._last_mouse=s}else if(u===Se){const a={x:n[1],y:n[2]},u=0;o._last_mouse_down=null;let g=pt(e,t);if(g>=0){let n=kt(e,g);Ct(e,n,0),d(n);let o=dt(e,g),a=rt(e,g);if(fe.pointDistance(a,o){for(let n of t)ot(e,n,{owner:-1,z:1})})(e,n),d(n);let r=At(e,t);0===et(e)?r+=n.length:1===et(e)&&(r+=1),lt(e,t,{d:u,ts:l,points:r}),c(),nt(e)===Xe(e)&&(it(e,{finished:l}),s())}else{const n=(e,t,n,l)=>{let i=qe[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const l=st(e,t),i=st(e,n);return!(!l||l!==i)})(e,t,n))return!1;const o=dt(e,t),a=fe.pointAdd(dt(e,n),{x:l[0]*i.tileSize,y:l[1]*i.tileSize});if(fe.pointDistance(o,a){const l=qe[e].puzzle.tiles,i=st(e,t),o=st(e,n);let a;const d=[];i&&d.push(i),o&&d.push(o),i?a=i:o?a=o:(it(e,{maxGroup:yt(e)+1}),s(),a=yt(e));if(ot(e,t,{group:a}),r(t),ot(e,n,{group:a}),r(n),d.length>0)for(const s of l){const t=We.decodeTile(s);d.includes(t.group)&&(ot(e,t.idx,{group:a}),r(t.idx))}})(e,t,n),i=kt(e,t);const c=((e,t)=>{let n=0;for(let l of t){let t=gt(e,l);t>n&&(n=t)}return n})(e,i);return vt(e,i,c),d(i),!0}return!1};let i=!1;for(let t of kt(e,g)){let l=wt(e,t);if(n(e,t,l[0],[0,1])||n(e,t,l[1],[-1,0])||n(e,t,l[2],[0,-1])||n(e,t,l[3],[1,0])){i=!0;break}}if(i&&1===et(e)){const n=At(e,t)+1;lt(e,t,{d:u,ts:l,points:n}),c()}else lt(e,t,{d:u,ts:l}),c()}}else lt(e,t,{d:u,ts:l}),c();o._last_mouse=a}else if(u===Pe){const i=n[1],a=n[2];lt(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else if(u===_e){const i=n[1],a=n[2];lt(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else lt(e,t,{ts:l}),c();return function(e,t,n){qe[e].evtInfos[t]=n}(e,t,o),a}},It=e({name:"new-game-dialog",components:{ResponsiveImage:W},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Fe.ANY}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Pt={class:"area-image"},_t={class:"has-image"},Dt={class:"area-settings"},Bt=n("td",null,[n("label",null,"Pieces")],-1),Et=n("td",null,[n("label",null,"Scoring: ")],-1),Ot=s(" Any (Score when pieces are connected to each other or on final location)"),Ut=n("br",null,null,-1),Mt=s(" Final (Score when pieces are put to their final location)"),Nt={class:"area-buttons"};It.render=function(e,l,i,s,r,d){const c=o("responsive-image");return a(),t("div",{class:"overlay new-game-dialog",onClick:l[6]||(l[6]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[5]||(l[5]=u((()=>{}),["stop"]))},[n("div",Pt,[n("div",_t,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Dt,[n("table",null,[n("tr",null,[Bt,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Et,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[2]||(l[2]=t=>e.scoreMode=t),value:"1"},null,512),[[y,e.scoreMode]]),Ot]),Ut,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[3]||(l[3]=t=>e.scoreMode=t),value:"0"},null,512),[[y,e.scoreMode]]),Mt])])])])]),n("div",Nt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:l[4]||(l[4]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var Gt=e({components:{ImageLibrary:L,NewImageDialog:Q,EditImageDialog:se,NewGameDialog:It},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},methods:{toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${We.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const $t={class:"upload-image-teaser"},Rt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),Vt={key:0},jt=s(" Tags: "),Ft=s(" Sort by: "),Lt=n("option",{value:"date_desc"},"Newest first",-1),Wt=n("option",{value:"date_asc"},"Oldest first",-1),qt=n("option",{value:"alpha_asc"},"A-Z",-1),Ht=n("option",{value:"alpha_desc"},"Z-A",-1);Gt.render=function(e,l,s,u,p,h){const m=o("image-library"),y=o("new-image-dialog"),w=o("edit-image-dialog"),v=o("new-game-dialog");return a(),t("div",null,[n("div",$t,[n("div",{class:"btn btn-big",onClick:l[1]||(l[1]=t=>e.dialog="new-image")},"Upload your image"),Rt]),n("div",null,[e.tags.length>0?(a(),t("label",Vt,[jt,(a(!0),t(d,null,c(e.tags,((n,l)=>(a(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:l,onClick:t=>e.toggleTag(n)},r(n.title),11,["onClick"])))),128))])):i("",!0),n("label",null,[Ft,g(n("select",{"onUpdate:modelValue":l[2]||(l[2]=t=>e.filters.sort=t),onChange:l[3]||(l[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[Lt,Wt,qt,Ht],544),[[f,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(a(),t(y,{key:0,onBgclick:l[4]||(l[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["onPostToGalleryClick","onSetupGameClick"])):i("",!0),"edit-image"===e.dialog?(a(),t(w,{key:1,onBgclick:l[5]||(l[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["onSaveClick","image"])):i("",!0),e.image&&"new-game"===e.dialog?(a(),t(v,{key:2,onBgclick:l[6]||(l[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):i("",!0)])};var Qt=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"},Kt=n("div",null,"Scores",-1),Zt=n("td",null,"⚡",-1),Jt=n("td",null,"💤",-1);Qt.render=function(e,l,i,o,s,u){return a(),t("div",Yt,[Kt,n("table",null,[(a(!0),t(d,null,c(e.actives,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[Zt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(a(!0),t(d,null,c(e.idles,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[Jt,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 E(this.duration)}}});const en={class:"timer"};Xt.render=function(e,l,i,o,s,d){return a(),t("div",en,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),w(e.$slots,"default")])};var tn=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 nn=n("td",null,[n("label",null,"Background: ")],-1),ln=n("td",null,[n("label",null,"Color: ")],-1),on=n("td",null,[n("label",null,"Name: ")],-1);tn.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("tr",null,[nn,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[1]||(l[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[ln,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[2]||(l[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[on,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":l[3]||(l[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])])])])};var an=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const sn={class:"preview"};an.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay",onClick:l[1]||(l[1]=t=>e.$emit("bgclick"))},[n("div",sn,[n("div",{class:"img",style:e.previewStyle},null,4)])])};const rn=Re("Communication.js");let dn,cn=e=>{},un=e=>{};let gn=0;const pn=e=>{gn!==e&&(gn=e,un(e))};function hn(e){if(2===gn)try{dn.send(JSON.stringify(e))}catch(t){rn.info("unable to send message.. maybe because ws is invalid?")}}let mn,yn;var fn={connect:function(e,t,n){return mn=0,yn={},pn(3),new Promise((l=>{dn=new WebSocket(e,n+"|"+t),dn.onopen=e=>{pn(2),hn([xe])},dn.onmessage=e=>{const t=JSON.parse(e.data),i=t[0];if(i===ve){const e=t[1];l(e)}else{if(i!==we)throw`[ 2021-05-09 invalid connect msgType ${i} ]`;{const e=t[1],l=t[2];if(e===n&&yn[l])return void delete yn[l];cn(t)}}},dn.onerror=e=>{throw pn(1),"[ 2021-05-15 onerror ]"},dn.onclose=e=>{4e3===e.code||1001===e.code?pn(4):pn(1)}}))},requestReplayData:async function(e,t,n){const l=await fetch(`/api/replay-data?gameId=${e}&offset=${t}&size=${n}`);return await l.json()},disconnect:function(){dn&&dn.close(4e3),mn=0,yn={}},sendClientEvent:function(e){mn++,yn[mn]=e,hn([be,mn,yn[mn]])},onServerChange:function(e){cn=e},onConnectionStateChange:function(e){un=e},CODE_CUSTOM_DISCONNECT:4e3,CONN_STATE_NOT_CONNECTED:0,CONN_STATE_DISCONNECTED:1,CONN_STATE_CLOSED:4,CONN_STATE_CONNECTED:2,CONN_STATE_CONNECTING:3},wn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===fn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===fn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const vn={key:0,class:"overlay connection-lost"},bn={key:0,class:"overlay-content"},xn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Cn={key:1,class:"overlay-content"},kn=n("div",null,"Connecting...",-1);wn.render=function(e,l,o,s,r,d){return e.show?(a(),t("div",vn,[e.lostConnection?(a(),t("div",bn,[xn,n("span",{class:"btn",onClick:l[1]||(l[1]=t=>e.$emit("reconnect"))},"Reconnect")])):i("",!0),e.connecting?(a(),t("div",Cn,[kn])):i("",!0)])):i("",!0)};var An=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"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),zn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),Sn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),In=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),Pn=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),_n=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),Dn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),Bn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),En=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);An.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[Tn,zn,Sn,In,Pn,_n,Dn,Bn,En,On])])};var Un=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Mn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Gn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function $n(){let e=0,t=0,n=1;const l=(l,i)=>{e+=l/n,t+=i/n},i=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},o=l=>({x:l.x/n-e,y:l.y/n-t}),a=l=>({x:(l.x+e)*n,y:(l.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n});return{move:l,canZoom:e=>n!=i(e),zoom:(e,t)=>((e,t)=>{if(n==e)return!1;const i=1-n/e;return l(-t.x*i,-t.y*i),n=e,!0})(i(e),t),worldToViewport:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:a,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=o(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:o}}function Rn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Vn={createCanvas:Rn,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=Rn(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=Rn(e.width,e.height),i=l.getContext("2d");return i.save(),i.drawImage(t,0,0),i.fillStyle=n,i.globalCompositeOperation="source-in",i.fillRect(0,0,t.width,t.height),i.restore(),i.save(),i.globalCompositeOperation="destination-over",i.drawImage(e,0,0),i.restore(),l}};const jn=Re("Debug.js");let Fn=0,Ln=0;var Wn=e=>{Fn=performance.now(),Ln=e},qn=e=>{const t=performance.now(),n=t-Fn;n>Ln&&jn.log(e+": "+n),Fn=t};const Hn=Re("PuzzleGraphics.js");function Qn(e,t){const n=We.coordByTileIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Yn={loadPuzzleBitmaps:async function(e){const t=await Vn.loadImageToBitmap(e.info.imageUrl),n=await Vn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Hn.log("start createPuzzleTileBitmaps");var l=n.tileSize,i=n.tileMarginWidth,o=n.tileDrawSize,a=l/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0];const r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,o={x:i,y:i},r=fe.pointAdd(o,{x:l,y:0}),c=fe.pointAdd(r,{x:0,y:l}),u=fe.pointSub(c,{x:l,y:0});if(n.moveTo(o.x,o.y),0!==e.top)for(let l=0;l=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Jn=t/2,Xn=t-Jn;const n=1/4*this.canvas.width/(t/2);Kn=-n,Zn=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new el(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new el(this.rng));const e=[];for(;this.explodedBombs.length>0;){const t=this.explodedBombs.shift();if(!t)break;t.update(),t.alive&&e.push(t)}this.explodedBombs=e;const t=[];for(;this.readyBombs.length>0;){const e=this.readyBombs.shift();if(!e)break;e.update(this.particles),e.hasExploded?this.explodedBombs.push(e):t.push(e)}this.readyBombs=t;const n=[];for(;this.particles.length>0;){const e=this.particles.shift();if(!e)break;e.update(),e.alive&&n.push(e)}this.particles=n}render(){this.ctx.beginPath(),this.ctx.fillStyle="rgba(0, 0, 0, 0.1)",this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height);for(let e=0;e{const t=e.color+" "+e.d;if(!h[t]){const n=e.d?a:s;if(e.color){const l=e.d?r:d;h[t]=await createImageBitmap(Vn.colorizedCanvas(n,l,e.color))}else h[t]=n}return h[t]},y=function(e,t){return t.width=window.innerWidth,t.height=window.innerHeight,e.appendChild(t),window.addEventListener("resize",(()=>{t.width=window.innerWidth,t.height=window.innerHeight,al=!0})),t}(i,Vn.createCanvas()),f={final:!1,requesting:!0,log:[],logPointer:0,logIdx:0,speeds:[.5,1,2,5,10,20,50],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0};fn.onConnectionStateChange((e=>{o.setConnectionState(e)}));let w=()=>0;const v=async()=>{if("play"===l){const l=await fn.connect(n,e,t),i=We.decodeGame(l);St.setGame(i.id,i),w=()=>D()}else{if("replay"!==l)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await fn.requestReplayData(e,0,1e4),n=We.decodeGame(t.game);St.setGame(n.id,n),f.requesting=!1,f.log=t.log,f.lastRealTs=D(),f.gameStartTs=parseInt(f.log[0][4],10),f.lastGameTs=f.gameStartTs,w=()=>f.lastGameTs}}al=!0};await v();const b=St.getTileDrawOffset(e),x=St.getTileDrawSize(e),C=St.getPuzzleWidth(e),k=St.getPuzzleHeight(e),A=St.getTableWidth(e),T=St.getTableHeight(e),z={x:(A-C)/2,y:(T-k)/2},S={w:C,h:k},I={w:x,h:x},P=await Yn.loadPuzzleBitmaps(St.getPuzzle(e)),_=new nl(y,St.getRng(e));_.init();const B=y.getContext("2d");y.classList.add("loaded");const E=$n();E.move(-(A-y.width)/2,-(T-y.height)/2);const O=function(e,t,n){let l=[],i=!0,o=!1,a=!1,s=!1,r=!1,d=!1,c=!1,u=!1;const g=(e,t)=>{const l=n.viewportToWorld({x:e,y:t});return[l.x,l.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{i&&("Shift"===t.key?u=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?s=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?r=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?o=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?a=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};e.addEventListener("mousedown",(e=>{0===e.button&&y([ze,...p(e)])})),e.addEventListener("mouseup",(e=>{0===e.button&&y([Se,...p(e)])})),e.addEventListener("mousemove",(e=>{y([Ie,...p(e)])})),e.addEventListener("wheel",(e=>{if(n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Pe:_e;y([t,...p(e)])}})),t.addEventListener("keydown",(e=>m(!0,e))),t.addEventListener("keyup",(e=>m(!1,e))),t.addEventListener("keypress",(e=>{i&&(" "===e.key&&y([Oe]),"F"!==e.key&&"f"!==e.key||(il=!il,al=!0),"G"!==e.key&&"g"!==e.key||(ol=!ol,al=!0))}));const y=e=>{l.push(e)};return{addEvent:y,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=u?20:10,t=(o?e:0)-(a?e:0),l=(s?e:0)-(r?e:0);0===t&&0===l||y([Te,t,l]),d&&c||(d?n.canZoom("in")&&y([Pe,...h()]):c&&n.canZoom("out")&&y([_e,...h()]))},setHotkeys:e=>{i=e}}}(y,window,E),U=St.getImageUrl(e),M=()=>{const t=St.getStartTs(e),n=St.getFinishTs(e),l=w();o.setFinished(!!n),o.setDuration((n||l)-t)};M(),o.setPiecesDone(St.getFinishedTileCount(e)),o.setPiecesTotal(St.getTileCount(e));const N=w();o.setActivePlayers(St.getActivePlayers(e,N)),o.setIdlePlayers(St.getIdlePlayers(e,N));const G=!!St.getFinishTs(e);let $=G;const R=()=>$&&!G,V=()=>St.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",j=()=>St.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let F="",L="",W=!1;const q=e=>{W=e;const[t,n]=e?[F,"grab"]:[L,"default"];y.style.cursor=`url('${t}') ${u} ${p}, ${n}`},H=e=>{F=Vn.colorizedCanvas(a,r,e).toDataURL(),L=Vn.colorizedCanvas(s,d,e).toDataURL(),q(W)};H(j());const Q=()=>{o.setReplaySpeed&&o.setReplaySpeed(f.speeds[f.speedIdx]),o.setReplayPaused&&o.setReplayPaused(f.paused)};if("play"===l?setInterval(M,1e3):"replay"===l&&Q(),"play"===l)fn.onServerChange((n=>{n[0],n[1],n[2];const l=n[3];for(const[i,o]of l)switch(i){case Ne:{const n=We.decodePlayer(o);n.id!==t&&(St.setPlayer(e,n.id,n),al=!0)}break;case Me:{const t=We.decodeTile(o);St.setTile(e,t.idx,t),al=!0}break;case Ue:St.setPuzzleData(e,o),al=!0}$=!!St.getFinishTs(e)}));else if("replay"===l){let t=setInterval((()=>{const n=D();if(f.requesting)return void(f.lastRealTs=n);if(f.logPointer+1>=f.log.length)return f.lastRealTs=n,f.requesting=!0,void(async(e,t,n)=>{const l=await fn.requestReplayData(e,t,n);f.log=f.log.slice(f.logPointer),f.logPointer=0,f.log.push(...l.log),l.log.length<1e4&&(f.final=!0),f.requesting=!1})(e,f.logIdx,1e4);if(f.paused)return void(f.lastRealTs=n);const l=(n-f.lastRealTs)*f.speeds[f.speedIdx],i=f.lastGameTs+l;for(;;){if(f.paused)break;const n=f.logPointer+1;if(n>=f.log.length){f.final&&clearInterval(t);break}const l=f.log[n],o=f.gameStartTs+l[l.length-1];if(o>i)break;const a=l.slice();if(a[0]===Ce){const t=a[1];St.addPlayer(e,t,o),al=!0}else if(a[0]===ke){const t=St.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";St.addPlayer(e,t,o),al=!0}else if(a[0]===Ae){const t=St.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=a[2];St.handleInput(e,t,n,o),al=!0}f.logPointer=n,f.logIdx++}f.lastRealTs=n,f.lastGameTs=i,M()}),50)}let Y=null;return(e=>{const t=e.fps||60,n=e.slow||1,l=e.update,i=e.render,o=window.requestAnimationFrame,a=1/t,s=n*a;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>s;)d-=s,l(a);i(d/n),c=r,o(u)};o(u)})({update:()=>{O.createKeyEvents();for(const n of O.consumeAll())if("play"===l){const l=n[0];if(l===Te){const e=n[1],t=n[2];al=!0,E.move(e,t)}else if(l===Ie){if(Y&&!St.getFirstOwnedTile(e,t)){const e={x:n[1],y:n[2]},t=E.worldToViewport(e),l=Math.round(t.x-Y.x),i=Math.round(t.y-Y.y);al=!0,E.move(l,i),Y=t}}else if(l===Be)H(n[1]);else if(l===ze){const e={x:n[1],y:n[2]};Y=E.worldToViewport(e),q(!0)}else if(l===Se)Y=null,q(!1);else if(l===Pe){const e={x:n[1],y:n[2]};al=!0,E.zoom("in",E.worldToViewport(e))}else if(l===_e){const e={x:n[1],y:n[2]};al=!0,E.zoom("out",E.worldToViewport(e))}else l===Oe&&o.togglePreview();const i=w();St.handleInput(e,t,n,i).length>0&&(al=!0),fn.sendClientEvent(n)}else if("replay"===l){const e=n[0];if(e===Te){const e=n[1],t=n[2];al=!0,E.move(e,t)}else if(e===Ie){if(Y){const e={x:n[1],y:n[2]},t=E.worldToViewport(e),l=Math.round(t.x-Y.x),i=Math.round(t.y-Y.y);al=!0,E.move(l,i),Y=t}}else if(e===ze){const e={x:n[1],y:n[2]};Y=E.worldToViewport(e)}else if(e===Se)Y=null;else if(e===Pe){const e={x:n[1],y:n[2]};al=!0,E.zoom("in",E.worldToViewport(e))}else if(e===_e){const e={x:n[1],y:n[2]};al=!0,E.zoom("out",E.worldToViewport(e))}else e===Oe&&o.togglePreview()}$=!!St.getFinishTs(e),R()&&(_.update(),al=!0)},render:async()=>{if(!al)return;const n=w();let i,a,s;window.DEBUG&&Wn(0),B.fillStyle=V(),B.fillRect(0,0,y.width,y.height),window.DEBUG&&qn("clear done"),i=E.worldToViewportRaw(z),a=E.worldDimToViewportRaw(S),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(i.x,i.y,a.w,a.h),window.DEBUG&&qn("board done");const r=St.getTilesSortedByZIndex(e);window.DEBUG&&qn("get tiles done"),a=E.worldDimToViewportRaw(I);for(const e of r)(-1===e.owner?il:ol)&&(s=P[e.idx],i=E.worldToViewportRaw({x:b+e.pos.x,y:b+e.pos.y}),B.drawImage(s,0,0,s.width,s.height,i.x,i.y,a.w,a.h));window.DEBUG&&qn("tiles done");const d=[];for(const o of St.getActivePlayers(e,n))c=o,("replay"===l||c.id!==t)&&(s=await m(o),i=E.worldToViewport(o),B.drawImage(s,i.x-u,i.y-p),d.push([`${o.name} (${o.points})`,i.x,i.y+g]));var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,l]of d)B.fillText(e,t,l);window.DEBUG&&qn("players done"),o.setActivePlayers(St.getActivePlayers(e,n)),o.setIdlePlayers(St.getIdlePlayers(e,n)),o.setPiecesDone(St.getFinishedTileCount(e)),window.DEBUG&&qn("HUD done"),R()&&_.render(),al=!1}}),{setHotkeys:e=>{O.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),O.addEvent([De,e])},onColorChange:e=>{localStorage.setItem("player_color",e),O.addEvent([Be,e])},onNameChange:e=>{localStorage.setItem("player_name",e),O.addEvent([Ee,e])},replayOnSpeedUp:()=>{f.speedIdx+1{f.speedIdx>=1&&(f.speedIdx--,Q())},replayOnPauseToggle:()=>{f.paused=!f.paused,Q()},previewImageUrl:U,player:{background:V(),color:j(),name:St.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon"},disconnect:fn.disconnect,connect:v}}var rl=e({name:"game",components:{PuzzleStatus:Xt,Scores:Qt,SettingsOverlay:tn,PreviewOverlay:an,ConnectionOverlay:wn,HelpOverlay:An},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},disconnect:()=>{},connect:()=>{}}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await sl(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},setConnectionState:e=>{this.connectionState=e},togglePreview:()=>{this.toggle("preview",!1)}}))},unmounted(){this.g.disconnect()},methods:{reconnect(){this.g.connect()},toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}}});const dl={id:"game"},cl={class:"menu"},ul={class:"tabs"},gl=s("🧩 Puzzles");rl.render=function(e,i,s,r,d,c){const u=o("settings-overlay"),p=o("preview-overlay"),h=o("help-overlay"),m=o("connection-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",dl,[g(n(u,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(p,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(h,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",cl,[n("div",ul,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[gl])),_:1}),n("div",{class:"opener",onClick:i[5]||(i[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var pl=e({name:"replay",components:{PuzzleStatus:Xt,Scores:Qt,SettingsOverlay:tn,PreviewOverlay:an,HelpOverlay:An},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},disconnect:()=>{}},replay:{speed:1,paused:!1}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await sl(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"replay",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},setReplaySpeed:e=>{this.replay.speed=e},setReplayPaused:e=>{this.replay.paused=e}}))},unmounted(){this.g.disconnect()},methods:{toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}},computed:{replayText(){return"Replay-Speed: "+this.replay.speed+"x"+(this.replay.paused?" Paused":"")}}});const hl={id:"replay"},ml={class:"menu"},yl={class:"tabs"},fl=s("🧩 Puzzles");pl.render=function(e,i,s,d,c,u){const p=o("settings-overlay"),h=o("preview-overlay"),m=o("help-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",hl,[g(n(p,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(m,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:l((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:i[5]||(i[5]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",ml,[n("div",yl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[fl])),_:1}),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=b({history:x(),routes:[{name:"index",path:"/",component:R},{name:"new-game",path:"/new-game",component:Gt},{name:"game",path:"/g/:id",component:rl},{name:"replay",path:"/replay/:id",component:pl}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const l=C(k);l.config.globalProperties.$config=t,l.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=We.uniqId(),localStorage.setItem("ID",e)),e}(),l.use(n),l.mount("#app")})(); +import{d as e,c as t,a as n,w as l,b as i,r as o,o as a,e as s,t as r,F as d,f as c,g as u,h as g,v as p,i as h,j as m,k as y,l as f,m as w,n as v,p as b,q as x,s as C}from"./vendor.b622ee49.js";var k=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const A={id:"app"},T={key:0,class:"nav"},z=s("Index"),S=s("New game");k.render=function(e,s,r,d,c,u){const g=o("router-link"),p=o("router-view");return a(),t("div",A,[e.showNav?(a(),t("ul",T,[n("li",null,[n(g,{class:"btn",to:{name:"index"}},{default:l((()=>[z])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:l((()=>[S])),_:1})])])):i("",!0),n(p)])};const I=864e5,P=e=>{const t=Math.floor(e/I);e%=I;const n=Math.floor(e/36e5);e%=36e5;const l=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${l}m ${Math.floor(e/1e3)}s`};var _=1e3,D=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>P(t-e),E=P,O=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",l=e,i=t||D();return`${n} ${B(l,i)}`}}});const U={class:"game-info-text"},M=n("br",null,null,-1),N=n("br",null,null,-1),G=n("br",null,null,-1),$=s(" ↪️ Watch replay ");O.render=function(e,d,c,u,g,p){const h=o("router-link");return a(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:l((()=>[n("span",U,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),M,s(" 👥 "+r(e.game.players),1),N,s(" "+r(e.time(e.game.started,e.game.finished)),1),G])])),_:1},8,["to"]),e.game.hasReplay?(a(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:l((()=>[$])),_:1},8,["to"])):i("",!0)],4)};var R=e({components:{GameTeaser:O},data:()=>({gamesRunning:[],gamesFinished:[]}),async created(){const e=await fetch("/api/index-data"),t=await e.json();this.gamesRunning=t.gamesRunning,this.gamesFinished=t.gamesFinished}});const V=n("h1",null,"Running games",-1),j=n("h1",null,"Finished games",-1);R.render=function(e,l,i,s,r,u){const g=o("game-teaser");return a(),t("div",null,[V,(a(!0),t(d,null,c(e.gamesRunning,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128)),j,(a(!0),t(d,null,c(e.gamesFinished,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128))])};var F=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});F.render=function(e,l,i,o,s,r){return a(),t("div",{class:"imageteaser",style:e.style,onClick:l[2]||(l[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:l[1]||(l[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var L=e({name:"image-library",components:{ImageTeaser:F},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});L.render=function(e,n,l,i,s,r){const u=o("image-teaser");return a(),t("div",null,[(a(!0),t(d,null,c(e.images,((n,l)=>(a(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:l},null,8,["image","onClick","onEditClick"])))),128))])};const W={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};W.render=function(e,n,l,i,o,s){return a(),t("div",{style:s.style,title:l.title},null,12,["title"])};var q=e({name:"tags-input",props:{modelValue:{type:Array,required:!0}},emits:{"update:modelValue":null},data:()=>({input:"",values:[]}),created(){this.values=this.modelValue},methods:{onKeyUp(e){if(","===e.key)return this.add(),e.stopPropagation(),!1},add(){const e=this.input.replace(/,/g,"").trim();e&&(this.values.includes(e)||this.values.push(e),this.input="",this.$emit("update:modelValue",this.values))},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const H=m()(((e,l,i,o,s,u)=>(a(),t("div",null,[g(n("input",{class:"input",type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.input=t),placeholder:"Plants, People",onKeydown:l[2]||(l[2]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:l[3]||(l[3]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[p,e.input]]),(a(!0),t(d,null,c(e.values,((n,l)=>(a(),t("span",{key:l,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));q.render=H,q.__scopeId="data-v-771460ae";var Q=e({name:"new-image-dialog",components:{ResponsiveImage:W,TagsInput:q},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const l=new FileReader;l.readAsDataURL(n),l.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const Y={key:0,class:"has-image"},K={key:1},Z={class:"upload"},J=n("span",{class:"btn"},"Upload File",-1),X={class:"area-settings"},ee=n("td",null,[n("label",null,"Title")],-1),te=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ne=n("td",null,[n("label",null,"Tags")],-1),le={class:"area-buttons"},ie=s("🧩 Post to gallery "),oe=n("br",null,null,-1),ae=s(" + set up game");Q.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay new-image-dialog",onClick:l[8]||(l[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[7]||(l[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(a(),t("div",Y,[n("span",{class:"remove btn",onClick:l[1]||(l[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(a(),t("div",K,[n("label",Z,[n("input",{type:"file",style:{display:"none"},onChange:l[2]||(l[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),J])]))],2),n("div",X,[n("table",null,[n("tr",null,[ee,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[3]||(l[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),te,n("tr",null,[ne,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[4]||(l[4]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",le,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:l[5]||(l[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:l[6]||(l[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[ie,oe,ae],8,["disabled"])])])])};var se=e({name:"edit-image-dialog",components:{ResponsiveImage:W,TagsInput:q},props:{image:{type:Object,required:!0}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const re={class:"area-image"},de={class:"has-image"},ce={class:"area-settings"},ue=n("td",null,[n("label",null,"Title")],-1),ge=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),pe=n("td",null,[n("label",null,"Tags")],-1),he={class:"area-buttons"};function me(e,t){const n=e.x-t.x,l=e.y-t.y;return Math.sqrt(n*n+l*l)}function ye(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}se.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay edit-image-dialog",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("div",re,[n("div",de,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ce,[n("table",null,[n("tr",null,[ue,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ge,n("tr",null,[pe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[2]||(l[2]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",he,[n("button",{class:"btn",onClick:l[3]||(l[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var fe={pointSub:function(e,t){return{x:e.x-t.x,y:e.y-t.y}},pointAdd:function(e,t){return{x:e.x+t.x,y:e.y+t.y}},pointDistance:me,pointInBounds:function(e,t){return e.x>=t.x&&e.x<=t.x+t.w&&e.y>=t.y&&e.y<=t.y+t.h},rectCenter:ye,rectMoved:function(e,t,n){return{x:e.x+t,y:e.y+n,w:e.w,h:e.h}},rectCenterDistance:function(e,t){return me(ye(e),ye(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};var we=1,ve=4,be=2,xe=3,Ce=2,ke=4,Ae=3,Te=9,ze=1,Se=2,Ie=3,Pe=4,_e=5,De=6,Be=7,Ee=8,Oe=10,Ue=1,Me=2,Ne=3;class Ge{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){return this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295,e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),l=t[n];t[n]=t[e],t[e]=l}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new Ge(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const $e=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},Re=(...e)=>{const t=t=>(...n)=>{const l=new Date,i=$e(l.getHours(),"00"),o=$e(l.getMinutes(),"00"),a=$e(l.getSeconds(),"00");console[t](`${i}:${o}:${a}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var Ve,je,Fe,Le,We={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodeTile:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodeTile:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return Array.isArray(e)?e:[e.id,e.rng.type,Ge.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode]},decodeGame:function(e){return Array.isArray(e)?{id:e[0],rng:{type:e[1],obj:Ge.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}:e},coordByTileIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(let n in e){const l=[n,e[n]].map(encodeURIComponent);t.push(l.join("="))}return 0===t.length?"":`?${t.join("&")}`}};(je=Ve||(Ve={}))[je.Flat=0]="Flat",je[je.Out=1]="Out",je[je.In=-1]="In",(Le=Fe||(Fe={}))[Le.FINAL=0]="FINAL",Le[Le.ANY=1]="ANY";const qe={};function He(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}function Qe(e,t){let n=0;for(let l of qe[e].players){if(We.decodePlayer(l).id===t)return n;n++}return-1}function Ye(e,t){const n=Qe(e,t);return-1===n?null:We.decodePlayer(qe[e].players[n])}function Ke(e,t,n){const l=Qe(e,t);-1===l?qe[e].players.push(We.encodePlayer(n)):qe[e].players[l]=We.encodePlayer(n)}function Ze(e,t){return-1!==Qe(e,t)}function Je(e){return qe[e]?qe[e].players.map(We.decodePlayer):[]}function Xe(e){return qe[e].puzzle.tiles.length}function et(e){return qe[e].scoreMode||0}function tt(e){return nt(e)===Xe(e)}function nt(e){let t=0;for(let n of qe[e].puzzle.tiles)-1===We.decodeTile(n).owner&&t++;return t}function lt(e,t,n){const l=Ye(e,t);if(null!==l){for(let e of Object.keys(n))l[e]=n[e];Ke(e,t,l)}}function it(e,t){for(let n of Object.keys(t))qe[e].puzzle.data[n]=t[n]}function ot(e,t,n){for(let l of Object.keys(n)){const i=We.decodeTile(qe[e].puzzle.tiles[t]);i[l]=n[l],qe[e].puzzle.tiles[t]=We.encodeTile(i)}}const at=(e,t)=>We.decodeTile(qe[e].puzzle.tiles[t]),st=(e,t)=>at(e,t).group,rt=(e,t)=>{const n=qe[e].puzzle.info,l={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},i=function(e,t){const n=qe[e].puzzle.info,l=We.coordByTileIdx(n,t),i=l.x*n.tileSize,o=l.y*n.tileSize;return{x:i,y:o}}(e,t);return fe.pointAdd(l,i)},dt=(e,t)=>at(e,t).pos,ct=e=>{const t=Tt(e),n=zt(e),l=Math.round(t/4),i=Math.round(n/4);return{x:0-l,y:0-i,w:t+2*l,h:n+2*i}},ut=(e,t)=>{const n=mt(e),l=at(e,t);return{x:l.pos.x,y:l.pos.y,w:n,h:n}},gt=(e,t)=>at(e,t).z,pt=(e,t)=>{for(let n of qe[e].puzzle.tiles){const e=We.decodeTile(n);if(e.owner===t)return e.idx}return-1},ht=e=>qe[e].puzzle.info.tileDrawSize,mt=e=>qe[e].puzzle.info.tileSize,yt=e=>qe[e].puzzle.data.maxGroup,ft=e=>qe[e].puzzle.data.maxZ;function wt(e,t){const n=qe[e].puzzle.info,l=We.coordByTileIdx(n,t);return[l.y>0?t-n.tilesX:-1,l.x0?t-1:-1]}const vt=(e,t,n)=>{for(let l of t)ot(e,l,{z:n})},bt=(e,t,n)=>{const l=dt(e,t);ot(e,t,{pos:fe.pointAdd(l,n)})},xt=(e,t,n)=>{const l=ht(e),i=ct(e),o=n;for(let a of t){const t=at(e,a);t.pos.x+n.xi.x+i.w&&(o.x=Math.min(i.x+i.w-t.pos.x+l,o.x)),t.pos.y+n.yi.y+i.h&&(o.y=Math.min(i.y+i.h-t.pos.y+l,o.y))}for(let a of t)bt(e,a,o)},Ct=(e,t,n)=>{for(let l of t)ot(e,l,{owner:n})};function kt(e,t){const n=qe[e].puzzle.tiles,l=We.decodeTile(n[t]),i=[];if(l.group)for(let o of n){const e=We.decodeTile(o);e.group===l.group&&i.push(e.idx)}else i.push(l.idx);return i}const At=(e,t)=>{const n=Ye(e,t);return n?n.points:0},Tt=e=>qe[e].puzzle.info.table.width,zt=e=>qe[e].puzzle.info.table.height;var St={__createPlayerObject:He,setGame:function(e,t){qe[e]=t},exists:function(e){return!!qe[e]||!1},playerExists:Ze,getActivePlayers:function(e,t){const n=t-30*_;return Je(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return Je(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Ze(e,t)?lt(e,t,{ts:n}):Ke(e,t,He(t,n))},getFinishedTileCount:nt,getTileCount:Xe,getImageUrl:function(e){return qe[e].puzzle.info.imageUrl},setImageUrl:function(e,t){qe[e].puzzle.info.imageUrl=t},get:function(e){return qe[e]},getAllGames:function(){return Object.values(qe).sort(((e,t)=>tt(e.id)===tt(t.id)?t.puzzle.data.started-e.puzzle.data.started:tt(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Ye(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Ye(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Ye(e,t);return n?n.name:null},getPlayerIndexById:Qe,getPlayerIdByIndex:function(e,t){return qe[e].players.length>t?We.decodePlayer(qe[e].players[t]).id:null},changePlayer:lt,setPlayer:Ke,setTile:function(e,t,n){qe[e].puzzle.tiles[t]=We.encodeTile(n)},setPuzzleData:function(e,t){qe[e].puzzle.data=t},getTableWidth:Tt,getTableHeight:zt,getPuzzle:e=>qe[e].puzzle,getRng:e=>qe[e].rng.obj,getPuzzleWidth:e=>qe[e].puzzle.info.width,getPuzzleHeight:e=>qe[e].puzzle.info.height,getTilesSortedByZIndex:function(e){return qe[e].puzzle.tiles.map(We.decodeTile).sort(((e,t)=>e.z-t.z))},getFirstOwnedTile:(e,t)=>{const n=pt(e,t);return n<0?null:qe[e].puzzle.tiles[n]},getTileDrawOffset:e=>qe[e].puzzle.info.tileDrawOffset,getTileDrawSize:ht,getFinalTilePos:rt,getStartTs:e=>qe[e].puzzle.data.started,getFinishTs:e=>qe[e].puzzle.data.finished,handleInput:function(e,t,n,l){const i=qe[e].puzzle,o=function(e,t){return t in qe[e].evtInfos?qe[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),a=[],s=()=>{a.push([Ue,i.data])},r=t=>{a.push([Me,We.encodeTile(at(e,t))])},d=e=>{for(const t of e)r(t)},c=()=>{const n=Ye(e,t);n&&a.push([Ne,We.encodePlayer(n)])},u=n[0];if(u===De){const i=n[1];lt(e,t,{bgcolor:i,ts:l}),c()}else if(u===Be){const i=n[1];lt(e,t,{color:i,ts:l}),c()}else if(u===Ee){const i=`${n[1]}`.substr(0,16);lt(e,t,{name:i,ts:l}),c()}else if(u===ze){const i={x:n[1],y:n[2]};lt(e,t,{d:1,ts:l}),c(),o._last_mouse_down=i;const a=((e,t)=>{let n=qe[e].puzzle.info,l=qe[e].puzzle.tiles,i=-1,o=-1;for(let a=0;ai)&&(i=e.z,o=a)}return o})(e,i);if(a>=0){let n=ft(e)+1;it(e,{maxZ:n}),s();const l=kt(e,a);vt(e,l,ft(e)),Ct(e,l,t),d(l)}o._last_mouse=i}else if(u===Ie){const i=n[1],a=n[2],s={x:i,y:a};if(null===o._last_mouse_down)lt(e,t,{x:i,y:a,ts:l}),c();else{let n=pt(e,t);if(n>=0){lt(e,t,{x:i,y:a,ts:l}),c();const r=kt(e,n);let u=fe.pointInBounds(s,ct(e))&&fe.pointInBounds(o._last_mouse_down,ct(e));for(let t of r){const n=ut(e,t);if(fe.pointInBounds(s,n)){u=!0;break}}if(u){const t=i-o._last_mouse_down.x,n=a-o._last_mouse_down.y;xt(e,r,{x:t,y:n}),d(r)}}else lt(e,t,{ts:l}),c();o._last_mouse_down=s}o._last_mouse=s}else if(u===Se){const a={x:n[1],y:n[2]},u=0;o._last_mouse_down=null;let g=pt(e,t);if(g>=0){let n=kt(e,g);Ct(e,n,0),d(n);let o=dt(e,g),a=rt(e,g);if(fe.pointDistance(a,o){for(let n of t)ot(e,n,{owner:-1,z:1})})(e,n),d(n);let r=At(e,t);0===et(e)?r+=n.length:1===et(e)&&(r+=1),lt(e,t,{d:u,ts:l,points:r}),c(),nt(e)===Xe(e)&&(it(e,{finished:l}),s())}else{const n=(e,t,n,l)=>{let i=qe[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const l=st(e,t),i=st(e,n);return!(!l||l!==i)})(e,t,n))return!1;const o=dt(e,t),a=fe.pointAdd(dt(e,n),{x:l[0]*i.tileSize,y:l[1]*i.tileSize});if(fe.pointDistance(o,a){const l=qe[e].puzzle.tiles,i=st(e,t),o=st(e,n);let a;const d=[];i&&d.push(i),o&&d.push(o),i?a=i:o?a=o:(it(e,{maxGroup:yt(e)+1}),s(),a=yt(e));if(ot(e,t,{group:a}),r(t),ot(e,n,{group:a}),r(n),d.length>0)for(const s of l){const t=We.decodeTile(s);d.includes(t.group)&&(ot(e,t.idx,{group:a}),r(t.idx))}})(e,t,n),i=kt(e,t);const c=((e,t)=>{let n=0;for(let l of t){let t=gt(e,l);t>n&&(n=t)}return n})(e,i);return vt(e,i,c),d(i),!0}return!1};let i=!1;for(let t of kt(e,g)){let l=wt(e,t);if(n(e,t,l[0],[0,1])||n(e,t,l[1],[-1,0])||n(e,t,l[2],[0,-1])||n(e,t,l[3],[1,0])){i=!0;break}}if(i&&1===et(e)){const n=At(e,t)+1;lt(e,t,{d:u,ts:l,points:n}),c()}else lt(e,t,{d:u,ts:l}),c()}}else lt(e,t,{d:u,ts:l}),c();o._last_mouse=a}else if(u===Pe){const i=n[1],a=n[2];lt(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else if(u===_e){const i=n[1],a=n[2];lt(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else lt(e,t,{ts:l}),c();return function(e,t,n){qe[e].evtInfos[t]=n}(e,t,o),a}},It=e({name:"new-game-dialog",components:{ResponsiveImage:W},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Fe.ANY}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Pt={class:"area-image"},_t={class:"has-image"},Dt={class:"area-settings"},Bt=n("td",null,[n("label",null,"Pieces")],-1),Et=n("td",null,[n("label",null,"Scoring: ")],-1),Ot=s(" Any (Score when pieces are connected to each other or on final location)"),Ut=n("br",null,null,-1),Mt=s(" Final (Score when pieces are put to their final location)"),Nt={class:"area-buttons"};It.render=function(e,l,i,s,r,d){const c=o("responsive-image");return a(),t("div",{class:"overlay new-game-dialog",onClick:l[6]||(l[6]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[5]||(l[5]=u((()=>{}),["stop"]))},[n("div",Pt,[n("div",_t,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Dt,[n("table",null,[n("tr",null,[Bt,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Et,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[2]||(l[2]=t=>e.scoreMode=t),value:"1"},null,512),[[y,e.scoreMode]]),Ot]),Ut,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[3]||(l[3]=t=>e.scoreMode=t),value:"0"},null,512),[[y,e.scoreMode]]),Mt])])])])]),n("div",Nt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:l[4]||(l[4]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var Gt=e({components:{ImageLibrary:L,NewImageDialog:Q,EditImageDialog:se,NewGameDialog:It},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},methods:{toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${We.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const $t={class:"upload-image-teaser"},Rt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),Vt={key:0},jt=s(" Tags: "),Ft=s(" Sort by: "),Lt=n("option",{value:"date_desc"},"Newest first",-1),Wt=n("option",{value:"date_asc"},"Oldest first",-1),qt=n("option",{value:"alpha_asc"},"A-Z",-1),Ht=n("option",{value:"alpha_desc"},"Z-A",-1);Gt.render=function(e,l,s,u,p,h){const m=o("image-library"),y=o("new-image-dialog"),w=o("edit-image-dialog"),v=o("new-game-dialog");return a(),t("div",null,[n("div",$t,[n("div",{class:"btn btn-big",onClick:l[1]||(l[1]=t=>e.dialog="new-image")},"Upload your image"),Rt]),n("div",null,[e.tags.length>0?(a(),t("label",Vt,[jt,(a(!0),t(d,null,c(e.tags,((n,l)=>(a(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:l,onClick:t=>e.toggleTag(n)},r(n.title),11,["onClick"])))),128))])):i("",!0),n("label",null,[Ft,g(n("select",{"onUpdate:modelValue":l[2]||(l[2]=t=>e.filters.sort=t),onChange:l[3]||(l[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[Lt,Wt,qt,Ht],544),[[f,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(a(),t(y,{key:0,onBgclick:l[4]||(l[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["onPostToGalleryClick","onSetupGameClick"])):i("",!0),"edit-image"===e.dialog?(a(),t(w,{key:1,onBgclick:l[5]||(l[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["onSaveClick","image"])):i("",!0),e.image&&"new-game"===e.dialog?(a(),t(v,{key:2,onBgclick:l[6]||(l[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):i("",!0)])};var Qt=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"},Kt=n("div",null,"Scores",-1),Zt=n("td",null,"⚡",-1),Jt=n("td",null,"💤",-1);Qt.render=function(e,l,i,o,s,u){return a(),t("div",Yt,[Kt,n("table",null,[(a(!0),t(d,null,c(e.actives,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[Zt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(a(!0),t(d,null,c(e.idles,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[Jt,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 E(this.duration)}}});const en={class:"timer"};Xt.render=function(e,l,i,o,s,d){return a(),t("div",en,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),w(e.$slots,"default")])};var tn=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 nn=n("td",null,[n("label",null,"Background: ")],-1),ln=n("td",null,[n("label",null,"Color: ")],-1),on=n("td",null,[n("label",null,"Name: ")],-1);tn.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("tr",null,[nn,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[1]||(l[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[ln,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[2]||(l[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[on,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":l[3]||(l[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])])])])};var an=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const sn={class:"preview"};an.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay",onClick:l[1]||(l[1]=t=>e.$emit("bgclick"))},[n("div",sn,[n("div",{class:"img",style:e.previewStyle},null,4)])])};const rn=Re("Communication.js");let dn,cn=e=>{},un=e=>{};let gn=0;const pn=e=>{gn!==e&&(gn=e,un(e))};function hn(e){if(2===gn)try{dn.send(JSON.stringify(e))}catch(t){rn.info("unable to send message.. maybe because ws is invalid?")}}let mn,yn;var fn={connect:function(e,t,n){return mn=0,yn={},pn(3),new Promise((l=>{dn=new WebSocket(e,n+"|"+t),dn.onopen=e=>{pn(2),hn([xe])},dn.onmessage=e=>{const t=JSON.parse(e.data),i=t[0];if(i===ve){const e=t[1];l(e)}else{if(i!==we)throw`[ 2021-05-09 invalid connect msgType ${i} ]`;{const e=t[1],l=t[2];if(e===n&&yn[l])return void delete yn[l];cn(t)}}},dn.onerror=e=>{throw pn(1),"[ 2021-05-15 onerror ]"},dn.onclose=e=>{4e3===e.code||1001===e.code?pn(4):pn(1)}}))},requestReplayData:async function(e,t,n){const l=await fetch(`/api/replay-data?gameId=${e}&offset=${t}&size=${n}`);return await l.json()},disconnect:function(){dn&&dn.close(4e3),mn=0,yn={}},sendClientEvent:function(e){mn++,yn[mn]=e,hn([be,mn,yn[mn]])},onServerChange:function(e){cn=e},onConnectionStateChange:function(e){un=e},CODE_CUSTOM_DISCONNECT:4e3,CONN_STATE_NOT_CONNECTED:0,CONN_STATE_DISCONNECTED:1,CONN_STATE_CLOSED:4,CONN_STATE_CONNECTED:2,CONN_STATE_CONNECTING:3},wn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===fn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===fn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const vn={key:0,class:"overlay connection-lost"},bn={key:0,class:"overlay-content"},xn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Cn={key:1,class:"overlay-content"},kn=n("div",null,"Connecting...",-1);wn.render=function(e,l,o,s,r,d){return e.show?(a(),t("div",vn,[e.lostConnection?(a(),t("div",bn,[xn,n("span",{class:"btn",onClick:l[1]||(l[1]=t=>e.$emit("reconnect"))},"Reconnect")])):i("",!0),e.connecting?(a(),t("div",Cn,[kn])):i("",!0)])):i("",!0)};var An=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"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),zn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),Sn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),In=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),Pn=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),_n=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),Dn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),Bn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),En=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);An.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[Tn,zn,Sn,In,Pn,_n,Dn,Bn,En,On])])};var Un=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Mn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Gn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function $n(){let e=0,t=0,n=1;const l=(l,i)=>{e+=l/n,t+=i/n},i=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},o=l=>({x:l.x/n-e,y:l.y/n-t}),a=l=>({x:(l.x+e)*n,y:(l.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n});return{move:l,canZoom:e=>n!=i(e),zoom:(e,t)=>((e,t)=>{if(n==e)return!1;const i=1-n/e;return l(-t.x*i,-t.y*i),n=e,!0})(i(e),t),worldToViewport:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:a,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=o(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:o}}function Rn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Vn={createCanvas:Rn,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=Rn(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=Rn(e.width,e.height),i=l.getContext("2d");return i.save(),i.drawImage(t,0,0),i.fillStyle=n,i.globalCompositeOperation="source-in",i.fillRect(0,0,t.width,t.height),i.restore(),i.save(),i.globalCompositeOperation="destination-over",i.drawImage(e,0,0),i.restore(),l}};const jn=Re("Debug.js");let Fn=0,Ln=0;var Wn=e=>{Fn=performance.now(),Ln=e},qn=e=>{const t=performance.now(),n=t-Fn;n>Ln&&jn.log(e+": "+n),Fn=t};const Hn=Re("PuzzleGraphics.js");function Qn(e,t){const n=We.coordByTileIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Yn={loadPuzzleBitmaps:async function(e){const t=await Vn.loadImageToBitmap(e.info.imageUrl),n=await Vn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Hn.log("start createPuzzleTileBitmaps");var l=n.tileSize,i=n.tileMarginWidth,o=n.tileDrawSize,a=l/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0];const r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,o={x:i,y:i},r=fe.pointAdd(o,{x:l,y:0}),c=fe.pointAdd(r,{x:0,y:l}),u=fe.pointSub(c,{x:l,y:0});if(n.moveTo(o.x,o.y),0!==e.top)for(let l=0;l=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Jn=t/2,Xn=t-Jn;const n=1/4*this.canvas.width/(t/2);Kn=-n,Zn=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new el(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new el(this.rng));const e=[];for(;this.explodedBombs.length>0;){const t=this.explodedBombs.shift();if(!t)break;t.update(),t.alive&&e.push(t)}this.explodedBombs=e;const t=[];for(;this.readyBombs.length>0;){const e=this.readyBombs.shift();if(!e)break;e.update(this.particles),e.hasExploded?this.explodedBombs.push(e):t.push(e)}this.readyBombs=t;const n=[];for(;this.particles.length>0;){const e=this.particles.shift();if(!e)break;e.update(),e.alive&&n.push(e)}this.particles=n}render(){this.ctx.beginPath(),this.ctx.fillStyle="rgba(0, 0, 0, 0.1)",this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height);for(let e=0;e{const t=e.color+" "+e.d;if(!h[t]){const n=e.d?a:s;if(e.color){const l=e.d?r:d;h[t]=await createImageBitmap(Vn.colorizedCanvas(n,l,e.color))}else h[t]=n}return h[t]},y=function(e,t){return t.width=window.innerWidth,t.height=window.innerHeight,e.appendChild(t),window.addEventListener("resize",(()=>{t.width=window.innerWidth,t.height=window.innerHeight,al=!0})),t}(i,Vn.createCanvas()),f={final:!1,requesting:!0,log:[],logPointer:0,logIdx:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0};fn.onConnectionStateChange((e=>{o.setConnectionState(e)}));let w=()=>0;const v=async()=>{if("play"===l){const l=await fn.connect(n,e,t),i=We.decodeGame(l);St.setGame(i.id,i),w=()=>D()}else{if("replay"!==l)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await fn.requestReplayData(e,0,1e4),n=We.decodeGame(t.game);St.setGame(n.id,n),f.requesting=!1,f.log=t.log,f.lastRealTs=D(),f.gameStartTs=parseInt(f.log[0][4],10),f.lastGameTs=f.gameStartTs,w=()=>f.lastGameTs}}al=!0};await v();const b=St.getTileDrawOffset(e),x=St.getTileDrawSize(e),C=St.getPuzzleWidth(e),k=St.getPuzzleHeight(e),A=St.getTableWidth(e),T=St.getTableHeight(e),z={x:(A-C)/2,y:(T-k)/2},S={w:C,h:k},I={w:x,h:x},P=await Yn.loadPuzzleBitmaps(St.getPuzzle(e)),_=new nl(y,St.getRng(e));_.init();const B=y.getContext("2d");y.classList.add("loaded");const E=$n();E.move(-(A-y.width)/2,-(T-y.height)/2);const O=function(e,t,n){let l=[],i=!0,o=!1,a=!1,s=!1,r=!1,d=!1,c=!1,u=!1;const g=(e,t)=>{const l=n.viewportToWorld({x:e,y:t});return[l.x,l.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{i&&("Shift"===t.key?u=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?s=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?r=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?o=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?a=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};e.addEventListener("mousedown",(e=>{0===e.button&&y([ze,...p(e)])})),e.addEventListener("mouseup",(e=>{0===e.button&&y([Se,...p(e)])})),e.addEventListener("mousemove",(e=>{y([Ie,...p(e)])})),e.addEventListener("wheel",(e=>{if(n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Pe:_e;y([t,...p(e)])}})),t.addEventListener("keydown",(e=>m(!0,e))),t.addEventListener("keyup",(e=>m(!1,e))),t.addEventListener("keypress",(e=>{i&&(" "===e.key&&y([Oe]),"F"!==e.key&&"f"!==e.key||(il=!il,al=!0),"G"!==e.key&&"g"!==e.key||(ol=!ol,al=!0))}));const y=e=>{l.push(e)};return{addEvent:y,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=u?20:10,t=(o?e:0)-(a?e:0),l=(s?e:0)-(r?e:0);0===t&&0===l||y([Te,t,l]),d&&c||(d?n.canZoom("in")&&y([Pe,...h()]):c&&n.canZoom("out")&&y([_e,...h()]))},setHotkeys:e=>{i=e}}}(y,window,E),U=St.getImageUrl(e),M=()=>{const t=St.getStartTs(e),n=St.getFinishTs(e),l=w();o.setFinished(!!n),o.setDuration((n||l)-t)};M(),o.setPiecesDone(St.getFinishedTileCount(e)),o.setPiecesTotal(St.getTileCount(e));const N=w();o.setActivePlayers(St.getActivePlayers(e,N)),o.setIdlePlayers(St.getIdlePlayers(e,N));const G=!!St.getFinishTs(e);let $=G;const R=()=>$&&!G,V=()=>St.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",j=()=>St.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let F="",L="",W=!1;const q=e=>{W=e;const[t,n]=e?[F,"grab"]:[L,"default"];y.style.cursor=`url('${t}') ${u} ${p}, ${n}`},H=e=>{F=Vn.colorizedCanvas(a,r,e).toDataURL(),L=Vn.colorizedCanvas(s,d,e).toDataURL(),q(W)};H(j());const Q=()=>{o.setReplaySpeed&&o.setReplaySpeed(f.speeds[f.speedIdx]),o.setReplayPaused&&o.setReplayPaused(f.paused)};if("play"===l?setInterval(M,1e3):"replay"===l&&Q(),"play"===l)fn.onServerChange((n=>{n[0],n[1],n[2];const l=n[3];for(const[i,o]of l)switch(i){case Ne:{const n=We.decodePlayer(o);n.id!==t&&(St.setPlayer(e,n.id,n),al=!0)}break;case Me:{const t=We.decodeTile(o);St.setTile(e,t.idx,t),al=!0}break;case Ue:St.setPuzzleData(e,o),al=!0}$=!!St.getFinishTs(e)}));else if("replay"===l){let t=setInterval((()=>{const n=D();if(f.requesting)return void(f.lastRealTs=n);if(f.logPointer+1>=f.log.length)return f.lastRealTs=n,f.requesting=!0,void(async(e,t,n)=>{const l=await fn.requestReplayData(e,t,n);f.log=f.log.slice(f.logPointer),f.logPointer=0,f.log.push(...l.log),l.log.length<1e4&&(f.final=!0),f.requesting=!1})(e,f.logIdx,1e4);if(f.paused)return void(f.lastRealTs=n);const l=(n-f.lastRealTs)*f.speeds[f.speedIdx],i=f.lastGameTs+l;for(;;){if(f.paused)break;const n=f.logPointer+1;if(n>=f.log.length){f.final&&clearInterval(t);break}const l=f.log[n],o=f.gameStartTs+l[l.length-1];if(o>i)break;const a=l.slice();if(a[0]===Ce){const t=a[1];St.addPlayer(e,t,o),al=!0}else if(a[0]===ke){const t=St.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";St.addPlayer(e,t,o),al=!0}else if(a[0]===Ae){const t=St.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=a[2];St.handleInput(e,t,n,o),al=!0}f.logPointer=n,f.logIdx++}f.lastRealTs=n,f.lastGameTs=i,M()}),50)}let Y=null;return(e=>{const t=e.fps||60,n=e.slow||1,l=e.update,i=e.render,o=window.requestAnimationFrame,a=1/t,s=n*a;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>s;)d-=s,l(a);i(d/n),c=r,o(u)};o(u)})({update:()=>{O.createKeyEvents();for(const n of O.consumeAll())if("play"===l){const l=n[0];if(l===Te){const e=n[1],t=n[2];al=!0,E.move(e,t)}else if(l===Ie){if(Y&&!St.getFirstOwnedTile(e,t)){const e={x:n[1],y:n[2]},t=E.worldToViewport(e),l=Math.round(t.x-Y.x),i=Math.round(t.y-Y.y);al=!0,E.move(l,i),Y=t}}else if(l===Be)H(n[1]);else if(l===ze){const e={x:n[1],y:n[2]};Y=E.worldToViewport(e),q(!0)}else if(l===Se)Y=null,q(!1);else if(l===Pe){const e={x:n[1],y:n[2]};al=!0,E.zoom("in",E.worldToViewport(e))}else if(l===_e){const e={x:n[1],y:n[2]};al=!0,E.zoom("out",E.worldToViewport(e))}else l===Oe&&o.togglePreview();const i=w();St.handleInput(e,t,n,i).length>0&&(al=!0),fn.sendClientEvent(n)}else if("replay"===l){const e=n[0];if(e===Te){const e=n[1],t=n[2];al=!0,E.move(e,t)}else if(e===Ie){if(Y){const e={x:n[1],y:n[2]},t=E.worldToViewport(e),l=Math.round(t.x-Y.x),i=Math.round(t.y-Y.y);al=!0,E.move(l,i),Y=t}}else if(e===ze){const e={x:n[1],y:n[2]};Y=E.worldToViewport(e)}else if(e===Se)Y=null;else if(e===Pe){const e={x:n[1],y:n[2]};al=!0,E.zoom("in",E.worldToViewport(e))}else if(e===_e){const e={x:n[1],y:n[2]};al=!0,E.zoom("out",E.worldToViewport(e))}else e===Oe&&o.togglePreview()}$=!!St.getFinishTs(e),R()&&(_.update(),al=!0)},render:async()=>{if(!al)return;const n=w();let i,a,s;window.DEBUG&&Wn(0),B.fillStyle=V(),B.fillRect(0,0,y.width,y.height),window.DEBUG&&qn("clear done"),i=E.worldToViewportRaw(z),a=E.worldDimToViewportRaw(S),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(i.x,i.y,a.w,a.h),window.DEBUG&&qn("board done");const r=St.getTilesSortedByZIndex(e);window.DEBUG&&qn("get tiles done"),a=E.worldDimToViewportRaw(I);for(const e of r)(-1===e.owner?il:ol)&&(s=P[e.idx],i=E.worldToViewportRaw({x:b+e.pos.x,y:b+e.pos.y}),B.drawImage(s,0,0,s.width,s.height,i.x,i.y,a.w,a.h));window.DEBUG&&qn("tiles done");const d=[];for(const o of St.getActivePlayers(e,n))c=o,("replay"===l||c.id!==t)&&(s=await m(o),i=E.worldToViewport(o),B.drawImage(s,i.x-u,i.y-p),d.push([`${o.name} (${o.points})`,i.x,i.y+g]));var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,l]of d)B.fillText(e,t,l);window.DEBUG&&qn("players done"),o.setActivePlayers(St.getActivePlayers(e,n)),o.setIdlePlayers(St.getIdlePlayers(e,n)),o.setPiecesDone(St.getFinishedTileCount(e)),window.DEBUG&&qn("HUD done"),R()&&_.render(),al=!1}}),{setHotkeys:e=>{O.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),O.addEvent([De,e])},onColorChange:e=>{localStorage.setItem("player_color",e),O.addEvent([Be,e])},onNameChange:e=>{localStorage.setItem("player_name",e),O.addEvent([Ee,e])},replayOnSpeedUp:()=>{f.speedIdx+1{f.speedIdx>=1&&(f.speedIdx--,Q())},replayOnPauseToggle:()=>{f.paused=!f.paused,Q()},previewImageUrl:U,player:{background:V(),color:j(),name:St.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon"},disconnect:fn.disconnect,connect:v}}var rl=e({name:"game",components:{PuzzleStatus:Xt,Scores:Qt,SettingsOverlay:tn,PreviewOverlay:an,ConnectionOverlay:wn,HelpOverlay:An},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},disconnect:()=>{},connect:()=>{}}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await sl(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},setConnectionState:e=>{this.connectionState=e},togglePreview:()=>{this.toggle("preview",!1)}}))},unmounted(){this.g.disconnect()},methods:{reconnect(){this.g.connect()},toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}}});const dl={id:"game"},cl={class:"menu"},ul={class:"tabs"},gl=s("🧩 Puzzles");rl.render=function(e,i,s,r,d,c){const u=o("settings-overlay"),p=o("preview-overlay"),h=o("help-overlay"),m=o("connection-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",dl,[g(n(u,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(p,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(h,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",cl,[n("div",ul,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[gl])),_:1}),n("div",{class:"opener",onClick:i[5]||(i[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var pl=e({name:"replay",components:{PuzzleStatus:Xt,Scores:Qt,SettingsOverlay:tn,PreviewOverlay:an,HelpOverlay:An},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},disconnect:()=>{}},replay:{speed:1,paused:!1}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await sl(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"replay",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},setReplaySpeed:e=>{this.replay.speed=e},setReplayPaused:e=>{this.replay.paused=e}}))},unmounted(){this.g.disconnect()},methods:{toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}},computed:{replayText(){return"Replay-Speed: "+this.replay.speed+"x"+(this.replay.paused?" Paused":"")}}});const hl={id:"replay"},ml={class:"menu"},yl={class:"tabs"},fl=s("🧩 Puzzles");pl.render=function(e,i,s,d,c,u){const p=o("settings-overlay"),h=o("preview-overlay"),m=o("help-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",hl,[g(n(p,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(m,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:l((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:i[5]||(i[5]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",ml,[n("div",yl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[fl])),_:1}),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=b({history:x(),routes:[{name:"index",path:"/",component:R},{name:"new-game",path:"/new-game",component:Gt},{name:"game",path:"/g/:id",component:rl},{name:"replay",path:"/replay/:id",component:pl}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const l=C(k);l.config.globalProperties.$config=t,l.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=We.uniqId(),localStorage.setItem("ID",e)),e}(),l.use(n),l.mount("#app")})(); diff --git a/build/public/index.html b/build/public/index.html index 24e70df..bf7b9b2 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 51e96ab..117d365 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -1,5 +1,6 @@ import WebSocket from 'ws'; import express from 'express'; +import compression from 'compression'; import multer from 'multer'; import fs from 'fs'; import readline from 'readline'; @@ -1916,6 +1917,7 @@ const log = logger('main.js'); const port = config.http.port; const hostname = config.http.hostname; const app = express(); +app.use(compression()); const storage = multer.diskStorage({ destination: UPLOAD_DIR, filename: function (req, file, cb) { diff --git a/package-lock.json b/package-lock.json index 9d2fc65..7246c08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ }, "devDependencies": { "@types/better-sqlite3": "^5.4.1", + "@types/compression": "^1.7.0", "@types/exif": "^0.6.2", "@types/express": "^4.17.11", "@types/multer": "^1.4.5", @@ -25,6 +26,7 @@ "@types/ws": "^7.4.4", "@vitejs/plugin-vue": "^1.2.2", "@vuedx/typescript-plugin-vue": "^0.6.3", + "compression": "^1.7.4", "jest": "^26.6.3", "rollup": "^2.48.0", "rollup-plugin-typescript2": "^0.30.0", @@ -1477,6 +1479,15 @@ "integrity": "sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw==", "dev": true }, + "node_modules/@types/compression": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.0.tgz", + "integrity": "sha512-3LzWUM+3k3XdWOUk/RO+uSjv7YWOatYq2QADJntK1pjkk4DfVP0KrIEPDnXRJxAAGKe0VpIPRmlINLDuCedZWw==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.34", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", @@ -3165,6 +3176,60 @@ "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", "dev": true }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -7871,6 +7936,15 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -12192,6 +12266,15 @@ "integrity": "sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw==", "dev": true }, + "@types/compression": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.7.0.tgz", + "integrity": "sha512-3LzWUM+3k3XdWOUk/RO+uSjv7YWOatYq2QADJntK1pjkk4DfVP0KrIEPDnXRJxAAGKe0VpIPRmlINLDuCedZWw==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/connect": { "version": "3.4.34", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", @@ -13575,6 +13658,53 @@ "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", "dev": true }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -17248,6 +17378,12 @@ "ee-first": "1.1.1" } }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", diff --git a/package.json b/package.json index aef04d8..d36af29 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ }, "devDependencies": { "@types/better-sqlite3": "^5.4.1", + "@types/compression": "^1.7.0", "@types/exif": "^0.6.2", "@types/express": "^4.17.11", "@types/multer": "^1.4.5", @@ -21,6 +22,7 @@ "@types/ws": "^7.4.4", "@vitejs/plugin-vue": "^1.2.2", "@vuedx/typescript-plugin-vue": "^0.6.3", + "compression": "^1.7.4", "jest": "^26.6.3", "rollup": "^2.48.0", "rollup-plugin-typescript2": "^0.30.0", diff --git a/src/frontend/game.ts b/src/frontend/game.ts index 06da228..cd69b1a 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -268,7 +268,7 @@ export async function main( log: [], logPointer: 0, logIdx: 0, - speeds: [0.5, 1, 2, 5, 10, 20, 50], + speeds: [0.5, 1, 2, 5, 10, 20, 50, 100, 250, 500], speedIdx: 1, paused: false, lastRealTs: 0, diff --git a/src/server/main.ts b/src/server/main.ts index bd04709..a25ece8 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -1,6 +1,7 @@ import WebSocketServer from './WebSocketServer' import WebSocket from 'ws' import express from 'express' +import compression from 'compression' import multer from 'multer' import Protocol from './../common/Protocol' import Util, { logger } from './../common/Util' @@ -17,7 +18,6 @@ import { DB_PATCHES_DIR, PUBLIC_DIR, UPLOAD_DIR, - UPLOAD_URL } from './Dirs' import { GameSettings, ScoreMode } from '../common/GameCommon' import GameStorage from './GameStorage' @@ -47,6 +47,8 @@ const port = config.http.port const hostname = config.http.hostname const app = express() +app.use(compression()) + const storage = multer.diskStorage({ destination: UPLOAD_DIR, filename: function (req, file, cb) { From 7b1f27058738f5f36a9fe8c3bfe88c68fbe560f1 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 29 May 2021 14:11:40 +0200 Subject: [PATCH 07/78] server restart only on js file changes --- scripts/server | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/server b/scripts/server index e91a29c..182e3a4 100755 --- a/scripts/server +++ b/scripts/server @@ -1,4 +1,4 @@ #!/bin/sh # server for built files -nodemon --max-old-space-size=64 build/server/main.js -c config.json +nodemon --max-old-space-size=64 -e js build/server/main.js -c config.json From 46f3fc74800d2df46aade9ffee5801ae88570907 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 29 May 2021 15:36:03 +0200 Subject: [PATCH 08/78] type hints galore! --- build/public/assets/index.643c957c.js | 1 + build/public/assets/index.8f906b9e.js | 1 - build/public/index.html | 2 +- build/server/main.js | 873 +++++++++++++------------- scripts/fix_tiles.ts | 8 +- src/common/GameCommon.ts | 233 ++++--- src/common/Rng.ts | 2 +- src/common/Types.ts | 6 + src/common/Util.ts | 34 +- src/frontend/Communication.ts | 12 +- src/frontend/PuzzleGraphics.ts | 2 +- src/frontend/game.ts | 94 +-- src/server/Game.ts | 30 +- src/server/GameLog.ts | 6 +- src/server/GameStorage.ts | 2 +- src/server/Puzzle.ts | 31 +- src/server/main.ts | 30 +- 17 files changed, 700 insertions(+), 667 deletions(-) create mode 100644 build/public/assets/index.643c957c.js delete mode 100644 build/public/assets/index.8f906b9e.js create mode 100644 src/common/Types.ts diff --git a/build/public/assets/index.643c957c.js b/build/public/assets/index.643c957c.js new file mode 100644 index 0000000..77b35d4 --- /dev/null +++ b/build/public/assets/index.643c957c.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as l,b as i,r as o,o as a,e as s,t as r,F as d,f as c,g as u,h as g,v as p,i as h,j as m,k as y,l as f,m as w,n as v,p as b,q as x,s as C}from"./vendor.b622ee49.js";var k=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const A={id:"app"},z={key:0,class:"nav"},S=s("Index"),P=s("New game");k.render=function(e,s,r,d,c,u){const g=o("router-link"),p=o("router-view");return a(),t("div",A,[e.showNav?(a(),t("ul",z,[n("li",null,[n(g,{class:"btn",to:{name:"index"}},{default:l((()=>[S])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:l((()=>[P])),_:1})])])):i("",!0),n(p)])};const I=864e5,T=e=>{const t=Math.floor(e/I);e%=I;const n=Math.floor(e/36e5);e%=36e5;const l=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${l}m ${Math.floor(e/1e3)}s`};var _=1e3,D=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>T(t-e),E=T,O=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",l=e,i=t||D();return`${n} ${B(l,i)}`}}});const U={class:"game-info-text"},N=n("br",null,null,-1),M=n("br",null,null,-1),G=n("br",null,null,-1),$=s(" ↪️ Watch replay ");O.render=function(e,d,c,u,g,p){const h=o("router-link");return a(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:l((()=>[n("span",U,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),N,s(" 👥 "+r(e.game.players),1),M,s(" "+r(e.time(e.game.started,e.game.finished)),1),G])])),_:1},8,["to"]),e.game.hasReplay?(a(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:l((()=>[$])),_:1},8,["to"])):i("",!0)],4)};var R=e({components:{GameTeaser:O},data:()=>({gamesRunning:[],gamesFinished:[]}),async created(){const e=await fetch("/api/index-data"),t=await e.json();this.gamesRunning=t.gamesRunning,this.gamesFinished=t.gamesFinished}});const V=n("h1",null,"Running games",-1),j=n("h1",null,"Finished games",-1);R.render=function(e,l,i,s,r,u){const g=o("game-teaser");return a(),t("div",null,[V,(a(!0),t(d,null,c(e.gamesRunning,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128)),j,(a(!0),t(d,null,c(e.gamesFinished,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128))])};var F=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});F.render=function(e,l,i,o,s,r){return a(),t("div",{class:"imageteaser",style:e.style,onClick:l[2]||(l[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:l[1]||(l[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var L=e({name:"image-library",components:{ImageTeaser:F},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});L.render=function(e,n,l,i,s,r){const u=o("image-teaser");return a(),t("div",null,[(a(!0),t(d,null,c(e.images,((n,l)=>(a(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:l},null,8,["image","onClick","onEditClick"])))),128))])};const W={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};W.render=function(e,n,l,i,o,s){return a(),t("div",{style:s.style,title:l.title},null,12,["title"])};var q=e({name:"tags-input",props:{modelValue:{type:Array,required:!0}},emits:{"update:modelValue":null},data:()=>({input:"",values:[]}),created(){this.values=this.modelValue},methods:{onKeyUp(e){if(","===e.key)return this.add(),e.stopPropagation(),!1},add(){const e=this.input.replace(/,/g,"").trim();e&&(this.values.includes(e)||this.values.push(e),this.input="",this.$emit("update:modelValue",this.values))},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const H=m()(((e,l,i,o,s,u)=>(a(),t("div",null,[g(n("input",{class:"input",type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.input=t),placeholder:"Plants, People",onKeydown:l[2]||(l[2]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:l[3]||(l[3]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[p,e.input]]),(a(!0),t(d,null,c(e.values,((n,l)=>(a(),t("span",{key:l,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));q.render=H,q.__scopeId="data-v-771460ae";var Q=e({name:"new-image-dialog",components:{ResponsiveImage:W,TagsInput:q},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const l=new FileReader;l.readAsDataURL(n),l.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const Y={key:0,class:"has-image"},K={key:1},Z={class:"upload"},J=n("span",{class:"btn"},"Upload File",-1),X={class:"area-settings"},ee=n("td",null,[n("label",null,"Title")],-1),te=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ne=n("td",null,[n("label",null,"Tags")],-1),le={class:"area-buttons"},ie=s("🧩 Post to gallery "),oe=n("br",null,null,-1),ae=s(" + set up game");Q.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay new-image-dialog",onClick:l[8]||(l[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[7]||(l[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(a(),t("div",Y,[n("span",{class:"remove btn",onClick:l[1]||(l[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(a(),t("div",K,[n("label",Z,[n("input",{type:"file",style:{display:"none"},onChange:l[2]||(l[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),J])]))],2),n("div",X,[n("table",null,[n("tr",null,[ee,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[3]||(l[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),te,n("tr",null,[ne,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[4]||(l[4]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",le,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:l[5]||(l[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:l[6]||(l[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[ie,oe,ae],8,["disabled"])])])])};var se=e({name:"edit-image-dialog",components:{ResponsiveImage:W,TagsInput:q},props:{image:{type:Object,required:!0}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const re={class:"area-image"},de={class:"has-image"},ce={class:"area-settings"},ue=n("td",null,[n("label",null,"Title")],-1),ge=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),pe=n("td",null,[n("label",null,"Tags")],-1),he={class:"area-buttons"};function me(e,t){const n=e.x-t.x,l=e.y-t.y;return Math.sqrt(n*n+l*l)}function ye(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}se.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay edit-image-dialog",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("div",re,[n("div",de,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ce,[n("table",null,[n("tr",null,[ue,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ge,n("tr",null,[pe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[2]||(l[2]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",he,[n("button",{class:"btn",onClick:l[3]||(l[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var fe={pointSub:function(e,t){return{x:e.x-t.x,y:e.y-t.y}},pointAdd:function(e,t){return{x:e.x+t.x,y:e.y+t.y}},pointDistance:me,pointInBounds:function(e,t){return e.x>=t.x&&e.x<=t.x+t.w&&e.y>=t.y&&e.y<=t.y+t.h},rectCenter:ye,rectMoved:function(e,t,n){return{x:e.x+t,y:e.y+n,w:e.w,h:e.h}},rectCenterDistance:function(e,t){return me(ye(e),ye(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};var we=1,ve=4,be=2,xe=3,Ce=2,ke=4,Ae=3,ze=9,Se=1,Pe=2,Ie=3,Te=4,_e=5,De=6,Be=7,Ee=8,Oe=10,Ue=1,Ne=2,Me=3;class Ge{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){return this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295,e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),l=t[n];t[n]=t[e],t[e]=l}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new Ge(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const $e=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},Re=(...e)=>{const t=t=>(...n)=>{const l=new Date,i=$e(l.getHours(),"00"),o=$e(l.getMinutes(),"00"),a=$e(l.getSeconds(),"00");console[t](`${i}:${o}:${a}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var Ve,je,Fe,Le,We={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",Ge.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Fe.FINAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:Ge.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}},coordByTileIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(let n in e){const l=[n,e[n]].map(encodeURIComponent);t.push(l.join("="))}return 0===t.length?"":`?${t.join("&")}`}};(je=Ve||(Ve={}))[je.Flat=0]="Flat",je[je.Out=1]="Out",je[je.In=-1]="In",(Le=Fe||(Fe={}))[Le.FINAL=0]="FINAL",Le[Le.ANY=1]="ANY";const qe={};function He(e,t){let n=0;for(let l of qe[e].players){if(We.decodePlayer(l).id===t)return n;n++}return-1}function Qe(e,t){const n=He(e,t);return-1===n?null:We.decodePlayer(qe[e].players[n])}function Ye(e,t,n){const l=He(e,t);-1===l?qe[e].players.push(We.encodePlayer(n)):qe[e].players[l]=We.encodePlayer(n)}function Ke(e,t){return-1!==He(e,t)}function Ze(e){return qe[e]?qe[e].players.map(We.decodePlayer):[]}function Je(e){return qe[e].puzzle.tiles.length}function Xe(e){return qe[e].scoreMode||0}function et(e){return tt(e)===Je(e)}function tt(e){let t=0;for(let n of qe[e].puzzle.tiles)-1===We.decodePiece(n).owner&&t++;return t}function nt(e,t,n){const l=Qe(e,t);if(null!==l){for(let e of Object.keys(n))l[e]=n[e];Ye(e,t,l)}}function lt(e,t){for(let n of Object.keys(t))qe[e].puzzle.data[n]=t[n]}function it(e,t,n){for(let l of Object.keys(n)){const i=We.decodePiece(qe[e].puzzle.tiles[t]);i[l]=n[l],qe[e].puzzle.tiles[t]=We.encodePiece(i)}}const ot=(e,t)=>We.decodePiece(qe[e].puzzle.tiles[t]),at=(e,t)=>ot(e,t).group,st=(e,t)=>{const n=qe[e].puzzle.info,l={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},i=function(e,t){const n=qe[e].puzzle.info,l=We.coordByTileIdx(n,t),i=l.x*n.tileSize,o=l.y*n.tileSize;return{x:i,y:o}}(e,t);return fe.pointAdd(l,i)},rt=(e,t)=>ot(e,t).pos,dt=e=>{const t=At(e),n=zt(e),l=Math.round(t/4),i=Math.round(n/4);return{x:0-l,y:0-i,w:t+2*l,h:n+2*i}},ct=(e,t)=>{const n=ht(e),l=ot(e,t);return{x:l.pos.x,y:l.pos.y,w:n,h:n}},ut=(e,t)=>ot(e,t).z,gt=(e,t)=>{for(let n of qe[e].puzzle.tiles){const e=We.decodePiece(n);if(e.owner===t)return e.idx}return-1},pt=e=>qe[e].puzzle.info.tileDrawSize,ht=e=>qe[e].puzzle.info.tileSize,mt=e=>qe[e].puzzle.data.maxGroup,yt=e=>qe[e].puzzle.data.maxZ;function ft(e,t){const n=qe[e].puzzle.info,l=We.coordByTileIdx(n,t);return[l.y>0?t-n.tilesX:-1,l.x0?t-1:-1]}const wt=(e,t,n)=>{for(let l of t)it(e,l,{z:n})},vt=(e,t,n)=>{const l=rt(e,t);it(e,t,{pos:fe.pointAdd(l,n)})},bt=(e,t,n)=>{const l=pt(e),i=dt(e),o=n;for(let a of t){const t=ot(e,a);t.pos.x+n.xi.x+i.w&&(o.x=Math.min(i.x+i.w-t.pos.x+l,o.x)),t.pos.y+n.yi.y+i.h&&(o.y=Math.min(i.y+i.h-t.pos.y+l,o.y))}for(let a of t)vt(e,a,o)},xt=(e,t,n)=>{for(let l of t)it(e,l,{owner:n})};function Ct(e,t){const n=qe[e].puzzle.tiles,l=We.decodePiece(n[t]),i=[];if(l.group)for(let o of n){const e=We.decodePiece(o);e.group===l.group&&i.push(e.idx)}else i.push(l.idx);return i}const kt=(e,t)=>{const n=Qe(e,t);return n?n.points:0},At=e=>qe[e].puzzle.info.table.width,zt=e=>qe[e].puzzle.info.table.height;var St={setGame:function(e,t){qe[e]=t},exists:function(e){return!!qe[e]||!1},playerExists:Ke,getActivePlayers:function(e,t){const n=t-30*_;return Ze(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return Ze(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Ke(e,t)?nt(e,t,{ts:n}):Ye(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:tt,getPieceCount:Je,getImageUrl:function(e){return qe[e].puzzle.info.imageUrl},setImageUrl:function(e,t){qe[e].puzzle.info.imageUrl=t},get:function(e){return qe[e]},getAllGames:function(){return Object.values(qe).sort(((e,t)=>et(e.id)===et(t.id)?t.puzzle.data.started-e.puzzle.data.started:et(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Qe(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Qe(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Qe(e,t);return n?n.name:null},getPlayerIndexById:He,getPlayerIdByIndex:function(e,t){return qe[e].players.length>t?We.decodePlayer(qe[e].players[t]).id:null},changePlayer:nt,setPlayer:Ye,setPiece:function(e,t,n){qe[e].puzzle.tiles[t]=We.encodePiece(n)},setPuzzleData:function(e,t){qe[e].puzzle.data=t},getTableWidth:At,getTableHeight:zt,getPuzzle:e=>qe[e].puzzle,getRng:e=>qe[e].rng.obj,getPuzzleWidth:e=>qe[e].puzzle.info.width,getPuzzleHeight:e=>qe[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return qe[e].puzzle.tiles.map(We.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=gt(e,t);return n<0?null:qe[e].puzzle.tiles[n]},getPieceDrawOffset:e=>qe[e].puzzle.info.tileDrawOffset,getPieceDrawSize:pt,getFinalPiecePos:st,getStartTs:e=>qe[e].puzzle.data.started,getFinishTs:e=>qe[e].puzzle.data.finished,handleInput:function(e,t,n,l){const i=qe[e].puzzle,o=function(e,t){return t in qe[e].evtInfos?qe[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),a=[],s=()=>{a.push([Ue,i.data])},r=t=>{a.push([Ne,We.encodePiece(ot(e,t))])},d=e=>{for(const t of e)r(t)},c=()=>{const n=Qe(e,t);n&&a.push([Me,We.encodePlayer(n)])},u=n[0];if(u===De){const i=n[1];nt(e,t,{bgcolor:i,ts:l}),c()}else if(u===Be){const i=n[1];nt(e,t,{color:i,ts:l}),c()}else if(u===Ee){const i=`${n[1]}`.substr(0,16);nt(e,t,{name:i,ts:l}),c()}else if(u===Se){const i={x:n[1],y:n[2]};nt(e,t,{d:1,ts:l}),c(),o._last_mouse_down=i;const a=((e,t)=>{let n=qe[e].puzzle.info,l=qe[e].puzzle.tiles,i=-1,o=-1;for(let a=0;ai)&&(i=e.z,o=a)}return o})(e,i);if(a>=0){let n=yt(e)+1;lt(e,{maxZ:n}),s();const l=Ct(e,a);wt(e,l,yt(e)),xt(e,l,t),d(l)}o._last_mouse=i}else if(u===Ie){const i=n[1],a=n[2],s={x:i,y:a};if(null===o._last_mouse_down)nt(e,t,{x:i,y:a,ts:l}),c();else{let n=gt(e,t);if(n>=0){nt(e,t,{x:i,y:a,ts:l}),c();const r=Ct(e,n);let u=fe.pointInBounds(s,dt(e))&&fe.pointInBounds(o._last_mouse_down,dt(e));for(let t of r){const n=ct(e,t);if(fe.pointInBounds(s,n)){u=!0;break}}if(u){const t=i-o._last_mouse_down.x,n=a-o._last_mouse_down.y;bt(e,r,{x:t,y:n}),d(r)}}else nt(e,t,{ts:l}),c();o._last_mouse_down=s}o._last_mouse=s}else if(u===Pe){const a={x:n[1],y:n[2]},u=0;o._last_mouse_down=null;let g=gt(e,t);if(g>=0){let n=Ct(e,g);xt(e,n,0),d(n);let o=rt(e,g),a=st(e,g);if(fe.pointDistance(a,o){for(let n of t)it(e,n,{owner:-1,z:1})})(e,n),d(n);let r=kt(e,t);0===Xe(e)?r+=n.length:1===Xe(e)&&(r+=1),nt(e,t,{d:u,ts:l,points:r}),c(),tt(e)===Je(e)&&(lt(e,{finished:l}),s())}else{const n=(e,t,n,l)=>{let i=qe[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const l=at(e,t),i=at(e,n);return!(!l||l!==i)})(e,t,n))return!1;const o=rt(e,t),a=fe.pointAdd(rt(e,n),{x:l[0]*i.tileSize,y:l[1]*i.tileSize});if(fe.pointDistance(o,a){const l=qe[e].puzzle.tiles,i=at(e,t),o=at(e,n);let a;const d=[];i&&d.push(i),o&&d.push(o),i?a=i:o?a=o:(lt(e,{maxGroup:mt(e)+1}),s(),a=mt(e));if(it(e,t,{group:a}),r(t),it(e,n,{group:a}),r(n),d.length>0)for(const s of l){const t=We.decodePiece(s);d.includes(t.group)&&(it(e,t.idx,{group:a}),r(t.idx))}})(e,t,n),i=Ct(e,t);const c=((e,t)=>{let n=0;for(let l of t){let t=ut(e,l);t>n&&(n=t)}return n})(e,i);return wt(e,i,c),d(i),!0}return!1};let i=!1;for(let t of Ct(e,g)){let l=ft(e,t);if(n(e,t,l[0],[0,1])||n(e,t,l[1],[-1,0])||n(e,t,l[2],[0,-1])||n(e,t,l[3],[1,0])){i=!0;break}}if(i&&1===Xe(e)){const n=kt(e,t)+1;nt(e,t,{d:u,ts:l,points:n}),c()}else nt(e,t,{d:u,ts:l}),c()}}else nt(e,t,{d:u,ts:l}),c();o._last_mouse=a}else if(u===Te){const i=n[1],a=n[2];nt(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else if(u===_e){const i=n[1],a=n[2];nt(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else nt(e,t,{ts:l}),c();return function(e,t,n){qe[e].evtInfos[t]=n}(e,t,o),a}},Pt=e({name:"new-game-dialog",components:{ResponsiveImage:W},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Fe.ANY}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const It={class:"area-image"},Tt={class:"has-image"},_t={class:"area-settings"},Dt=n("td",null,[n("label",null,"Pieces")],-1),Bt=n("td",null,[n("label",null,"Scoring: ")],-1),Et=s(" Any (Score when pieces are connected to each other or on final location)"),Ot=n("br",null,null,-1),Ut=s(" Final (Score when pieces are put to their final location)"),Nt={class:"area-buttons"};Pt.render=function(e,l,i,s,r,d){const c=o("responsive-image");return a(),t("div",{class:"overlay new-game-dialog",onClick:l[6]||(l[6]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[5]||(l[5]=u((()=>{}),["stop"]))},[n("div",It,[n("div",Tt,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",_t,[n("table",null,[n("tr",null,[Dt,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Bt,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[2]||(l[2]=t=>e.scoreMode=t),value:"1"},null,512),[[y,e.scoreMode]]),Et]),Ot,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[3]||(l[3]=t=>e.scoreMode=t),value:"0"},null,512),[[y,e.scoreMode]]),Ut])])])])]),n("div",Nt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:l[4]||(l[4]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var Mt=e({components:{ImageLibrary:L,NewImageDialog:Q,EditImageDialog:se,NewGameDialog:Pt},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},methods:{toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${We.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const Gt={class:"upload-image-teaser"},$t=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),Rt={key:0},Vt=s(" Tags: "),jt=s(" Sort by: "),Ft=n("option",{value:"date_desc"},"Newest first",-1),Lt=n("option",{value:"date_asc"},"Oldest first",-1),Wt=n("option",{value:"alpha_asc"},"A-Z",-1),qt=n("option",{value:"alpha_desc"},"Z-A",-1);Mt.render=function(e,l,s,u,p,h){const m=o("image-library"),y=o("new-image-dialog"),w=o("edit-image-dialog"),v=o("new-game-dialog");return a(),t("div",null,[n("div",Gt,[n("div",{class:"btn btn-big",onClick:l[1]||(l[1]=t=>e.dialog="new-image")},"Upload your image"),$t]),n("div",null,[e.tags.length>0?(a(),t("label",Rt,[Vt,(a(!0),t(d,null,c(e.tags,((n,l)=>(a(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:l,onClick:t=>e.toggleTag(n)},r(n.title),11,["onClick"])))),128))])):i("",!0),n("label",null,[jt,g(n("select",{"onUpdate:modelValue":l[2]||(l[2]=t=>e.filters.sort=t),onChange:l[3]||(l[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[Ft,Lt,Wt,qt],544),[[f,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(a(),t(y,{key:0,onBgclick:l[4]||(l[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["onPostToGalleryClick","onSetupGameClick"])):i("",!0),"edit-image"===e.dialog?(a(),t(w,{key:1,onBgclick:l[5]||(l[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["onSaveClick","image"])):i("",!0),e.image&&"new-game"===e.dialog?(a(),t(v,{key:2,onBgclick:l[6]||(l[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):i("",!0)])};var Ht=e({name:"scores",props:{activePlayers:{type:Array,required:!0},idlePlayers:{type:Array,required:!0}},computed:{actives(){return this.activePlayers.sort(((e,t)=>t.points-e.points)),this.activePlayers},idles(){return this.idlePlayers.sort(((e,t)=>t.points-e.points)),this.idlePlayers}}});const Qt={class:"scores"},Yt=n("div",null,"Scores",-1),Kt=n("td",null,"⚡",-1),Zt=n("td",null,"💤",-1);Ht.render=function(e,l,i,o,s,u){return a(),t("div",Qt,[Yt,n("table",null,[(a(!0),t(d,null,c(e.actives,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[Kt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(a(!0),t(d,null,c(e.idles,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[Zt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var Jt=e({name:"puzzle-status",props:{finished:{type:Boolean,required:!0},duration:{type:Number,required:!0},piecesDone:{type:Number,required:!0},piecesTotal:{type:Number,required:!0}},computed:{icon(){return this.finished?"🏁":"⏳"},durationStr(){return E(this.duration)}}});const Xt={class:"timer"};Jt.render=function(e,l,i,o,s,d){return a(),t("div",Xt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),w(e.$slots,"default")])};var en=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:Object},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const tn=n("td",null,[n("label",null,"Background: ")],-1),nn=n("td",null,[n("label",null,"Color: ")],-1),ln=n("td",null,[n("label",null,"Name: ")],-1);en.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("tr",null,[tn,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[1]||(l[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[nn,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[2]||(l[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[ln,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":l[3]||(l[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])])])])};var on=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const an={class:"preview"};on.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay",onClick:l[1]||(l[1]=t=>e.$emit("bgclick"))},[n("div",an,[n("div",{class:"img",style:e.previewStyle},null,4)])])};const sn=Re("Communication.js");let rn,dn=e=>{},cn=e=>{};let un=0;const gn=e=>{un!==e&&(un=e,cn(e))};function pn(e){if(2===un)try{rn.send(JSON.stringify(e))}catch(t){sn.info("unable to send message.. maybe because ws is invalid?")}}let hn,mn;var yn={connect:function(e,t,n){return hn=0,mn={},gn(3),new Promise((l=>{rn=new WebSocket(e,n+"|"+t),rn.onopen=e=>{gn(2),pn([xe])},rn.onmessage=e=>{const t=JSON.parse(e.data),i=t[0];if(i===ve){const e=t[1];l(e)}else{if(i!==we)throw`[ 2021-05-09 invalid connect msgType ${i} ]`;{const e=t[1],l=t[2];if(e===n&&mn[l])return void delete mn[l];dn(t)}}},rn.onerror=e=>{throw gn(1),"[ 2021-05-15 onerror ]"},rn.onclose=e=>{4e3===e.code||1001===e.code?gn(4):gn(1)}}))},requestReplayData:async function(e,t,n){const l={gameId:e,offset:t,size:n},i=await fetch(`/api/replay-data${We.asQueryArgs(l)}`);return await i.json()},disconnect:function(){rn&&rn.close(4e3),hn=0,mn={}},sendClientEvent:function(e){hn++,mn[hn]=e,pn([be,hn,mn[hn]])},onServerChange:function(e){dn=e},onConnectionStateChange:function(e){cn=e},CODE_CUSTOM_DISCONNECT:4e3,CONN_STATE_NOT_CONNECTED:0,CONN_STATE_DISCONNECTED:1,CONN_STATE_CLOSED:4,CONN_STATE_CONNECTED:2,CONN_STATE_CONNECTING:3},fn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===yn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===yn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const wn={key:0,class:"overlay connection-lost"},vn={key:0,class:"overlay-content"},bn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),xn={key:1,class:"overlay-content"},Cn=n("div",null,"Connecting...",-1);fn.render=function(e,l,o,s,r,d){return e.show?(a(),t("div",wn,[e.lostConnection?(a(),t("div",vn,[bn,n("span",{class:"btn",onClick:l[1]||(l[1]=t=>e.$emit("reconnect"))},"Reconnect")])):i("",!0),e.connecting?(a(),t("div",xn,[Cn])):i("",!0)])):i("",!0)};var kn=e({name:"help-overlay",emits:{bgclick:null}});const An=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),zn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),Sn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),Pn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),In=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Tn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),_n=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),Dn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Bn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),En=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1);kn.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[An,zn,Sn,Pn,In,Tn,_n,Dn,Bn,En])])};var On=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Un=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Mn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Gn(){let e=0,t=0,n=1;const l=(l,i)=>{e+=l/n,t+=i/n},i=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},o=l=>({x:l.x/n-e,y:l.y/n-t}),a=l=>({x:(l.x+e)*n,y:(l.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n});return{move:l,canZoom:e=>n!=i(e),zoom:(e,t)=>((e,t)=>{if(n==e)return!1;const i=1-n/e;return l(-t.x*i,-t.y*i),n=e,!0})(i(e),t),worldToViewport:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:a,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=o(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:o}}function $n(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Rn={createCanvas:$n,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const l=$n(t,n);return l.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(l)},colorizedCanvas:function(e,t,n){const l=$n(e.width,e.height),i=l.getContext("2d");return i.save(),i.drawImage(t,0,0),i.fillStyle=n,i.globalCompositeOperation="source-in",i.fillRect(0,0,t.width,t.height),i.restore(),i.save(),i.globalCompositeOperation="destination-over",i.drawImage(e,0,0),i.restore(),l}};const Vn=Re("Debug.js");let jn=0,Fn=0;var Ln=e=>{jn=performance.now(),Fn=e},Wn=e=>{const t=performance.now(),n=t-jn;n>Fn&&Vn.log(e+": "+n),jn=t};const qn=Re("PuzzleGraphics.js");function Hn(e,t){const n=We.coordByTileIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Qn={loadPuzzleBitmaps:async function(e){const t=await Rn.loadImageToBitmap(e.info.imageUrl),n=await Rn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){qn.log("start createPuzzleTileBitmaps");var l=n.tileSize,i=n.tileMarginWidth,o=n.tileDrawSize,a=l/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0];const r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,o={x:i,y:i},r=fe.pointAdd(o,{x:l,y:0}),c=fe.pointAdd(r,{x:0,y:l}),u=fe.pointSub(c,{x:l,y:0});if(n.moveTo(o.x,o.y),0!==e.top)for(let l=0;l=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Zn=t/2,Jn=t-Zn;const n=1/4*this.canvas.width/(t/2);Yn=-n,Kn=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Xn(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Xn(this.rng));const e=[];for(;this.explodedBombs.length>0;){const t=this.explodedBombs.shift();if(!t)break;t.update(),t.alive&&e.push(t)}this.explodedBombs=e;const t=[];for(;this.readyBombs.length>0;){const e=this.readyBombs.shift();if(!e)break;e.update(this.particles),e.hasExploded?this.explodedBombs.push(e):t.push(e)}this.readyBombs=t;const n=[];for(;this.particles.length>0;){const e=this.particles.shift();if(!e)break;e.update(),e.alive&&n.push(e)}this.particles=n}render(){this.ctx.beginPath(),this.ctx.fillStyle="rgba(0, 0, 0, 0.1)",this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height);for(let e=0;e{const t=e.color+" "+e.d;if(!h[t]){const n=e.d?a:s;if(e.color){const l=e.d?r:d;h[t]=await createImageBitmap(Rn.colorizedCanvas(n,l,e.color))}else h[t]=n}return h[t]},y=function(e,t){return t.width=window.innerWidth,t.height=window.innerHeight,e.appendChild(t),window.addEventListener("resize",(()=>{t.width=window.innerWidth,t.height=window.innerHeight,ol=!0})),t}(i,Rn.createCanvas()),f={final:!1,requesting:!0,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,dataOffset:0,dataSize:1e4};yn.onConnectionStateChange((e=>{o.setConnectionState(e)}));const w=async e=>{f.requesting=!0;const t=await yn.requestReplayData(e,f.dataOffset,f.dataSize);return f.dataOffset+=f.dataSize,f.requesting=!1,t};let v=()=>0;const b=async()=>{if("play"===l){const l=await yn.connect(n,e,t),i=We.decodeGame(l);St.setGame(i.id,i),v=()=>D()}else{if("replay"!==l)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await w(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=We.decodeGame(t.game);St.setGame(n.id,n),f.requesting=!1,f.log=t.log,f.lastRealTs=D(),f.gameStartTs=parseInt(f.log[0][4],10),f.lastGameTs=f.gameStartTs,v=()=>f.lastGameTs}}ol=!0};await b();const x=St.getPieceDrawOffset(e),C=St.getPieceDrawSize(e),k=St.getPuzzleWidth(e),A=St.getPuzzleHeight(e),z=St.getTableWidth(e),S=St.getTableHeight(e),P={x:(z-k)/2,y:(S-A)/2},I={w:k,h:A},T={w:C,h:C},_=await Qn.loadPuzzleBitmaps(St.getPuzzle(e)),B=new tl(y,St.getRng(e));B.init();const E=y.getContext("2d");y.classList.add("loaded");const O=Gn();O.move(-(z-y.width)/2,-(S-y.height)/2);const U=function(e,t,n){let l=[],i=!0,o=!1,a=!1,s=!1,r=!1,d=!1,c=!1,u=!1;const g=(e,t)=>{const l=n.viewportToWorld({x:e,y:t});return[l.x,l.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{i&&("Shift"===t.key?u=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?s=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?r=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?o=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?a=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};e.addEventListener("mousedown",(e=>{0===e.button&&y([Se,...p(e)])})),e.addEventListener("mouseup",(e=>{0===e.button&&y([Pe,...p(e)])})),e.addEventListener("mousemove",(e=>{y([Ie,...p(e)])})),e.addEventListener("wheel",(e=>{if(n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Te:_e;y([t,...p(e)])}})),t.addEventListener("keydown",(e=>m(!0,e))),t.addEventListener("keyup",(e=>m(!1,e))),t.addEventListener("keypress",(e=>{i&&(" "===e.key&&y([Oe]),"F"!==e.key&&"f"!==e.key||(ll=!ll,ol=!0),"G"!==e.key&&"g"!==e.key||(il=!il,ol=!0))}));const y=e=>{l.push(e)};return{addEvent:y,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=u?20:10,t=(o?e:0)-(a?e:0),l=(s?e:0)-(r?e:0);0===t&&0===l||y([ze,t,l]),d&&c||(d?n.canZoom("in")&&y([Te,...h()]):c&&n.canZoom("out")&&y([_e,...h()]))},setHotkeys:e=>{i=e}}}(y,window,O),N=St.getImageUrl(e),M=()=>{const t=St.getStartTs(e),n=St.getFinishTs(e),l=v();o.setFinished(!!n),o.setDuration((n||l)-t)};M(),o.setPiecesDone(St.getFinishedPiecesCount(e)),o.setPiecesTotal(St.getPieceCount(e));const G=v();o.setActivePlayers(St.getActivePlayers(e,G)),o.setIdlePlayers(St.getIdlePlayers(e,G));const $=!!St.getFinishTs(e);let R=$;const V=()=>R&&!$,j=()=>St.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",F=()=>St.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let L="",W="",q=!1;const H=e=>{q=e;const[t,n]=e?[L,"grab"]:[W,"default"];y.style.cursor=`url('${t}') ${u} ${p}, ${n}`},Q=e=>{L=Rn.colorizedCanvas(a,r,e).toDataURL(),W=Rn.colorizedCanvas(s,d,e).toDataURL(),H(q)};Q(F());const Y=()=>{o.setReplaySpeed&&o.setReplaySpeed(f.speeds[f.speedIdx]),o.setReplayPaused&&o.setReplayPaused(f.paused)};if("play"===l?setInterval(M,1e3):"replay"===l&&Y(),"play"===l)yn.onServerChange((n=>{n[0],n[1],n[2];const l=n[3];for(const[i,o]of l)switch(i){case Me:{const n=We.decodePlayer(o);n.id!==t&&(St.setPlayer(e,n.id,n),ol=!0)}break;case Ne:{const t=We.decodePiece(o);St.setPiece(e,t.idx,t),ol=!0}break;case Ue:St.setPuzzleData(e,o),ol=!0}R=!!St.getFinishTs(e)}));else if("replay"===l){let t=setInterval((()=>{const n=D();if(f.requesting)return void(f.lastRealTs=n);if(f.logPointer+1>=f.log.length)return f.lastRealTs=n,void(async e=>{const t=await w(e);f.log=f.log.slice(f.logPointer),f.logPointer=0,f.log.push(...t.log),t.log.length=f.log.length){f.final&&clearInterval(t);break}const l=f.log[n],o=f.gameStartTs+l[l.length-1];if(o>i)break;const a=l.slice();if(a[0]===Ce){const t=a[1];St.addPlayer(e,t,o),ol=!0}else if(a[0]===ke){const t=St.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";St.addPlayer(e,t,o),ol=!0}else if(a[0]===Ae){const t=St.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=a[2];St.handleInput(e,t,n,o),ol=!0}f.logPointer=n}f.lastRealTs=n,f.lastGameTs=i,M()}),50)}let K=null;return(e=>{const t=e.fps||60,n=e.slow||1,l=e.update,i=e.render,o=window.requestAnimationFrame,a=1/t,s=n*a;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>s;)d-=s,l(a);i(d/n),c=r,o(u)};o(u)})({update:()=>{U.createKeyEvents();for(const n of U.consumeAll())if("play"===l){const l=n[0];if(l===ze){const e=n[1],t=n[2];ol=!0,O.move(e,t)}else if(l===Ie){if(K&&!St.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=O.worldToViewport(e),l=Math.round(t.x-K.x),i=Math.round(t.y-K.y);ol=!0,O.move(l,i),K=t}}else if(l===Be)Q(n[1]);else if(l===Se){const e={x:n[1],y:n[2]};K=O.worldToViewport(e),H(!0)}else if(l===Pe)K=null,H(!1);else if(l===Te){const e={x:n[1],y:n[2]};ol=!0,O.zoom("in",O.worldToViewport(e))}else if(l===_e){const e={x:n[1],y:n[2]};ol=!0,O.zoom("out",O.worldToViewport(e))}else l===Oe&&o.togglePreview();const i=v();St.handleInput(e,t,n,i).length>0&&(ol=!0),yn.sendClientEvent(n)}else if("replay"===l){const e=n[0];if(e===ze){const e=n[1],t=n[2];ol=!0,O.move(e,t)}else if(e===Ie){if(K){const e={x:n[1],y:n[2]},t=O.worldToViewport(e),l=Math.round(t.x-K.x),i=Math.round(t.y-K.y);ol=!0,O.move(l,i),K=t}}else if(e===Se){const e={x:n[1],y:n[2]};K=O.worldToViewport(e)}else if(e===Pe)K=null;else if(e===Te){const e={x:n[1],y:n[2]};ol=!0,O.zoom("in",O.worldToViewport(e))}else if(e===_e){const e={x:n[1],y:n[2]};ol=!0,O.zoom("out",O.worldToViewport(e))}else e===Oe&&o.togglePreview()}R=!!St.getFinishTs(e),V()&&(B.update(),ol=!0)},render:async()=>{if(!ol)return;const n=v();let i,a,s;window.DEBUG&&Ln(0),E.fillStyle=j(),E.fillRect(0,0,y.width,y.height),window.DEBUG&&Wn("clear done"),i=O.worldToViewportRaw(P),a=O.worldDimToViewportRaw(I),E.fillStyle="rgba(255, 255, 255, .3)",E.fillRect(i.x,i.y,a.w,a.h),window.DEBUG&&Wn("board done");const r=St.getPiecesSortedByZIndex(e);window.DEBUG&&Wn("get tiles done"),a=O.worldDimToViewportRaw(T);for(const e of r)(-1===e.owner?ll:il)&&(s=_[e.idx],i=O.worldToViewportRaw({x:x+e.pos.x,y:x+e.pos.y}),E.drawImage(s,0,0,s.width,s.height,i.x,i.y,a.w,a.h));window.DEBUG&&Wn("tiles done");const d=[];for(const o of St.getActivePlayers(e,n))c=o,("replay"===l||c.id!==t)&&(s=await m(o),i=O.worldToViewport(o),E.drawImage(s,i.x-u,i.y-p),d.push([`${o.name} (${o.points})`,i.x,i.y+g]));var c;E.fillStyle="white",E.textAlign="center";for(const[e,t,l]of d)E.fillText(e,t,l);window.DEBUG&&Wn("players done"),o.setActivePlayers(St.getActivePlayers(e,n)),o.setIdlePlayers(St.getIdlePlayers(e,n)),o.setPiecesDone(St.getFinishedPiecesCount(e)),window.DEBUG&&Wn("HUD done"),V()&&B.render(),ol=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([De,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([Be,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Ee,e])},replayOnSpeedUp:()=>{f.speedIdx+1{f.speedIdx>=1&&(f.speedIdx--,Y())},replayOnPauseToggle:()=>{f.paused=!f.paused,Y()},previewImageUrl:N,player:{background:j(),color:F(),name:St.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon"},disconnect:yn.disconnect,connect:b}}var sl=e({name:"game",components:{PuzzleStatus:Jt,Scores:Ht,SettingsOverlay:en,PreviewOverlay:on,ConnectionOverlay:fn,HelpOverlay:kn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},disconnect:()=>{},connect:()=>{}}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await al(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},setConnectionState:e=>{this.connectionState=e},togglePreview:()=>{this.toggle("preview",!1)}}))},unmounted(){this.g.disconnect()},methods:{reconnect(){this.g.connect()},toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}}});const rl={id:"game"},dl={class:"menu"},cl={class:"tabs"},ul=s("🧩 Puzzles");sl.render=function(e,i,s,r,d,c){const u=o("settings-overlay"),p=o("preview-overlay"),h=o("help-overlay"),m=o("connection-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",rl,[g(n(u,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(p,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(h,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",dl,[n("div",cl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[ul])),_:1}),n("div",{class:"opener",onClick:i[5]||(i[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var gl=e({name:"replay",components:{PuzzleStatus:Jt,Scores:Ht,SettingsOverlay:en,PreviewOverlay:on,HelpOverlay:kn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},disconnect:()=>{}},replay:{speed:1,paused:!1}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await al(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"replay",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},setReplaySpeed:e=>{this.replay.speed=e},setReplayPaused:e=>{this.replay.paused=e}}))},unmounted(){this.g.disconnect()},methods:{toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}},computed:{replayText(){return"Replay-Speed: "+this.replay.speed+"x"+(this.replay.paused?" Paused":"")}}});const pl={id:"replay"},hl={class:"menu"},ml={class:"tabs"},yl=s("🧩 Puzzles");gl.render=function(e,i,s,d,c,u){const p=o("settings-overlay"),h=o("preview-overlay"),m=o("help-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",pl,[g(n(p,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(m,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:l((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:i[5]||(i[5]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",hl,[n("div",ml,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[yl])),_:1}),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=b({history:x(),routes:[{name:"index",path:"/",component:R},{name:"new-game",path:"/new-game",component:Mt},{name:"game",path:"/g/:id",component:sl},{name:"replay",path:"/replay/:id",component:gl}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const l=C(k);l.config.globalProperties.$config=t,l.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=We.uniqId(),localStorage.setItem("ID",e)),e}(),l.use(n),l.mount("#app")})(); diff --git a/build/public/assets/index.8f906b9e.js b/build/public/assets/index.8f906b9e.js deleted file mode 100644 index df0bb1e..0000000 --- a/build/public/assets/index.8f906b9e.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as n,w as l,b as i,r as o,o as a,e as s,t as r,F as d,f as c,g as u,h as g,v as p,i as h,j as m,k as y,l as f,m as w,n as v,p as b,q as x,s as C}from"./vendor.b622ee49.js";var k=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const A={id:"app"},T={key:0,class:"nav"},z=s("Index"),S=s("New game");k.render=function(e,s,r,d,c,u){const g=o("router-link"),p=o("router-view");return a(),t("div",A,[e.showNav?(a(),t("ul",T,[n("li",null,[n(g,{class:"btn",to:{name:"index"}},{default:l((()=>[z])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:l((()=>[S])),_:1})])])):i("",!0),n(p)])};const I=864e5,P=e=>{const t=Math.floor(e/I);e%=I;const n=Math.floor(e/36e5);e%=36e5;const l=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${l}m ${Math.floor(e/1e3)}s`};var _=1e3,D=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>P(t-e),E=P,O=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",l=e,i=t||D();return`${n} ${B(l,i)}`}}});const U={class:"game-info-text"},M=n("br",null,null,-1),N=n("br",null,null,-1),G=n("br",null,null,-1),$=s(" ↪️ Watch replay ");O.render=function(e,d,c,u,g,p){const h=o("router-link");return a(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:l((()=>[n("span",U,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),M,s(" 👥 "+r(e.game.players),1),N,s(" "+r(e.time(e.game.started,e.game.finished)),1),G])])),_:1},8,["to"]),e.game.hasReplay?(a(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:l((()=>[$])),_:1},8,["to"])):i("",!0)],4)};var R=e({components:{GameTeaser:O},data:()=>({gamesRunning:[],gamesFinished:[]}),async created(){const e=await fetch("/api/index-data"),t=await e.json();this.gamesRunning=t.gamesRunning,this.gamesFinished=t.gamesFinished}});const V=n("h1",null,"Running games",-1),j=n("h1",null,"Finished games",-1);R.render=function(e,l,i,s,r,u){const g=o("game-teaser");return a(),t("div",null,[V,(a(!0),t(d,null,c(e.gamesRunning,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128)),j,(a(!0),t(d,null,c(e.gamesFinished,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128))])};var F=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});F.render=function(e,l,i,o,s,r){return a(),t("div",{class:"imageteaser",style:e.style,onClick:l[2]||(l[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:l[1]||(l[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var L=e({name:"image-library",components:{ImageTeaser:F},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});L.render=function(e,n,l,i,s,r){const u=o("image-teaser");return a(),t("div",null,[(a(!0),t(d,null,c(e.images,((n,l)=>(a(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:l},null,8,["image","onClick","onEditClick"])))),128))])};const W={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};W.render=function(e,n,l,i,o,s){return a(),t("div",{style:s.style,title:l.title},null,12,["title"])};var q=e({name:"tags-input",props:{modelValue:{type:Array,required:!0}},emits:{"update:modelValue":null},data:()=>({input:"",values:[]}),created(){this.values=this.modelValue},methods:{onKeyUp(e){if(","===e.key)return this.add(),e.stopPropagation(),!1},add(){const e=this.input.replace(/,/g,"").trim();e&&(this.values.includes(e)||this.values.push(e),this.input="",this.$emit("update:modelValue",this.values))},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const H=m()(((e,l,i,o,s,u)=>(a(),t("div",null,[g(n("input",{class:"input",type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.input=t),placeholder:"Plants, People",onKeydown:l[2]||(l[2]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:l[3]||(l[3]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[p,e.input]]),(a(!0),t(d,null,c(e.values,((n,l)=>(a(),t("span",{key:l,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));q.render=H,q.__scopeId="data-v-771460ae";var Q=e({name:"new-image-dialog",components:{ResponsiveImage:W,TagsInput:q},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const l=new FileReader;l.readAsDataURL(n),l.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const Y={key:0,class:"has-image"},K={key:1},Z={class:"upload"},J=n("span",{class:"btn"},"Upload File",-1),X={class:"area-settings"},ee=n("td",null,[n("label",null,"Title")],-1),te=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ne=n("td",null,[n("label",null,"Tags")],-1),le={class:"area-buttons"},ie=s("🧩 Post to gallery "),oe=n("br",null,null,-1),ae=s(" + set up game");Q.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay new-image-dialog",onClick:l[8]||(l[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[7]||(l[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(a(),t("div",Y,[n("span",{class:"remove btn",onClick:l[1]||(l[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(a(),t("div",K,[n("label",Z,[n("input",{type:"file",style:{display:"none"},onChange:l[2]||(l[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),J])]))],2),n("div",X,[n("table",null,[n("tr",null,[ee,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[3]||(l[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),te,n("tr",null,[ne,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[4]||(l[4]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",le,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:l[5]||(l[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:l[6]||(l[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[ie,oe,ae],8,["disabled"])])])])};var se=e({name:"edit-image-dialog",components:{ResponsiveImage:W,TagsInput:q},props:{image:{type:Object,required:!0}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const re={class:"area-image"},de={class:"has-image"},ce={class:"area-settings"},ue=n("td",null,[n("label",null,"Title")],-1),ge=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),pe=n("td",null,[n("label",null,"Tags")],-1),he={class:"area-buttons"};function me(e,t){const n=e.x-t.x,l=e.y-t.y;return Math.sqrt(n*n+l*l)}function ye(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}se.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay edit-image-dialog",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("div",re,[n("div",de,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ce,[n("table",null,[n("tr",null,[ue,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ge,n("tr",null,[pe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[2]||(l[2]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",he,[n("button",{class:"btn",onClick:l[3]||(l[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var fe={pointSub:function(e,t){return{x:e.x-t.x,y:e.y-t.y}},pointAdd:function(e,t){return{x:e.x+t.x,y:e.y+t.y}},pointDistance:me,pointInBounds:function(e,t){return e.x>=t.x&&e.x<=t.x+t.w&&e.y>=t.y&&e.y<=t.y+t.h},rectCenter:ye,rectMoved:function(e,t,n){return{x:e.x+t,y:e.y+n,w:e.w,h:e.h}},rectCenterDistance:function(e,t){return me(ye(e),ye(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};var we=1,ve=4,be=2,xe=3,Ce=2,ke=4,Ae=3,Te=9,ze=1,Se=2,Ie=3,Pe=4,_e=5,De=6,Be=7,Ee=8,Oe=10,Ue=1,Me=2,Ne=3;class Ge{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){return this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295,e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),l=t[n];t[n]=t[e],t[e]=l}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new Ge(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const $e=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},Re=(...e)=>{const t=t=>(...n)=>{const l=new Date,i=$e(l.getHours(),"00"),o=$e(l.getMinutes(),"00"),a=$e(l.getSeconds(),"00");console[t](`${i}:${o}:${a}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var Ve,je,Fe,Le,We={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodeTile:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodeTile:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return Array.isArray(e)?e:[e.id,e.rng.type,Ge.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode]},decodeGame:function(e){return Array.isArray(e)?{id:e[0],rng:{type:e[1],obj:Ge.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}:e},coordByTileIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(let n in e){const l=[n,e[n]].map(encodeURIComponent);t.push(l.join("="))}return 0===t.length?"":`?${t.join("&")}`}};(je=Ve||(Ve={}))[je.Flat=0]="Flat",je[je.Out=1]="Out",je[je.In=-1]="In",(Le=Fe||(Fe={}))[Le.FINAL=0]="FINAL",Le[Le.ANY=1]="ANY";const qe={};function He(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}function Qe(e,t){let n=0;for(let l of qe[e].players){if(We.decodePlayer(l).id===t)return n;n++}return-1}function Ye(e,t){const n=Qe(e,t);return-1===n?null:We.decodePlayer(qe[e].players[n])}function Ke(e,t,n){const l=Qe(e,t);-1===l?qe[e].players.push(We.encodePlayer(n)):qe[e].players[l]=We.encodePlayer(n)}function Ze(e,t){return-1!==Qe(e,t)}function Je(e){return qe[e]?qe[e].players.map(We.decodePlayer):[]}function Xe(e){return qe[e].puzzle.tiles.length}function et(e){return qe[e].scoreMode||0}function tt(e){return nt(e)===Xe(e)}function nt(e){let t=0;for(let n of qe[e].puzzle.tiles)-1===We.decodeTile(n).owner&&t++;return t}function lt(e,t,n){const l=Ye(e,t);if(null!==l){for(let e of Object.keys(n))l[e]=n[e];Ke(e,t,l)}}function it(e,t){for(let n of Object.keys(t))qe[e].puzzle.data[n]=t[n]}function ot(e,t,n){for(let l of Object.keys(n)){const i=We.decodeTile(qe[e].puzzle.tiles[t]);i[l]=n[l],qe[e].puzzle.tiles[t]=We.encodeTile(i)}}const at=(e,t)=>We.decodeTile(qe[e].puzzle.tiles[t]),st=(e,t)=>at(e,t).group,rt=(e,t)=>{const n=qe[e].puzzle.info,l={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},i=function(e,t){const n=qe[e].puzzle.info,l=We.coordByTileIdx(n,t),i=l.x*n.tileSize,o=l.y*n.tileSize;return{x:i,y:o}}(e,t);return fe.pointAdd(l,i)},dt=(e,t)=>at(e,t).pos,ct=e=>{const t=Tt(e),n=zt(e),l=Math.round(t/4),i=Math.round(n/4);return{x:0-l,y:0-i,w:t+2*l,h:n+2*i}},ut=(e,t)=>{const n=mt(e),l=at(e,t);return{x:l.pos.x,y:l.pos.y,w:n,h:n}},gt=(e,t)=>at(e,t).z,pt=(e,t)=>{for(let n of qe[e].puzzle.tiles){const e=We.decodeTile(n);if(e.owner===t)return e.idx}return-1},ht=e=>qe[e].puzzle.info.tileDrawSize,mt=e=>qe[e].puzzle.info.tileSize,yt=e=>qe[e].puzzle.data.maxGroup,ft=e=>qe[e].puzzle.data.maxZ;function wt(e,t){const n=qe[e].puzzle.info,l=We.coordByTileIdx(n,t);return[l.y>0?t-n.tilesX:-1,l.x0?t-1:-1]}const vt=(e,t,n)=>{for(let l of t)ot(e,l,{z:n})},bt=(e,t,n)=>{const l=dt(e,t);ot(e,t,{pos:fe.pointAdd(l,n)})},xt=(e,t,n)=>{const l=ht(e),i=ct(e),o=n;for(let a of t){const t=at(e,a);t.pos.x+n.xi.x+i.w&&(o.x=Math.min(i.x+i.w-t.pos.x+l,o.x)),t.pos.y+n.yi.y+i.h&&(o.y=Math.min(i.y+i.h-t.pos.y+l,o.y))}for(let a of t)bt(e,a,o)},Ct=(e,t,n)=>{for(let l of t)ot(e,l,{owner:n})};function kt(e,t){const n=qe[e].puzzle.tiles,l=We.decodeTile(n[t]),i=[];if(l.group)for(let o of n){const e=We.decodeTile(o);e.group===l.group&&i.push(e.idx)}else i.push(l.idx);return i}const At=(e,t)=>{const n=Ye(e,t);return n?n.points:0},Tt=e=>qe[e].puzzle.info.table.width,zt=e=>qe[e].puzzle.info.table.height;var St={__createPlayerObject:He,setGame:function(e,t){qe[e]=t},exists:function(e){return!!qe[e]||!1},playerExists:Ze,getActivePlayers:function(e,t){const n=t-30*_;return Je(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return Je(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Ze(e,t)?lt(e,t,{ts:n}):Ke(e,t,He(t,n))},getFinishedTileCount:nt,getTileCount:Xe,getImageUrl:function(e){return qe[e].puzzle.info.imageUrl},setImageUrl:function(e,t){qe[e].puzzle.info.imageUrl=t},get:function(e){return qe[e]},getAllGames:function(){return Object.values(qe).sort(((e,t)=>tt(e.id)===tt(t.id)?t.puzzle.data.started-e.puzzle.data.started:tt(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Ye(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Ye(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Ye(e,t);return n?n.name:null},getPlayerIndexById:Qe,getPlayerIdByIndex:function(e,t){return qe[e].players.length>t?We.decodePlayer(qe[e].players[t]).id:null},changePlayer:lt,setPlayer:Ke,setTile:function(e,t,n){qe[e].puzzle.tiles[t]=We.encodeTile(n)},setPuzzleData:function(e,t){qe[e].puzzle.data=t},getTableWidth:Tt,getTableHeight:zt,getPuzzle:e=>qe[e].puzzle,getRng:e=>qe[e].rng.obj,getPuzzleWidth:e=>qe[e].puzzle.info.width,getPuzzleHeight:e=>qe[e].puzzle.info.height,getTilesSortedByZIndex:function(e){return qe[e].puzzle.tiles.map(We.decodeTile).sort(((e,t)=>e.z-t.z))},getFirstOwnedTile:(e,t)=>{const n=pt(e,t);return n<0?null:qe[e].puzzle.tiles[n]},getTileDrawOffset:e=>qe[e].puzzle.info.tileDrawOffset,getTileDrawSize:ht,getFinalTilePos:rt,getStartTs:e=>qe[e].puzzle.data.started,getFinishTs:e=>qe[e].puzzle.data.finished,handleInput:function(e,t,n,l){const i=qe[e].puzzle,o=function(e,t){return t in qe[e].evtInfos?qe[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),a=[],s=()=>{a.push([Ue,i.data])},r=t=>{a.push([Me,We.encodeTile(at(e,t))])},d=e=>{for(const t of e)r(t)},c=()=>{const n=Ye(e,t);n&&a.push([Ne,We.encodePlayer(n)])},u=n[0];if(u===De){const i=n[1];lt(e,t,{bgcolor:i,ts:l}),c()}else if(u===Be){const i=n[1];lt(e,t,{color:i,ts:l}),c()}else if(u===Ee){const i=`${n[1]}`.substr(0,16);lt(e,t,{name:i,ts:l}),c()}else if(u===ze){const i={x:n[1],y:n[2]};lt(e,t,{d:1,ts:l}),c(),o._last_mouse_down=i;const a=((e,t)=>{let n=qe[e].puzzle.info,l=qe[e].puzzle.tiles,i=-1,o=-1;for(let a=0;ai)&&(i=e.z,o=a)}return o})(e,i);if(a>=0){let n=ft(e)+1;it(e,{maxZ:n}),s();const l=kt(e,a);vt(e,l,ft(e)),Ct(e,l,t),d(l)}o._last_mouse=i}else if(u===Ie){const i=n[1],a=n[2],s={x:i,y:a};if(null===o._last_mouse_down)lt(e,t,{x:i,y:a,ts:l}),c();else{let n=pt(e,t);if(n>=0){lt(e,t,{x:i,y:a,ts:l}),c();const r=kt(e,n);let u=fe.pointInBounds(s,ct(e))&&fe.pointInBounds(o._last_mouse_down,ct(e));for(let t of r){const n=ut(e,t);if(fe.pointInBounds(s,n)){u=!0;break}}if(u){const t=i-o._last_mouse_down.x,n=a-o._last_mouse_down.y;xt(e,r,{x:t,y:n}),d(r)}}else lt(e,t,{ts:l}),c();o._last_mouse_down=s}o._last_mouse=s}else if(u===Se){const a={x:n[1],y:n[2]},u=0;o._last_mouse_down=null;let g=pt(e,t);if(g>=0){let n=kt(e,g);Ct(e,n,0),d(n);let o=dt(e,g),a=rt(e,g);if(fe.pointDistance(a,o){for(let n of t)ot(e,n,{owner:-1,z:1})})(e,n),d(n);let r=At(e,t);0===et(e)?r+=n.length:1===et(e)&&(r+=1),lt(e,t,{d:u,ts:l,points:r}),c(),nt(e)===Xe(e)&&(it(e,{finished:l}),s())}else{const n=(e,t,n,l)=>{let i=qe[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const l=st(e,t),i=st(e,n);return!(!l||l!==i)})(e,t,n))return!1;const o=dt(e,t),a=fe.pointAdd(dt(e,n),{x:l[0]*i.tileSize,y:l[1]*i.tileSize});if(fe.pointDistance(o,a){const l=qe[e].puzzle.tiles,i=st(e,t),o=st(e,n);let a;const d=[];i&&d.push(i),o&&d.push(o),i?a=i:o?a=o:(it(e,{maxGroup:yt(e)+1}),s(),a=yt(e));if(ot(e,t,{group:a}),r(t),ot(e,n,{group:a}),r(n),d.length>0)for(const s of l){const t=We.decodeTile(s);d.includes(t.group)&&(ot(e,t.idx,{group:a}),r(t.idx))}})(e,t,n),i=kt(e,t);const c=((e,t)=>{let n=0;for(let l of t){let t=gt(e,l);t>n&&(n=t)}return n})(e,i);return vt(e,i,c),d(i),!0}return!1};let i=!1;for(let t of kt(e,g)){let l=wt(e,t);if(n(e,t,l[0],[0,1])||n(e,t,l[1],[-1,0])||n(e,t,l[2],[0,-1])||n(e,t,l[3],[1,0])){i=!0;break}}if(i&&1===et(e)){const n=At(e,t)+1;lt(e,t,{d:u,ts:l,points:n}),c()}else lt(e,t,{d:u,ts:l}),c()}}else lt(e,t,{d:u,ts:l}),c();o._last_mouse=a}else if(u===Pe){const i=n[1],a=n[2];lt(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else if(u===_e){const i=n[1],a=n[2];lt(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else lt(e,t,{ts:l}),c();return function(e,t,n){qe[e].evtInfos[t]=n}(e,t,o),a}},It=e({name:"new-game-dialog",components:{ResponsiveImage:W},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Fe.ANY}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Pt={class:"area-image"},_t={class:"has-image"},Dt={class:"area-settings"},Bt=n("td",null,[n("label",null,"Pieces")],-1),Et=n("td",null,[n("label",null,"Scoring: ")],-1),Ot=s(" Any (Score when pieces are connected to each other or on final location)"),Ut=n("br",null,null,-1),Mt=s(" Final (Score when pieces are put to their final location)"),Nt={class:"area-buttons"};It.render=function(e,l,i,s,r,d){const c=o("responsive-image");return a(),t("div",{class:"overlay new-game-dialog",onClick:l[6]||(l[6]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[5]||(l[5]=u((()=>{}),["stop"]))},[n("div",Pt,[n("div",_t,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Dt,[n("table",null,[n("tr",null,[Bt,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Et,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[2]||(l[2]=t=>e.scoreMode=t),value:"1"},null,512),[[y,e.scoreMode]]),Ot]),Ut,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[3]||(l[3]=t=>e.scoreMode=t),value:"0"},null,512),[[y,e.scoreMode]]),Mt])])])])]),n("div",Nt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:l[4]||(l[4]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var Gt=e({components:{ImageLibrary:L,NewImageDialog:Q,EditImageDialog:se,NewGameDialog:It},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},methods:{toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${We.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const $t={class:"upload-image-teaser"},Rt=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),Vt={key:0},jt=s(" Tags: "),Ft=s(" Sort by: "),Lt=n("option",{value:"date_desc"},"Newest first",-1),Wt=n("option",{value:"date_asc"},"Oldest first",-1),qt=n("option",{value:"alpha_asc"},"A-Z",-1),Ht=n("option",{value:"alpha_desc"},"Z-A",-1);Gt.render=function(e,l,s,u,p,h){const m=o("image-library"),y=o("new-image-dialog"),w=o("edit-image-dialog"),v=o("new-game-dialog");return a(),t("div",null,[n("div",$t,[n("div",{class:"btn btn-big",onClick:l[1]||(l[1]=t=>e.dialog="new-image")},"Upload your image"),Rt]),n("div",null,[e.tags.length>0?(a(),t("label",Vt,[jt,(a(!0),t(d,null,c(e.tags,((n,l)=>(a(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:l,onClick:t=>e.toggleTag(n)},r(n.title),11,["onClick"])))),128))])):i("",!0),n("label",null,[Ft,g(n("select",{"onUpdate:modelValue":l[2]||(l[2]=t=>e.filters.sort=t),onChange:l[3]||(l[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[Lt,Wt,qt,Ht],544),[[f,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(a(),t(y,{key:0,onBgclick:l[4]||(l[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["onPostToGalleryClick","onSetupGameClick"])):i("",!0),"edit-image"===e.dialog?(a(),t(w,{key:1,onBgclick:l[5]||(l[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["onSaveClick","image"])):i("",!0),e.image&&"new-game"===e.dialog?(a(),t(v,{key:2,onBgclick:l[6]||(l[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):i("",!0)])};var Qt=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"},Kt=n("div",null,"Scores",-1),Zt=n("td",null,"⚡",-1),Jt=n("td",null,"💤",-1);Qt.render=function(e,l,i,o,s,u){return a(),t("div",Yt,[Kt,n("table",null,[(a(!0),t(d,null,c(e.actives,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[Zt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(a(!0),t(d,null,c(e.idles,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[Jt,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 E(this.duration)}}});const en={class:"timer"};Xt.render=function(e,l,i,o,s,d){return a(),t("div",en,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),w(e.$slots,"default")])};var tn=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 nn=n("td",null,[n("label",null,"Background: ")],-1),ln=n("td",null,[n("label",null,"Color: ")],-1),on=n("td",null,[n("label",null,"Name: ")],-1);tn.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("tr",null,[nn,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[1]||(l[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[ln,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[2]||(l[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[on,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":l[3]||(l[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])])])])};var an=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const sn={class:"preview"};an.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay",onClick:l[1]||(l[1]=t=>e.$emit("bgclick"))},[n("div",sn,[n("div",{class:"img",style:e.previewStyle},null,4)])])};const rn=Re("Communication.js");let dn,cn=e=>{},un=e=>{};let gn=0;const pn=e=>{gn!==e&&(gn=e,un(e))};function hn(e){if(2===gn)try{dn.send(JSON.stringify(e))}catch(t){rn.info("unable to send message.. maybe because ws is invalid?")}}let mn,yn;var fn={connect:function(e,t,n){return mn=0,yn={},pn(3),new Promise((l=>{dn=new WebSocket(e,n+"|"+t),dn.onopen=e=>{pn(2),hn([xe])},dn.onmessage=e=>{const t=JSON.parse(e.data),i=t[0];if(i===ve){const e=t[1];l(e)}else{if(i!==we)throw`[ 2021-05-09 invalid connect msgType ${i} ]`;{const e=t[1],l=t[2];if(e===n&&yn[l])return void delete yn[l];cn(t)}}},dn.onerror=e=>{throw pn(1),"[ 2021-05-15 onerror ]"},dn.onclose=e=>{4e3===e.code||1001===e.code?pn(4):pn(1)}}))},requestReplayData:async function(e,t,n){const l=await fetch(`/api/replay-data?gameId=${e}&offset=${t}&size=${n}`);return await l.json()},disconnect:function(){dn&&dn.close(4e3),mn=0,yn={}},sendClientEvent:function(e){mn++,yn[mn]=e,hn([be,mn,yn[mn]])},onServerChange:function(e){cn=e},onConnectionStateChange:function(e){un=e},CODE_CUSTOM_DISCONNECT:4e3,CONN_STATE_NOT_CONNECTED:0,CONN_STATE_DISCONNECTED:1,CONN_STATE_CLOSED:4,CONN_STATE_CONNECTED:2,CONN_STATE_CONNECTING:3},wn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===fn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===fn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const vn={key:0,class:"overlay connection-lost"},bn={key:0,class:"overlay-content"},xn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Cn={key:1,class:"overlay-content"},kn=n("div",null,"Connecting...",-1);wn.render=function(e,l,o,s,r,d){return e.show?(a(),t("div",vn,[e.lostConnection?(a(),t("div",bn,[xn,n("span",{class:"btn",onClick:l[1]||(l[1]=t=>e.$emit("reconnect"))},"Reconnect")])):i("",!0),e.connecting?(a(),t("div",Cn,[kn])):i("",!0)])):i("",!0)};var An=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"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),zn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),Sn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),In=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),Pn=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),_n=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),Dn=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),Bn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),En=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);An.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[Tn,zn,Sn,In,Pn,_n,Dn,Bn,En,On])])};var Un=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Mn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Gn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function $n(){let e=0,t=0,n=1;const l=(l,i)=>{e+=l/n,t+=i/n},i=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},o=l=>({x:l.x/n-e,y:l.y/n-t}),a=l=>({x:(l.x+e)*n,y:(l.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n});return{move:l,canZoom:e=>n!=i(e),zoom:(e,t)=>((e,t)=>{if(n==e)return!1;const i=1-n/e;return l(-t.x*i,-t.y*i),n=e,!0})(i(e),t),worldToViewport:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:a,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=o(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:o}}function Rn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Vn={createCanvas:Rn,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=Rn(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=Rn(e.width,e.height),i=l.getContext("2d");return i.save(),i.drawImage(t,0,0),i.fillStyle=n,i.globalCompositeOperation="source-in",i.fillRect(0,0,t.width,t.height),i.restore(),i.save(),i.globalCompositeOperation="destination-over",i.drawImage(e,0,0),i.restore(),l}};const jn=Re("Debug.js");let Fn=0,Ln=0;var Wn=e=>{Fn=performance.now(),Ln=e},qn=e=>{const t=performance.now(),n=t-Fn;n>Ln&&jn.log(e+": "+n),Fn=t};const Hn=Re("PuzzleGraphics.js");function Qn(e,t){const n=We.coordByTileIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Yn={loadPuzzleBitmaps:async function(e){const t=await Vn.loadImageToBitmap(e.info.imageUrl),n=await Vn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){Hn.log("start createPuzzleTileBitmaps");var l=n.tileSize,i=n.tileMarginWidth,o=n.tileDrawSize,a=l/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0];const r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,o={x:i,y:i},r=fe.pointAdd(o,{x:l,y:0}),c=fe.pointAdd(r,{x:0,y:l}),u=fe.pointSub(c,{x:l,y:0});if(n.moveTo(o.x,o.y),0!==e.top)for(let l=0;l=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Jn=t/2,Xn=t-Jn;const n=1/4*this.canvas.width/(t/2);Kn=-n,Zn=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new el(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new el(this.rng));const e=[];for(;this.explodedBombs.length>0;){const t=this.explodedBombs.shift();if(!t)break;t.update(),t.alive&&e.push(t)}this.explodedBombs=e;const t=[];for(;this.readyBombs.length>0;){const e=this.readyBombs.shift();if(!e)break;e.update(this.particles),e.hasExploded?this.explodedBombs.push(e):t.push(e)}this.readyBombs=t;const n=[];for(;this.particles.length>0;){const e=this.particles.shift();if(!e)break;e.update(),e.alive&&n.push(e)}this.particles=n}render(){this.ctx.beginPath(),this.ctx.fillStyle="rgba(0, 0, 0, 0.1)",this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height);for(let e=0;e{const t=e.color+" "+e.d;if(!h[t]){const n=e.d?a:s;if(e.color){const l=e.d?r:d;h[t]=await createImageBitmap(Vn.colorizedCanvas(n,l,e.color))}else h[t]=n}return h[t]},y=function(e,t){return t.width=window.innerWidth,t.height=window.innerHeight,e.appendChild(t),window.addEventListener("resize",(()=>{t.width=window.innerWidth,t.height=window.innerHeight,al=!0})),t}(i,Vn.createCanvas()),f={final:!1,requesting:!0,log:[],logPointer:0,logIdx:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0};fn.onConnectionStateChange((e=>{o.setConnectionState(e)}));let w=()=>0;const v=async()=>{if("play"===l){const l=await fn.connect(n,e,t),i=We.decodeGame(l);St.setGame(i.id,i),w=()=>D()}else{if("replay"!==l)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await fn.requestReplayData(e,0,1e4),n=We.decodeGame(t.game);St.setGame(n.id,n),f.requesting=!1,f.log=t.log,f.lastRealTs=D(),f.gameStartTs=parseInt(f.log[0][4],10),f.lastGameTs=f.gameStartTs,w=()=>f.lastGameTs}}al=!0};await v();const b=St.getTileDrawOffset(e),x=St.getTileDrawSize(e),C=St.getPuzzleWidth(e),k=St.getPuzzleHeight(e),A=St.getTableWidth(e),T=St.getTableHeight(e),z={x:(A-C)/2,y:(T-k)/2},S={w:C,h:k},I={w:x,h:x},P=await Yn.loadPuzzleBitmaps(St.getPuzzle(e)),_=new nl(y,St.getRng(e));_.init();const B=y.getContext("2d");y.classList.add("loaded");const E=$n();E.move(-(A-y.width)/2,-(T-y.height)/2);const O=function(e,t,n){let l=[],i=!0,o=!1,a=!1,s=!1,r=!1,d=!1,c=!1,u=!1;const g=(e,t)=>{const l=n.viewportToWorld({x:e,y:t});return[l.x,l.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{i&&("Shift"===t.key?u=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?s=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?r=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?o=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?a=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};e.addEventListener("mousedown",(e=>{0===e.button&&y([ze,...p(e)])})),e.addEventListener("mouseup",(e=>{0===e.button&&y([Se,...p(e)])})),e.addEventListener("mousemove",(e=>{y([Ie,...p(e)])})),e.addEventListener("wheel",(e=>{if(n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Pe:_e;y([t,...p(e)])}})),t.addEventListener("keydown",(e=>m(!0,e))),t.addEventListener("keyup",(e=>m(!1,e))),t.addEventListener("keypress",(e=>{i&&(" "===e.key&&y([Oe]),"F"!==e.key&&"f"!==e.key||(il=!il,al=!0),"G"!==e.key&&"g"!==e.key||(ol=!ol,al=!0))}));const y=e=>{l.push(e)};return{addEvent:y,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=u?20:10,t=(o?e:0)-(a?e:0),l=(s?e:0)-(r?e:0);0===t&&0===l||y([Te,t,l]),d&&c||(d?n.canZoom("in")&&y([Pe,...h()]):c&&n.canZoom("out")&&y([_e,...h()]))},setHotkeys:e=>{i=e}}}(y,window,E),U=St.getImageUrl(e),M=()=>{const t=St.getStartTs(e),n=St.getFinishTs(e),l=w();o.setFinished(!!n),o.setDuration((n||l)-t)};M(),o.setPiecesDone(St.getFinishedTileCount(e)),o.setPiecesTotal(St.getTileCount(e));const N=w();o.setActivePlayers(St.getActivePlayers(e,N)),o.setIdlePlayers(St.getIdlePlayers(e,N));const G=!!St.getFinishTs(e);let $=G;const R=()=>$&&!G,V=()=>St.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",j=()=>St.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let F="",L="",W=!1;const q=e=>{W=e;const[t,n]=e?[F,"grab"]:[L,"default"];y.style.cursor=`url('${t}') ${u} ${p}, ${n}`},H=e=>{F=Vn.colorizedCanvas(a,r,e).toDataURL(),L=Vn.colorizedCanvas(s,d,e).toDataURL(),q(W)};H(j());const Q=()=>{o.setReplaySpeed&&o.setReplaySpeed(f.speeds[f.speedIdx]),o.setReplayPaused&&o.setReplayPaused(f.paused)};if("play"===l?setInterval(M,1e3):"replay"===l&&Q(),"play"===l)fn.onServerChange((n=>{n[0],n[1],n[2];const l=n[3];for(const[i,o]of l)switch(i){case Ne:{const n=We.decodePlayer(o);n.id!==t&&(St.setPlayer(e,n.id,n),al=!0)}break;case Me:{const t=We.decodeTile(o);St.setTile(e,t.idx,t),al=!0}break;case Ue:St.setPuzzleData(e,o),al=!0}$=!!St.getFinishTs(e)}));else if("replay"===l){let t=setInterval((()=>{const n=D();if(f.requesting)return void(f.lastRealTs=n);if(f.logPointer+1>=f.log.length)return f.lastRealTs=n,f.requesting=!0,void(async(e,t,n)=>{const l=await fn.requestReplayData(e,t,n);f.log=f.log.slice(f.logPointer),f.logPointer=0,f.log.push(...l.log),l.log.length<1e4&&(f.final=!0),f.requesting=!1})(e,f.logIdx,1e4);if(f.paused)return void(f.lastRealTs=n);const l=(n-f.lastRealTs)*f.speeds[f.speedIdx],i=f.lastGameTs+l;for(;;){if(f.paused)break;const n=f.logPointer+1;if(n>=f.log.length){f.final&&clearInterval(t);break}const l=f.log[n],o=f.gameStartTs+l[l.length-1];if(o>i)break;const a=l.slice();if(a[0]===Ce){const t=a[1];St.addPlayer(e,t,o),al=!0}else if(a[0]===ke){const t=St.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";St.addPlayer(e,t,o),al=!0}else if(a[0]===Ae){const t=St.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=a[2];St.handleInput(e,t,n,o),al=!0}f.logPointer=n,f.logIdx++}f.lastRealTs=n,f.lastGameTs=i,M()}),50)}let Y=null;return(e=>{const t=e.fps||60,n=e.slow||1,l=e.update,i=e.render,o=window.requestAnimationFrame,a=1/t,s=n*a;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>s;)d-=s,l(a);i(d/n),c=r,o(u)};o(u)})({update:()=>{O.createKeyEvents();for(const n of O.consumeAll())if("play"===l){const l=n[0];if(l===Te){const e=n[1],t=n[2];al=!0,E.move(e,t)}else if(l===Ie){if(Y&&!St.getFirstOwnedTile(e,t)){const e={x:n[1],y:n[2]},t=E.worldToViewport(e),l=Math.round(t.x-Y.x),i=Math.round(t.y-Y.y);al=!0,E.move(l,i),Y=t}}else if(l===Be)H(n[1]);else if(l===ze){const e={x:n[1],y:n[2]};Y=E.worldToViewport(e),q(!0)}else if(l===Se)Y=null,q(!1);else if(l===Pe){const e={x:n[1],y:n[2]};al=!0,E.zoom("in",E.worldToViewport(e))}else if(l===_e){const e={x:n[1],y:n[2]};al=!0,E.zoom("out",E.worldToViewport(e))}else l===Oe&&o.togglePreview();const i=w();St.handleInput(e,t,n,i).length>0&&(al=!0),fn.sendClientEvent(n)}else if("replay"===l){const e=n[0];if(e===Te){const e=n[1],t=n[2];al=!0,E.move(e,t)}else if(e===Ie){if(Y){const e={x:n[1],y:n[2]},t=E.worldToViewport(e),l=Math.round(t.x-Y.x),i=Math.round(t.y-Y.y);al=!0,E.move(l,i),Y=t}}else if(e===ze){const e={x:n[1],y:n[2]};Y=E.worldToViewport(e)}else if(e===Se)Y=null;else if(e===Pe){const e={x:n[1],y:n[2]};al=!0,E.zoom("in",E.worldToViewport(e))}else if(e===_e){const e={x:n[1],y:n[2]};al=!0,E.zoom("out",E.worldToViewport(e))}else e===Oe&&o.togglePreview()}$=!!St.getFinishTs(e),R()&&(_.update(),al=!0)},render:async()=>{if(!al)return;const n=w();let i,a,s;window.DEBUG&&Wn(0),B.fillStyle=V(),B.fillRect(0,0,y.width,y.height),window.DEBUG&&qn("clear done"),i=E.worldToViewportRaw(z),a=E.worldDimToViewportRaw(S),B.fillStyle="rgba(255, 255, 255, .3)",B.fillRect(i.x,i.y,a.w,a.h),window.DEBUG&&qn("board done");const r=St.getTilesSortedByZIndex(e);window.DEBUG&&qn("get tiles done"),a=E.worldDimToViewportRaw(I);for(const e of r)(-1===e.owner?il:ol)&&(s=P[e.idx],i=E.worldToViewportRaw({x:b+e.pos.x,y:b+e.pos.y}),B.drawImage(s,0,0,s.width,s.height,i.x,i.y,a.w,a.h));window.DEBUG&&qn("tiles done");const d=[];for(const o of St.getActivePlayers(e,n))c=o,("replay"===l||c.id!==t)&&(s=await m(o),i=E.worldToViewport(o),B.drawImage(s,i.x-u,i.y-p),d.push([`${o.name} (${o.points})`,i.x,i.y+g]));var c;B.fillStyle="white",B.textAlign="center";for(const[e,t,l]of d)B.fillText(e,t,l);window.DEBUG&&qn("players done"),o.setActivePlayers(St.getActivePlayers(e,n)),o.setIdlePlayers(St.getIdlePlayers(e,n)),o.setPiecesDone(St.getFinishedTileCount(e)),window.DEBUG&&qn("HUD done"),R()&&_.render(),al=!1}}),{setHotkeys:e=>{O.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),O.addEvent([De,e])},onColorChange:e=>{localStorage.setItem("player_color",e),O.addEvent([Be,e])},onNameChange:e=>{localStorage.setItem("player_name",e),O.addEvent([Ee,e])},replayOnSpeedUp:()=>{f.speedIdx+1{f.speedIdx>=1&&(f.speedIdx--,Q())},replayOnPauseToggle:()=>{f.paused=!f.paused,Q()},previewImageUrl:U,player:{background:V(),color:j(),name:St.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon"},disconnect:fn.disconnect,connect:v}}var rl=e({name:"game",components:{PuzzleStatus:Xt,Scores:Qt,SettingsOverlay:tn,PreviewOverlay:an,ConnectionOverlay:wn,HelpOverlay:An},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},disconnect:()=>{},connect:()=>{}}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await sl(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},setConnectionState:e=>{this.connectionState=e},togglePreview:()=>{this.toggle("preview",!1)}}))},unmounted(){this.g.disconnect()},methods:{reconnect(){this.g.connect()},toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}}});const dl={id:"game"},cl={class:"menu"},ul={class:"tabs"},gl=s("🧩 Puzzles");rl.render=function(e,i,s,r,d,c){const u=o("settings-overlay"),p=o("preview-overlay"),h=o("help-overlay"),m=o("connection-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",dl,[g(n(u,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(p,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(h,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",cl,[n("div",ul,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[gl])),_:1}),n("div",{class:"opener",onClick:i[5]||(i[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var pl=e({name:"replay",components:{PuzzleStatus:Xt,Scores:Qt,SettingsOverlay:tn,PreviewOverlay:an,HelpOverlay:An},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},disconnect:()=>{}},replay:{speed:1,paused:!1}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await sl(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"replay",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},setReplaySpeed:e=>{this.replay.speed=e},setReplayPaused:e=>{this.replay.paused=e}}))},unmounted(){this.g.disconnect()},methods:{toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}},computed:{replayText(){return"Replay-Speed: "+this.replay.speed+"x"+(this.replay.paused?" Paused":"")}}});const hl={id:"replay"},ml={class:"menu"},yl={class:"tabs"},fl=s("🧩 Puzzles");pl.render=function(e,i,s,d,c,u){const p=o("settings-overlay"),h=o("preview-overlay"),m=o("help-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",hl,[g(n(p,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(m,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:l((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:i[5]||(i[5]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",ml,[n("div",yl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[fl])),_:1}),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=b({history:x(),routes:[{name:"index",path:"/",component:R},{name:"new-game",path:"/new-game",component:Gt},{name:"game",path:"/g/:id",component:rl},{name:"replay",path:"/replay/:id",component:pl}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const l=C(k);l.config.globalProperties.$config=t,l.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=We.uniqId(),localStorage.setItem("ID",e)),e}(),l.use(n),l.mount("#app")})(); diff --git a/build/public/index.html b/build/public/index.html index bf7b9b2..9d4f95b 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 117d365..7438e73 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -14,271 +14,73 @@ import bodyParser from 'body-parser'; import v8 from 'v8'; import bsqlite from 'better-sqlite3'; -class Rng { - constructor(seed) { - this.rand_high = seed || 0xDEADC0DE; - this.rand_low = seed ^ 0x49616E42; - } - random(min, max) { - this.rand_high = ((this.rand_high << 16) + (this.rand_high >> 16) + this.rand_low) & 0xffffffff; - this.rand_low = (this.rand_low + this.rand_high) & 0xffffffff; - var n = (this.rand_high >>> 0) / 0xffffffff; - return (min + n * (max - min + 1)) | 0; - } - // get one random item from the given array - choice(array) { - return array[this.random(0, array.length - 1)]; - } - // return a shuffled (shallow) copy of the given array - shuffle(array) { - const arr = array.slice(); - for (let i = 0; i <= arr.length - 2; i++) { - const j = this.random(i, arr.length - 1); - const tmp = arr[i]; - arr[i] = arr[j]; - arr[j] = tmp; - } - return arr; - } - static serialize(rng) { - return { - rand_high: rng.rand_high, - rand_low: rng.rand_low - }; - } - static unserialize(rngSerialized) { - const rng = new Rng(0); - rng.rand_high = rngSerialized.rand_high; - rng.rand_low = rngSerialized.rand_low; - return rng; - } -} - -const slug = (str) => { - let tmp = str.toLowerCase(); - tmp = tmp.replace(/[^a-z0-9]+/g, '-'); - tmp = tmp.replace(/^-|-$/, ''); - return tmp; -}; -const pad = (x, pad) => { - const str = `${x}`; - if (str.length >= pad.length) { - return str; - } - return pad.substr(0, pad.length - str.length) + str; -}; -const logger = (...pre) => { - const log = (m) => (...args) => { - const d = new Date(); - const hh = pad(d.getHours(), '00'); - const mm = pad(d.getMinutes(), '00'); - const ss = pad(d.getSeconds(), '00'); - console[m](`${hh}:${mm}:${ss}`, ...pre, ...args); - }; +function pointSub(a, b) { + return { x: a.x - b.x, y: a.y - b.y }; +} +function pointAdd(a, b) { + return { x: a.x + b.x, y: a.y + b.y }; +} +function pointDistance(a, b) { + const diffX = a.x - b.x; + const diffY = a.y - b.y; + return Math.sqrt(diffX * diffX + diffY * diffY); +} +function pointInBounds(pt, rect) { + return pt.x >= rect.x + && pt.x <= rect.x + rect.w + && pt.y >= rect.y + && pt.y <= rect.y + rect.h; +} +function rectCenter(rect) { return { - log: log('log'), - error: log('error'), - info: log('info'), + x: rect.x + (rect.w / 2), + y: rect.y + (rect.h / 2), }; -}; -// get a unique id -const uniqId = () => Date.now().toString(36) + Math.random().toString(36).substring(2); -function encodeShape(data) { - /* encoded in 1 byte: - 00000000 - ^^ top - ^^ right - ^^ bottom - ^^ left - */ - return ((data.top + 1) << 0) - | ((data.right + 1) << 2) - | ((data.bottom + 1) << 4) - | ((data.left + 1) << 6); } -function decodeShape(data) { +/** + * Returns a rectangle with same dimensions as the given one, but + * location (x/y) moved by x and y. + * + * @param {x, y, w,, h} rect + * @param number x + * @param number y + * @returns {x, y, w, h} + */ +function rectMoved(rect, x, y) { return { - top: (data >> 0 & 0b11) - 1, - right: (data >> 2 & 0b11) - 1, - bottom: (data >> 4 & 0b11) - 1, - left: (data >> 6 & 0b11) - 1, + x: rect.x + x, + y: rect.y + y, + w: rect.w, + h: rect.h, }; } -function encodeTile(data) { - return [data.idx, data.pos.x, data.pos.y, data.z, data.owner, data.group]; +/** + * Returns true if the rectangles overlap, including their borders. + * + * @param {x, y, w, h} rectA + * @param {x, y, w, h} rectB + * @returns bool + */ +function rectsOverlap(rectA, rectB) { + return !(rectB.x > (rectA.x + rectA.w) + || rectA.x > (rectB.x + rectB.w) + || rectB.y > (rectA.y + rectA.h) + || rectA.y > (rectB.y + rectB.h)); } -function decodeTile(data) { - return { - idx: data[0], - pos: { - x: data[1], - y: data[2], - }, - z: data[3], - owner: data[4], - group: data[5], - }; +function rectCenterDistance(rectA, rectB) { + return pointDistance(rectCenter(rectA), rectCenter(rectB)); } -function encodePlayer(data) { - return [ - data.id, - data.x, - data.y, - data.d, - data.name, - data.color, - data.bgcolor, - data.points, - data.ts, - ]; -} -function decodePlayer(data) { - return { - id: data[0], - x: data[1], - y: data[2], - d: data[3], - name: data[4], - color: data[5], - bgcolor: data[6], - points: data[7], - ts: data[8], - }; -} -function encodeGame(data) { - if (Array.isArray(data)) { - return data; - } - return [ - data.id, - data.rng.type, - Rng.serialize(data.rng.obj), - data.puzzle, - data.players, - data.evtInfos, - data.scoreMode, - ]; -} -function decodeGame(data) { - if (!Array.isArray(data)) { - return data; - } - return { - id: data[0], - rng: { - type: data[1], - obj: Rng.unserialize(data[2]), - }, - puzzle: data[3], - players: data[4], - evtInfos: data[5], - scoreMode: data[6], - }; -} -function coordByTileIdx(info, tileIdx) { - const wTiles = info.width / info.tileSize; - return { - x: tileIdx % wTiles, - y: Math.floor(tileIdx / wTiles), - }; -} -const hash = (str) => { - let hash = 0; - for (let i = 0; i < str.length; i++) { - let chr = str.charCodeAt(i); - hash = ((hash << 5) - hash) + chr; - hash |= 0; // Convert to 32bit integer - } - return hash; -}; -function asQueryArgs(data) { - const q = []; - for (let k in data) { - const pair = [k, data[k]].map(encodeURIComponent); - q.push(pair.join('=')); - } - if (q.length === 0) { - return ''; - } - return `?${q.join('&')}`; -} -var Util = { - hash, - slug, - uniqId, - encodeShape, - decodeShape, - encodeTile, - decodeTile, - encodePlayer, - decodePlayer, - encodeGame, - decodeGame, - coordByTileIdx, - asQueryArgs, +var Geometry = { + pointSub, + pointAdd, + pointDistance, + pointInBounds, + rectCenter, + rectMoved, + rectCenterDistance, + rectsOverlap, }; -const log$4 = logger('WebSocketServer.js'); -/* -Example config - -config = { - hostname: 'localhost', - port: 1338, - connectstring: `ws://localhost:1338/ws`, -} -*/ -class EvtBus { - constructor() { - this._on = {}; - } - on(type, callback) { - this._on[type] = this._on[type] || []; - this._on[type].push(callback); - } - dispatch(type, ...args) { - (this._on[type] || []).forEach((cb) => { - cb(...args); - }); - } -} -class WebSocketServer { - constructor(config) { - this.config = config; - this._websocketserver = null; - this.evt = new EvtBus(); - } - on(type, callback) { - this.evt.on(type, callback); - } - listen() { - this._websocketserver = new WebSocket.Server(this.config); - this._websocketserver.on('connection', (socket, request) => { - const pathname = new URL(this.config.connectstring).pathname; - if (request.url.indexOf(pathname) !== 0) { - log$4.log('bad request url: ', request.url); - socket.close(); - return; - } - socket.on('message', (data) => { - log$4.log(`ws`, socket.protocol, data); - this.evt.dispatch('message', { socket, data }); - }); - socket.on('close', () => { - this.evt.dispatch('close', { socket }); - }); - }); - } - close() { - if (this._websocketserver) { - this._websocketserver.close(); - } - } - notifyOne(data, socket) { - socket.send(JSON.stringify(data)); - } -} - /* SERVER_CLIENT_MESSAGE_PROTOCOL NOTE: clients always send game id and their id @@ -364,73 +166,6 @@ var Protocol = { CHANGE_PLAYER, }; -function pointSub(a, b) { - return { x: a.x - b.x, y: a.y - b.y }; -} -function pointAdd(a, b) { - return { x: a.x + b.x, y: a.y + b.y }; -} -function pointDistance(a, b) { - const diffX = a.x - b.x; - const diffY = a.y - b.y; - return Math.sqrt(diffX * diffX + diffY * diffY); -} -function pointInBounds(pt, rect) { - return pt.x >= rect.x - && pt.x <= rect.x + rect.w - && pt.y >= rect.y - && pt.y <= rect.y + rect.h; -} -function rectCenter(rect) { - return { - x: rect.x + (rect.w / 2), - y: rect.y + (rect.h / 2), - }; -} -/** - * Returns a rectangle with same dimensions as the given one, but - * location (x/y) moved by x and y. - * - * @param {x, y, w,, h} rect - * @param number x - * @param number y - * @returns {x, y, w, h} - */ -function rectMoved(rect, x, y) { - return { - x: rect.x + x, - y: rect.y + y, - w: rect.w, - h: rect.h, - }; -} -/** - * Returns true if the rectangles overlap, including their borders. - * - * @param {x, y, w, h} rectA - * @param {x, y, w, h} rectB - * @returns bool - */ -function rectsOverlap(rectA, rectB) { - return !(rectB.x > (rectA.x + rectA.w) - || rectA.x > (rectB.x + rectB.w) - || rectB.y > (rectA.y + rectA.h) - || rectA.y > (rectB.y + rectB.h)); -} -function rectCenterDistance(rectA, rectB) { - return pointDistance(rectCenter(rectA), rectCenter(rectB)); -} -var Geometry = { - pointSub, - pointAdd, - pointDistance, - pointInBounds, - rectCenter, - rectMoved, - rectCenterDistance, - rectsOverlap, -}; - const MS = 1; const SEC = MS * 1000; const MIN = SEC * 60; @@ -527,8 +262,8 @@ function setPlayer(gameId, playerId, player) { GAMES[gameId].players[idx] = Util.encodePlayer(player); } } -function setTile(gameId, tileIdx, tile) { - GAMES[gameId].puzzle.tiles[tileIdx] = Util.encodeTile(tile); +function setPiece(gameId, pieceIdx, piece) { + GAMES[gameId].puzzle.tiles[pieceIdx] = Util.encodePiece(piece); } function setPuzzleData(gameId, data) { GAMES[gameId].puzzle.data = data; @@ -583,7 +318,7 @@ function getAllPlayers(gameId) { function get$1(gameId) { return GAMES[gameId]; } -function getTileCount(gameId) { +function getPieceCount(gameId) { return GAMES[gameId].puzzle.tiles.length; } function getImageUrl(gameId) { @@ -596,20 +331,20 @@ function getScoreMode(gameId) { return GAMES[gameId].scoreMode || ScoreMode.FINAL; } function isFinished(gameId) { - return getFinishedTileCount(gameId) === getTileCount(gameId); + return getFinishedPiecesCount(gameId) === getPieceCount(gameId); } -function getFinishedTileCount(gameId) { +function getFinishedPiecesCount(gameId) { let count = 0; for (let t of GAMES[gameId].puzzle.tiles) { - if (Util.decodeTile(t).owner === -1) { + if (Util.decodePiece(t).owner === -1) { count++; } } return count; } -function getTilesSortedByZIndex(gameId) { - const tiles = GAMES[gameId].puzzle.tiles.map(Util.decodeTile); - return tiles.sort((t1, t2) => t1.z - t2.z); +function getPiecesSortedByZIndex(gameId) { + const pieces = GAMES[gameId].puzzle.tiles.map(Util.decodePiece); + return pieces.sort((t1, t2) => t1.z - t2.z); } function changePlayer(gameId, playerId, change) { const player = getPlayer(gameId, playerId); @@ -628,22 +363,22 @@ function changeData(gameId, change) { GAMES[gameId].puzzle.data[k] = change[k]; } } -function changeTile(gameId, tileIdx, change) { +function changeTile(gameId, pieceIdx, change) { for (let k of Object.keys(change)) { - const tile = Util.decodeTile(GAMES[gameId].puzzle.tiles[tileIdx]); + const piece = Util.decodePiece(GAMES[gameId].puzzle.tiles[pieceIdx]); // @ts-ignore - tile[k] = change[k]; - GAMES[gameId].puzzle.tiles[tileIdx] = Util.encodeTile(tile); + piece[k] = change[k]; + GAMES[gameId].puzzle.tiles[pieceIdx] = Util.encodePiece(piece); } } -const getTile = (gameId, tileIdx) => { - return Util.decodeTile(GAMES[gameId].puzzle.tiles[tileIdx]); +const getPiece = (gameId, pieceIdx) => { + return Util.decodePiece(GAMES[gameId].puzzle.tiles[pieceIdx]); }; -const getTileGroup = (gameId, tileIdx) => { - const tile = getTile(gameId, tileIdx); +const getPieceGroup = (gameId, tileIdx) => { + const tile = getPiece(gameId, tileIdx); return tile.group; }; -const getFinalTilePos = (gameId, tileIdx) => { +const getFinalPiecePos = (gameId, tileIdx) => { const info = GAMES[gameId].puzzle.info; const boardPos = { x: (info.table.width - info.width) / 2, @@ -652,8 +387,8 @@ const getFinalTilePos = (gameId, tileIdx) => { const srcPos = srcPosByTileIdx(gameId, tileIdx); return Geometry.pointAdd(boardPos, srcPos); }; -const getTilePos = (gameId, tileIdx) => { - const tile = getTile(gameId, tileIdx); +const getPiecePos = (gameId, tileIdx) => { + const tile = getPiece(gameId, tileIdx); return tile.pos; }; // todo: instead, just make the table bigger and use that :) @@ -669,9 +404,9 @@ const getBounds = (gameId) => { h: th + 2 * overY, }; }; -const getTileBounds = (gameId, tileIdx) => { - const s = getTileSize(gameId); - const tile = getTile(gameId, tileIdx); +const getPieceBounds = (gameId, tileIdx) => { + const s = getPieceSize(gameId); + const tile = getPiece(gameId, tileIdx); return { x: tile.pos.x, y: tile.pos.y, @@ -680,29 +415,29 @@ const getTileBounds = (gameId, tileIdx) => { }; }; const getTileZIndex = (gameId, tileIdx) => { - const tile = getTile(gameId, tileIdx); + const tile = getPiece(gameId, tileIdx); return tile.z; }; -const getFirstOwnedTileIdx = (gameId, playerId) => { +const getFirstOwnedPieceIdx = (gameId, playerId) => { for (let t of GAMES[gameId].puzzle.tiles) { - const tile = Util.decodeTile(t); + const tile = Util.decodePiece(t); if (tile.owner === playerId) { return tile.idx; } } return -1; }; -const getFirstOwnedTile = (gameId, playerId) => { - const idx = getFirstOwnedTileIdx(gameId, playerId); +const getFirstOwnedPiece = (gameId, playerId) => { + const idx = getFirstOwnedPieceIdx(gameId, playerId); return idx < 0 ? null : GAMES[gameId].puzzle.tiles[idx]; }; -const getTileDrawOffset = (gameId) => { +const getPieceDrawOffset = (gameId) => { return GAMES[gameId].puzzle.info.tileDrawOffset; }; -const getTileDrawSize = (gameId) => { +const getPieceDrawSize = (gameId) => { return GAMES[gameId].puzzle.info.tileDrawSize; }; -const getTileSize = (gameId) => { +const getPieceSize = (gameId) => { return GAMES[gameId].puzzle.info.tileSize; }; const getStartTs = (gameId) => { @@ -754,27 +489,27 @@ const setTilesZIndex = (gameId, tileIdxs, zIndex) => { } }; const moveTileDiff = (gameId, tileIdx, diff) => { - const oldPos = getTilePos(gameId, tileIdx); + const oldPos = getPiecePos(gameId, tileIdx); const pos = Geometry.pointAdd(oldPos, diff); changeTile(gameId, tileIdx, { pos }); }; const moveTilesDiff = (gameId, tileIdxs, diff) => { - const tileDrawSize = getTileDrawSize(gameId); + const drawSize = getPieceDrawSize(gameId); const bounds = getBounds(gameId); const cappedDiff = diff; for (let tileIdx of tileIdxs) { - const t = getTile(gameId, tileIdx); + const t = getPiece(gameId, tileIdx); if (t.pos.x + diff.x < bounds.x) { cappedDiff.x = Math.max(bounds.x - t.pos.x, cappedDiff.x); } - else if (t.pos.x + tileDrawSize + diff.x > bounds.x + bounds.w) { - cappedDiff.x = Math.min(bounds.x + bounds.w - t.pos.x + tileDrawSize, cappedDiff.x); + else if (t.pos.x + drawSize + diff.x > bounds.x + bounds.w) { + cappedDiff.x = Math.min(bounds.x + bounds.w - t.pos.x + drawSize, cappedDiff.x); } if (t.pos.y + diff.y < bounds.y) { cappedDiff.y = Math.max(bounds.y - t.pos.y, cappedDiff.y); } - else if (t.pos.y + tileDrawSize + diff.y > bounds.y + bounds.h) { - cappedDiff.y = Math.min(bounds.y + bounds.h - t.pos.y + tileDrawSize, cappedDiff.y); + else if (t.pos.y + drawSize + diff.y > bounds.y + bounds.h) { + cappedDiff.y = Math.min(bounds.y + bounds.h - t.pos.y + drawSize, cappedDiff.y); } } for (let tileIdx of tileIdxs) { @@ -792,49 +527,49 @@ const setTilesOwner = (gameId, tileIdxs, owner) => { } }; // get all grouped tiles for a tile -function getGroupedTileIdxs(gameId, tileIdx) { - const tiles = GAMES[gameId].puzzle.tiles; - const tile = Util.decodeTile(tiles[tileIdx]); +function getGroupedPieceIdxs(gameId, pieceIdx) { + const pieces = GAMES[gameId].puzzle.tiles; + const piece = Util.decodePiece(pieces[pieceIdx]); const grouped = []; - if (tile.group) { - for (let other of tiles) { - const otherTile = Util.decodeTile(other); - if (otherTile.group === tile.group) { - grouped.push(otherTile.idx); + if (piece.group) { + for (let other of pieces) { + const otherPiece = Util.decodePiece(other); + if (otherPiece.group === piece.group) { + grouped.push(otherPiece.idx); } } } else { - grouped.push(tile.idx); + grouped.push(piece.idx); } return grouped; } // Returns the index of the puzzle tile with the highest z index // that is not finished yet and that matches the position -const freeTileIdxByPos = (gameId, pos) => { +const freePieceIdxByPos = (gameId, pos) => { let info = GAMES[gameId].puzzle.info; - let tiles = GAMES[gameId].puzzle.tiles; + let pieces = GAMES[gameId].puzzle.tiles; let maxZ = -1; - let tileIdx = -1; - for (let idx = 0; idx < tiles.length; idx++) { - const tile = Util.decodeTile(tiles[idx]); - if (tile.owner !== 0) { + let pieceIdx = -1; + for (let idx = 0; idx < pieces.length; idx++) { + const piece = Util.decodePiece(pieces[idx]); + if (piece.owner !== 0) { continue; } const collisionRect = { - x: tile.pos.x, - y: tile.pos.y, + x: piece.pos.x, + y: piece.pos.y, w: info.tileSize, h: info.tileSize, }; if (Geometry.pointInBounds(pos, collisionRect)) { - if (maxZ === -1 || tile.z > maxZ) { - maxZ = tile.z; - tileIdx = idx; + if (maxZ === -1 || piece.z > maxZ) { + maxZ = piece.z; + pieceIdx = idx; } } } - return tileIdx; + return pieceIdx; }; const getPlayerBgColor = (gameId, playerId) => { const p = getPlayer(gameId, playerId); @@ -854,8 +589,8 @@ const getPlayerPoints = (gameId, playerId) => { }; // determine if two tiles are grouped together const areGrouped = (gameId, tileIdx1, tileIdx2) => { - const g1 = getTileGroup(gameId, tileIdx1); - const g2 = getTileGroup(gameId, tileIdx2); + const g1 = getPieceGroup(gameId, tileIdx1); + const g2 = getPieceGroup(gameId, tileIdx2); return !!(g1 && g1 === g2); }; const getTableWidth = (gameId) => { @@ -886,7 +621,7 @@ function handleInput$1(gameId, playerId, input, ts) { const _tileChange = (tileIdx) => { changes.push([ Protocol.CHANGE_TILE, - Util.encodeTile(getTile(gameId, tileIdx)), + Util.encodePiece(getPiece(gameId, tileIdx)), ]); }; const _tileChanges = (tileIdxs) => { @@ -907,8 +642,8 @@ function handleInput$1(gameId, playerId, input, ts) { // put both tiles (and their grouped tiles) in the same group const groupTiles = (gameId, tileIdx1, tileIdx2) => { const tiles = GAMES[gameId].puzzle.tiles; - const group1 = getTileGroup(gameId, tileIdx1); - const group2 = getTileGroup(gameId, tileIdx2); + const group1 = getPieceGroup(gameId, tileIdx1); + const group2 = getPieceGroup(gameId, tileIdx2); let group; const searchGroups = []; if (group1) { @@ -936,10 +671,10 @@ function handleInput$1(gameId, playerId, input, ts) { // TODO: strange if (searchGroups.length > 0) { for (const t of tiles) { - const tile = Util.decodeTile(t); - if (searchGroups.includes(tile.group)) { - changeTile(gameId, tile.idx, { group }); - _tileChange(tile.idx); + const piece = Util.decodePiece(t); + if (searchGroups.includes(piece.group)) { + changeTile(gameId, piece.idx, { group }); + _tileChange(piece.idx); } } } @@ -967,12 +702,12 @@ function handleInput$1(gameId, playerId, input, ts) { changePlayer(gameId, playerId, { d: 1, ts }); _playerChange(); evtInfo._last_mouse_down = pos; - const tileIdxAtPos = freeTileIdxByPos(gameId, pos); + const tileIdxAtPos = freePieceIdxByPos(gameId, pos); if (tileIdxAtPos >= 0) { let maxZ = getMaxZIndex(gameId) + 1; changeData(gameId, { maxZ }); _dataChange(); - const tileIdxs = getGroupedTileIdxs(gameId, tileIdxAtPos); + const tileIdxs = getGroupedPieceIdxs(gameId, tileIdxAtPos); setTilesZIndex(gameId, tileIdxs, getMaxZIndex(gameId)); setTilesOwner(gameId, tileIdxs, playerId); _tileChanges(tileIdxs); @@ -989,18 +724,18 @@ function handleInput$1(gameId, playerId, input, ts) { _playerChange(); } else { - let tileIdx = getFirstOwnedTileIdx(gameId, playerId); + let tileIdx = getFirstOwnedPieceIdx(gameId, playerId); if (tileIdx >= 0) { // player is moving a tile (and hand) changePlayer(gameId, playerId, { x, y, ts }); _playerChange(); // check if pos is on the tile, otherwise dont move // (mouse could be out of table, but tile stays on it) - const tileIdxs = getGroupedTileIdxs(gameId, tileIdx); + const tileIdxs = getGroupedPieceIdxs(gameId, tileIdx); let anyOk = Geometry.pointInBounds(pos, getBounds(gameId)) && Geometry.pointInBounds(evtInfo._last_mouse_down, getBounds(gameId)); for (let idx of tileIdxs) { - const bounds = getTileBounds(gameId, idx); + const bounds = getPieceBounds(gameId, idx); if (Geometry.pointInBounds(pos, bounds)) { anyOk = true; break; @@ -1029,15 +764,15 @@ function handleInput$1(gameId, playerId, input, ts) { const pos = { x, y }; const d = 0; evtInfo._last_mouse_down = null; - let tileIdx = getFirstOwnedTileIdx(gameId, playerId); + let tileIdx = getFirstOwnedPieceIdx(gameId, playerId); if (tileIdx >= 0) { // drop the tile(s) - let tileIdxs = getGroupedTileIdxs(gameId, tileIdx); + let tileIdxs = getGroupedPieceIdxs(gameId, tileIdx); setTilesOwner(gameId, tileIdxs, 0); _tileChanges(tileIdxs); // Check if the tile was dropped near the final location - let tilePos = getTilePos(gameId, tileIdx); - let finalPos = getFinalTilePos(gameId, tileIdx); + let tilePos = getPiecePos(gameId, tileIdx); + let finalPos = getFinalPiecePos(gameId, tileIdx); if (Geometry.pointDistance(finalPos, tilePos) < puzzle.info.snapDistance) { let diff = Geometry.pointSub(finalPos, tilePos); // Snap the tile to the final destination @@ -1055,7 +790,7 @@ function handleInput$1(gameId, playerId, input, ts) { changePlayer(gameId, playerId, { d, ts, points }); _playerChange(); // check if the puzzle is finished - if (getFinishedTileCount(gameId) === getTileCount(gameId)) { + if (getFinishedPiecesCount(gameId) === getPieceCount(gameId)) { changeData(gameId, { finished: ts }); _dataChange(); } @@ -1070,14 +805,14 @@ function handleInput$1(gameId, playerId, input, ts) { if (areGrouped(gameId, tileIdx, otherTileIdx)) { return false; } - const tilePos = getTilePos(gameId, tileIdx); - const dstPos = Geometry.pointAdd(getTilePos(gameId, otherTileIdx), { x: off[0] * info.tileSize, y: off[1] * info.tileSize }); + const tilePos = getPiecePos(gameId, tileIdx); + const dstPos = Geometry.pointAdd(getPiecePos(gameId, otherTileIdx), { x: off[0] * info.tileSize, y: off[1] * info.tileSize }); if (Geometry.pointDistance(tilePos, dstPos) < info.snapDistance) { let diff = Geometry.pointSub(dstPos, tilePos); - let tileIdxs = getGroupedTileIdxs(gameId, tileIdx); + let tileIdxs = getGroupedPieceIdxs(gameId, tileIdx); moveTilesDiff(gameId, tileIdxs, diff); groupTiles(gameId, tileIdx, otherTileIdx); - tileIdxs = getGroupedTileIdxs(gameId, tileIdx); + tileIdxs = getGroupedPieceIdxs(gameId, tileIdx); const zIndex = getMaxZIndexByTileIdxs(gameId, tileIdxs); setTilesZIndex(gameId, tileIdxs, zIndex); _tileChanges(tileIdxs); @@ -1086,7 +821,7 @@ function handleInput$1(gameId, playerId, input, ts) { return false; }; let snapped = false; - for (let tileIdxTmp of getGroupedTileIdxs(gameId, tileIdx)) { + for (let tileIdxTmp of getGroupedPieceIdxs(gameId, tileIdx)) { let othersIdxs = getSurroundingTilesByIdx(gameId, tileIdxTmp); if (check(gameId, tileIdxTmp, othersIdxs[0], [0, 1]) // top || check(gameId, tileIdxTmp, othersIdxs[1], [-1, 0]) // right @@ -1136,15 +871,14 @@ function handleInput$1(gameId, playerId, input, ts) { return changes; } var GameCommon = { - __createPlayerObject, setGame, exists: exists$1, playerExists, getActivePlayers, getIdlePlayers, addPlayer: addPlayer$1, - getFinishedTileCount, - getTileCount, + getFinishedPiecesCount, + getPieceCount, getImageUrl, setImageUrl, get: get$1, @@ -1156,7 +890,7 @@ var GameCommon = { getPlayerIdByIndex, changePlayer, setPlayer, - setTile, + setPiece, setPuzzleData, getTableWidth, getTableHeight, @@ -1164,16 +898,275 @@ var GameCommon = { getRng, getPuzzleWidth, getPuzzleHeight, - getTilesSortedByZIndex, - getFirstOwnedTile, - getTileDrawOffset, - getTileDrawSize, - getFinalTilePos, + getPiecesSortedByZIndex, + getFirstOwnedPiece, + getPieceDrawOffset, + getPieceDrawSize, + getFinalPiecePos, getStartTs, getFinishTs, handleInput: handleInput$1, }; +class Rng { + constructor(seed) { + this.rand_high = seed || 0xDEADC0DE; + this.rand_low = seed ^ 0x49616E42; + } + random(min, max) { + this.rand_high = ((this.rand_high << 16) + (this.rand_high >> 16) + this.rand_low) & 0xffffffff; + this.rand_low = (this.rand_low + this.rand_high) & 0xffffffff; + var n = (this.rand_high >>> 0) / 0xffffffff; + return (min + n * (max - min + 1)) | 0; + } + // get one random item from the given array + choice(array) { + return array[this.random(0, array.length - 1)]; + } + // return a shuffled (shallow) copy of the given array + shuffle(array) { + const arr = array.slice(); + for (let i = 0; i <= arr.length - 2; i++) { + const j = this.random(i, arr.length - 1); + const tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + return arr; + } + static serialize(rng) { + return { + rand_high: rng.rand_high, + rand_low: rng.rand_low + }; + } + static unserialize(rngSerialized) { + const rng = new Rng(0); + rng.rand_high = rngSerialized.rand_high; + rng.rand_low = rngSerialized.rand_low; + return rng; + } +} + +const slug = (str) => { + let tmp = str.toLowerCase(); + tmp = tmp.replace(/[^a-z0-9]+/g, '-'); + tmp = tmp.replace(/^-|-$/, ''); + return tmp; +}; +const pad = (x, pad) => { + const str = `${x}`; + if (str.length >= pad.length) { + return str; + } + return pad.substr(0, pad.length - str.length) + str; +}; +const logger = (...pre) => { + const log = (m) => (...args) => { + const d = new Date(); + const hh = pad(d.getHours(), '00'); + const mm = pad(d.getMinutes(), '00'); + const ss = pad(d.getSeconds(), '00'); + console[m](`${hh}:${mm}:${ss}`, ...pre, ...args); + }; + return { + log: log('log'), + error: log('error'), + info: log('info'), + }; +}; +// get a unique id +const uniqId = () => Date.now().toString(36) + Math.random().toString(36).substring(2); +function encodeShape(data) { + /* encoded in 1 byte: + 00000000 + ^^ top + ^^ right + ^^ bottom + ^^ left + */ + return ((data.top + 1) << 0) + | ((data.right + 1) << 2) + | ((data.bottom + 1) << 4) + | ((data.left + 1) << 6); +} +function decodeShape(data) { + return { + top: (data >> 0 & 0b11) - 1, + right: (data >> 2 & 0b11) - 1, + bottom: (data >> 4 & 0b11) - 1, + left: (data >> 6 & 0b11) - 1, + }; +} +function encodePiece(data) { + return [data.idx, data.pos.x, data.pos.y, data.z, data.owner, data.group]; +} +function decodePiece(data) { + return { + idx: data[0], + pos: { + x: data[1], + y: data[2], + }, + z: data[3], + owner: data[4], + group: data[5], + }; +} +function encodePlayer(data) { + return [ + data.id, + data.x, + data.y, + data.d, + data.name, + data.color, + data.bgcolor, + data.points, + data.ts, + ]; +} +function decodePlayer(data) { + return { + id: data[0], + x: data[1], + y: data[2], + d: data[3], + name: data[4], + color: data[5], + bgcolor: data[6], + points: data[7], + ts: data[8], + }; +} +function encodeGame(data) { + return [ + data.id, + data.rng.type || '', + Rng.serialize(data.rng.obj), + data.puzzle, + data.players, + data.evtInfos, + data.scoreMode || ScoreMode.FINAL, + ]; +} +function decodeGame(data) { + return { + id: data[0], + rng: { + type: data[1], + obj: Rng.unserialize(data[2]), + }, + puzzle: data[3], + players: data[4], + evtInfos: data[5], + scoreMode: data[6], + }; +} +function coordByTileIdx(info, tileIdx) { + const wTiles = info.width / info.tileSize; + return { + x: tileIdx % wTiles, + y: Math.floor(tileIdx / wTiles), + }; +} +const hash = (str) => { + let hash = 0; + for (let i = 0; i < str.length; i++) { + let chr = str.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash |= 0; // Convert to 32bit integer + } + return hash; +}; +function asQueryArgs(data) { + const q = []; + for (let k in data) { + const pair = [k, data[k]].map(encodeURIComponent); + q.push(pair.join('=')); + } + if (q.length === 0) { + return ''; + } + return `?${q.join('&')}`; +} +var Util = { + hash, + slug, + uniqId, + encodeShape, + decodeShape, + encodePiece, + decodePiece, + encodePlayer, + decodePlayer, + encodeGame, + decodeGame, + coordByTileIdx, + asQueryArgs, +}; + +const log$4 = logger('WebSocketServer.js'); +/* +Example config + +config = { + hostname: 'localhost', + port: 1338, + connectstring: `ws://localhost:1338/ws`, +} +*/ +class EvtBus { + constructor() { + this._on = {}; + } + on(type, callback) { + this._on[type] = this._on[type] || []; + this._on[type].push(callback); + } + dispatch(type, ...args) { + (this._on[type] || []).forEach((cb) => { + cb(...args); + }); + } +} +class WebSocketServer { + constructor(config) { + this.config = config; + this._websocketserver = null; + this.evt = new EvtBus(); + } + on(type, callback) { + this.evt.on(type, callback); + } + listen() { + this._websocketserver = new WebSocket.Server(this.config); + this._websocketserver.on('connection', (socket, request) => { + const pathname = new URL(this.config.connectstring).pathname; + if (request.url.indexOf(pathname) !== 0) { + log$4.log('bad request url: ', request.url); + socket.close(); + return; + } + socket.on('message', (data) => { + log$4.log(`ws`, socket.protocol, data); + this.evt.dispatch('message', { socket, data }); + }); + socket.on('close', () => { + this.evt.dispatch('close', { socket }); + }); + }); + } + close() { + if (this._websocketserver) { + this._websocketserver.close(); + } + } + notifyOne(data, socket) { + socket.send(JSON.stringify(data)); + } +} + const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const BASE_DIR = `${__dirname}/../..`; @@ -1409,7 +1402,7 @@ async function createPuzzle(rng, targetTiles, image, ts) { if (!dim.w || !dim.h) { throw `[ 2021-05-16 invalid dimension for path ${imagePath} ]`; } - const info = determinePuzzleInfo(dim.w, dim.h, targetTiles); + const info = determinePuzzleInfo(dim, targetTiles); let tiles = new Array(info.tiles); for (let i = 0; i < tiles.length; i++) { tiles[i] = { idx: i }; @@ -1464,7 +1457,7 @@ async function createPuzzle(rng, targetTiles, image, ts) { // then shuffle the positions positions = rng.shuffle(positions); const pieces = tiles.map(tile => { - return Util.encodeTile({ + return Util.encodePiece({ idx: tile.idx, group: 0, z: 0, @@ -1537,9 +1530,9 @@ function determinePuzzleTileShapes(rng, info) { } return shapes.map(Util.encodeShape); } -const determineTilesXY = (w, h, targetTiles) => { - const w_ = w < h ? (w * h) : (w * w); - const h_ = w < h ? (h * h) : (w * h); +const determineTilesXY = (dim, targetTiles) => { + const w_ = dim.w < dim.h ? (dim.w * dim.h) : (dim.w * dim.w); + const h_ = dim.w < dim.h ? (dim.h * dim.h) : (dim.w * dim.h); let size = 0; let tiles = 0; do { @@ -1552,8 +1545,8 @@ const determineTilesXY = (w, h, targetTiles) => { tilesY: Math.round(h_ / size), }; }; -const determinePuzzleInfo = (w, h, targetTiles) => { - const { tilesX, tilesY } = determineTilesXY(w, h, targetTiles); +const determinePuzzleInfo = (dim, targetTiles) => { + const { tilesX, tilesY } = determineTilesXY(dim, targetTiles); const tiles = tilesX * tilesY; const tileSize = TILE_SIZE; const width = tilesX * tileSize; @@ -1606,7 +1599,7 @@ function loadGame(gameId) { } if (typeof game.puzzle.data.finished === 'undefined') { const unfinished = game.puzzle.tiles - .map(Util.decodeTile) + .map(Util.decodePiece) .find((t) => t.owner !== -1); game.puzzle.data.finished = unfinished ? 0 : Time.timestamp(); } @@ -1700,16 +1693,6 @@ var Game = { createGame, addPlayer, handleInput, - getAllGames: GameCommon.getAllGames, - getActivePlayers: GameCommon.getActivePlayers, - getFinishedTileCount: GameCommon.getFinishedTileCount, - getImageUrl: GameCommon.getImageUrl, - getTileCount: GameCommon.getTileCount, - exists: GameCommon.exists, - playerExists: GameCommon.playerExists, - get: GameCommon.get, - getStartTs: GameCommon.getStartTs, - getFinishTs: GameCommon.getFinishTs, }; const log$2 = logger('GameSocket.js'); @@ -1966,15 +1949,15 @@ app.get('/api/newgame-data', (req, res) => { app.get('/api/index-data', (req, res) => { const ts = Time.timestamp(); const games = [ - ...Game.getAllGames().map((game) => ({ + ...GameCommon.getAllGames().map((game) => ({ id: game.id, hasReplay: GameLog.exists(game.id), - started: Game.getStartTs(game.id), - finished: Game.getFinishTs(game.id), - tilesFinished: Game.getFinishedTileCount(game.id), - tilesTotal: Game.getTileCount(game.id), - players: Game.getActivePlayers(game.id, ts).length, - imageUrl: Game.getImageUrl(game.id), + started: GameCommon.getStartTs(game.id), + finished: GameCommon.getFinishTs(game.id), + tilesFinished: GameCommon.getFinishedPiecesCount(game.id), + tilesTotal: GameCommon.getPieceCount(game.id), + players: GameCommon.getActivePlayers(game.id, ts).length, + imageUrl: GameCommon.getImageUrl(game.id), })), ]; res.send({ @@ -2036,7 +2019,7 @@ app.post('/newgame', bodyParser.json(), async (req, res) => { const gameSettings = req.body; log.log(gameSettings); const gameId = Util.uniqId(); - if (!Game.exists(gameId)) { + if (!GameCommon.exists(gameId)) { const ts = Time.timestamp(); await Game.createGame(gameId, gameSettings.tiles, gameSettings.image, ts, gameSettings.scoreMode); } @@ -2072,26 +2055,26 @@ wss.on('message', async ({ socket, data }) => { switch (msgType) { case Protocol.EV_CLIENT_INIT: { - if (!Game.exists(gameId)) { + if (!GameCommon.exists(gameId)) { throw `[game ${gameId} does not exist... ]`; } const ts = Time.timestamp(); Game.addPlayer(gameId, clientId, ts); GameSockets.addSocket(gameId, socket); - const game = Game.get(gameId); + const game = GameCommon.get(gameId); notify([Protocol.EV_SERVER_INIT, Util.encodeGame(game)], [socket]); } break; case Protocol.EV_CLIENT_EVENT: { - if (!Game.exists(gameId)) { + if (!GameCommon.exists(gameId)) { throw `[game ${gameId} does not exist... ]`; } const clientSeq = msg[1]; const clientEvtData = msg[2]; const ts = Time.timestamp(); let sendGame = false; - if (!Game.playerExists(gameId, clientId)) { + if (!GameCommon.playerExists(gameId, clientId)) { Game.addPlayer(gameId, clientId, ts); sendGame = true; } @@ -2100,7 +2083,7 @@ wss.on('message', async ({ socket, data }) => { sendGame = true; } if (sendGame) { - const game = Game.get(gameId); + const game = GameCommon.get(gameId); notify([Protocol.EV_SERVER_INIT, Util.encodeGame(game)], [socket]); } const changes = Game.handleInput(gameId, clientId, clientEvtData, ts); diff --git a/scripts/fix_tiles.ts b/scripts/fix_tiles.ts index 6da6aef..5eb7ee9 100644 --- a/scripts/fix_tiles.ts +++ b/scripts/fix_tiles.ts @@ -7,22 +7,22 @@ const log = logger('fix_tiles.js') function fix_tiles(gameId) { GameStorage.loadGame(gameId) let changed = false - const tiles = GameCommon.getTilesSortedByZIndex(gameId) + const tiles = GameCommon.getPiecesSortedByZIndex(gameId) for (let tile of tiles) { if (tile.owner === -1) { - const p = GameCommon.getFinalTilePos(gameId, tile.idx) + const p = GameCommon.getFinalPiecePos(gameId, tile.idx) if (p.x === tile.pos.x && p.y === tile.pos.y) { // log.log('all good', tile.pos) } else { log.log('bad tile pos', tile.pos, 'should be: ', p) tile.pos = p - GameCommon.setTile(gameId, tile.idx, tile) + GameCommon.setPiece(gameId, tile.idx, tile) changed = true } } else if (tile.owner !== 0) { tile.owner = 0 log.log('unowning tile', tile.idx) - GameCommon.setTile(gameId, tile.idx, tile) + GameCommon.setPiece(gameId, tile.idx, tile) changed = true } } diff --git a/src/common/GameCommon.ts b/src/common/GameCommon.ts index f498009..e9e2d30 100644 --- a/src/common/GameCommon.ts +++ b/src/common/GameCommon.ts @@ -1,14 +1,50 @@ import Geometry, { Point, Rect } from './Geometry' import Protocol from './Protocol' -import { Rng } from './Rng' +import { Rng, RngSerialized } from './Rng' import Time from './Time' +import { FixedLengthArray } from './Types' import Util from './Util' export type Timestamp = number -export type EncodedPlayer = Array -export type EncodedPiece = Array + +export type EncodedPlayer = FixedLengthArray<[ + string, + number, + number, + 0|1, + string|null, + string|null, + string|null, + number, + Timestamp, +]> + +export type EncodedPiece = FixedLengthArray<[ + number, + number, + number, + number, + string|number, + number, +]> + export type EncodedPieceShape = number +export type EncodedGame = FixedLengthArray<[ + string, + string, + RngSerialized, + Puzzle, + Array, + Record, + ScoreMode, +]> + +export interface ReplayData { + log: any[], + game: EncodedGame|null +} + export interface Tag { id: number slug: string @@ -20,7 +56,7 @@ interface GameRng { type?: string } -interface Game { +export interface Game { id: string players: Array puzzle: Puzzle @@ -112,7 +148,7 @@ export interface Player { color: string|null bgcolor: string|null points: number - ts: number + ts: Timestamp } interface EvtInfo { @@ -134,7 +170,7 @@ function exists(gameId: string) { return (!!GAMES[gameId]) || false } -function __createPlayerObject(id: string, ts: number): Player { +function __createPlayerObject(id: string, ts: Timestamp): Player { return { id: id, x: 0, @@ -191,8 +227,8 @@ function setPlayer( } } -function setTile(gameId: string, tileIdx: number, tile: Piece): void { - GAMES[gameId].puzzle.tiles[tileIdx] = Util.encodeTile(tile) +function setPiece(gameId: string, pieceIdx: number, piece: Piece): void { + GAMES[gameId].puzzle.tiles[pieceIdx] = Util.encodePiece(piece) } function setPuzzleData(gameId: string, data: PuzzleData): void { @@ -214,7 +250,7 @@ function getIdlePlayers(gameId: string, ts: number): Array { return getAllPlayers(gameId).filter((p: Player) => p.ts < minTs && p.points > 0) } -function addPlayer(gameId: string, playerId: string, ts: number): void { +function addPlayer(gameId: string, playerId: string, ts: Timestamp): void { if (!playerExists(gameId, playerId)) { setPlayer(gameId, playerId, __createPlayerObject(playerId, ts)) } else { @@ -261,7 +297,7 @@ function get(gameId: string) { return GAMES[gameId] } -function getTileCount(gameId: string): number { +function getPieceCount(gameId: string): number { return GAMES[gameId].puzzle.tiles.length } @@ -278,22 +314,22 @@ function getScoreMode(gameId: string): ScoreMode { } function isFinished(gameId: string): boolean { - return getFinishedTileCount(gameId) === getTileCount(gameId) + return getFinishedPiecesCount(gameId) === getPieceCount(gameId) } -function getFinishedTileCount(gameId: string): number { +function getFinishedPiecesCount(gameId: string): number { let count = 0 for (let t of GAMES[gameId].puzzle.tiles) { - if (Util.decodeTile(t).owner === -1) { + if (Util.decodePiece(t).owner === -1) { count++ } } return count } -function getTilesSortedByZIndex(gameId: string): Piece[] { - const tiles = GAMES[gameId].puzzle.tiles.map(Util.decodeTile) - return tiles.sort((t1, t2) => t1.z - t2.z) +function getPiecesSortedByZIndex(gameId: string): Piece[] { + const pieces = GAMES[gameId].puzzle.tiles.map(Util.decodePiece) + return pieces.sort((t1, t2) => t1.z - t2.z) } function changePlayer( @@ -320,25 +356,25 @@ function changeData(gameId: string, change: any): void { } } -function changeTile(gameId: string, tileIdx: number, change: any): void { +function changeTile(gameId: string, pieceIdx: number, change: any): void { for (let k of Object.keys(change)) { - const tile = Util.decodeTile(GAMES[gameId].puzzle.tiles[tileIdx]) + const piece = Util.decodePiece(GAMES[gameId].puzzle.tiles[pieceIdx]) // @ts-ignore - tile[k] = change[k] - GAMES[gameId].puzzle.tiles[tileIdx] = Util.encodeTile(tile) + piece[k] = change[k] + GAMES[gameId].puzzle.tiles[pieceIdx] = Util.encodePiece(piece) } } -const getTile = (gameId: string, tileIdx: number): Piece => { - return Util.decodeTile(GAMES[gameId].puzzle.tiles[tileIdx]) +const getPiece = (gameId: string, pieceIdx: number): Piece => { + return Util.decodePiece(GAMES[gameId].puzzle.tiles[pieceIdx]) } -const getTileGroup = (gameId: string, tileIdx: number): number => { - const tile = getTile(gameId, tileIdx) +const getPieceGroup = (gameId: string, tileIdx: number): number => { + const tile = getPiece(gameId, tileIdx) return tile.group } -const getFinalTilePos = (gameId: string, tileIdx: number): Point => { +const getFinalPiecePos = (gameId: string, tileIdx: number): Point => { const info = GAMES[gameId].puzzle.info const boardPos = { x: (info.table.width - info.width) / 2, @@ -348,8 +384,8 @@ const getFinalTilePos = (gameId: string, tileIdx: number): Point => { return Geometry.pointAdd(boardPos, srcPos) } -const getTilePos = (gameId: string, tileIdx: number): Point => { - const tile = getTile(gameId, tileIdx) +const getPiecePos = (gameId: string, tileIdx: number): Point => { + const tile = getPiece(gameId, tileIdx) return tile.pos } @@ -368,9 +404,9 @@ const getBounds = (gameId: string): Rect => { } } -const getTileBounds = (gameId: string, tileIdx: number): Rect => { - const s = getTileSize(gameId) - const tile = getTile(gameId, tileIdx) +const getPieceBounds = (gameId: string, tileIdx: number): Rect => { + const s = getPieceSize(gameId) + const tile = getPiece(gameId, tileIdx) return { x: tile.pos.x, y: tile.pos.y, @@ -380,13 +416,13 @@ const getTileBounds = (gameId: string, tileIdx: number): Rect => { } const getTileZIndex = (gameId: string, tileIdx: number): number => { - const tile = getTile(gameId, tileIdx) + const tile = getPiece(gameId, tileIdx) return tile.z } -const getFirstOwnedTileIdx = (gameId: string, playerId: string): number => { +const getFirstOwnedPieceIdx = (gameId: string, playerId: string): number => { for (let t of GAMES[gameId].puzzle.tiles) { - const tile = Util.decodeTile(t) + const tile = Util.decodePiece(t) if (tile.owner === playerId) { return tile.idx } @@ -394,20 +430,20 @@ const getFirstOwnedTileIdx = (gameId: string, playerId: string): number => { return -1 } -const getFirstOwnedTile = (gameId: string, playerId: string): EncodedPiece|null => { - const idx = getFirstOwnedTileIdx(gameId, playerId) +const getFirstOwnedPiece = (gameId: string, playerId: string): EncodedPiece|null => { + const idx = getFirstOwnedPieceIdx(gameId, playerId) return idx < 0 ? null : GAMES[gameId].puzzle.tiles[idx] } -const getTileDrawOffset = (gameId: string): number => { +const getPieceDrawOffset = (gameId: string): number => { return GAMES[gameId].puzzle.info.tileDrawOffset } -const getTileDrawSize = (gameId: string): number => { +const getPieceDrawSize = (gameId: string): number => { return GAMES[gameId].puzzle.info.tileDrawSize } -const getTileSize = (gameId: string): number => { +const getPieceSize = (gameId: string): number => { return GAMES[gameId].puzzle.info.tileSize } @@ -472,7 +508,7 @@ const setTilesZIndex = (gameId: string, tileIdxs: Array, zIndex: number) } const moveTileDiff = (gameId: string, tileIdx: number, diff: Point): void => { - const oldPos = getTilePos(gameId, tileIdx) + const oldPos = getPiecePos(gameId, tileIdx) const pos = Geometry.pointAdd(oldPos, diff) changeTile(gameId, tileIdx, { pos }) } @@ -482,21 +518,21 @@ const moveTilesDiff = ( tileIdxs: Array, diff: Point ): void => { - const tileDrawSize = getTileDrawSize(gameId) + const drawSize = getPieceDrawSize(gameId) const bounds = getBounds(gameId) const cappedDiff = diff for (let tileIdx of tileIdxs) { - const t = getTile(gameId, tileIdx) + const t = getPiece(gameId, tileIdx) if (t.pos.x + diff.x < bounds.x) { cappedDiff.x = Math.max(bounds.x - t.pos.x, cappedDiff.x) - } else if (t.pos.x + tileDrawSize + diff.x > bounds.x + bounds.w) { - cappedDiff.x = Math.min(bounds.x + bounds.w - t.pos.x + tileDrawSize, cappedDiff.x) + } else if (t.pos.x + drawSize + diff.x > bounds.x + bounds.w) { + cappedDiff.x = Math.min(bounds.x + bounds.w - t.pos.x + drawSize, cappedDiff.x) } if (t.pos.y + diff.y < bounds.y) { cappedDiff.y = Math.max(bounds.y - t.pos.y, cappedDiff.y) - } else if (t.pos.y + tileDrawSize + diff.y > bounds.y + bounds.h) { - cappedDiff.y = Math.min(bounds.y + bounds.h - t.pos.y + tileDrawSize, cappedDiff.y) + } else if (t.pos.y + drawSize + diff.y > bounds.y + bounds.h) { + cappedDiff.y = Math.min(bounds.y + bounds.h - t.pos.y + drawSize, cappedDiff.y) } } @@ -522,52 +558,52 @@ const setTilesOwner = ( } // get all grouped tiles for a tile -function getGroupedTileIdxs(gameId: string, tileIdx: number): number[] { - const tiles = GAMES[gameId].puzzle.tiles - const tile = Util.decodeTile(tiles[tileIdx]) +function getGroupedPieceIdxs(gameId: string, pieceIdx: number): number[] { + const pieces = GAMES[gameId].puzzle.tiles + const piece = Util.decodePiece(pieces[pieceIdx]) const grouped = [] - if (tile.group) { - for (let other of tiles) { - const otherTile = Util.decodeTile(other) - if (otherTile.group === tile.group) { - grouped.push(otherTile.idx) + if (piece.group) { + for (let other of pieces) { + const otherPiece = Util.decodePiece(other) + if (otherPiece.group === piece.group) { + grouped.push(otherPiece.idx) } } } else { - grouped.push(tile.idx) + grouped.push(piece.idx) } return grouped } // Returns the index of the puzzle tile with the highest z index // that is not finished yet and that matches the position -const freeTileIdxByPos = (gameId: string, pos: Point): number => { +const freePieceIdxByPos = (gameId: string, pos: Point): number => { let info = GAMES[gameId].puzzle.info - let tiles = GAMES[gameId].puzzle.tiles + let pieces = GAMES[gameId].puzzle.tiles let maxZ = -1 - let tileIdx = -1 - for (let idx = 0; idx < tiles.length; idx++) { - const tile = Util.decodeTile(tiles[idx]) - if (tile.owner !== 0) { + let pieceIdx = -1 + for (let idx = 0; idx < pieces.length; idx++) { + const piece = Util.decodePiece(pieces[idx]) + if (piece.owner !== 0) { continue } const collisionRect: Rect = { - x: tile.pos.x, - y: tile.pos.y, + x: piece.pos.x, + y: piece.pos.y, w: info.tileSize, h: info.tileSize, } if (Geometry.pointInBounds(pos, collisionRect)) { - if (maxZ === -1 || tile.z > maxZ) { - maxZ = tile.z - tileIdx = idx + if (maxZ === -1 || piece.z > maxZ) { + maxZ = piece.z + pieceIdx = idx } } } - return tileIdx + return pieceIdx } const getPlayerBgColor = (gameId: string, playerId: string): string|null => { @@ -596,8 +632,8 @@ const areGrouped = ( tileIdx1: number, tileIdx2: number ): boolean => { - const g1 = getTileGroup(gameId, tileIdx1) - const g2 = getTileGroup(gameId, tileIdx2) + const g1 = getPieceGroup(gameId, tileIdx1) + const g2 = getPieceGroup(gameId, tileIdx2) return !!(g1 && g1 === g2) } @@ -643,7 +679,7 @@ function handleInput( const _tileChange = (tileIdx: number): void => { changes.push([ Protocol.CHANGE_TILE, - Util.encodeTile(getTile(gameId, tileIdx)), + Util.encodePiece(getPiece(gameId, tileIdx)), ]) } @@ -671,8 +707,8 @@ function handleInput( tileIdx2: number ): void => { const tiles = GAMES[gameId].puzzle.tiles - const group1 = getTileGroup(gameId, tileIdx1) - const group2 = getTileGroup(gameId, tileIdx2) + const group1 = getPieceGroup(gameId, tileIdx1) + const group2 = getPieceGroup(gameId, tileIdx2) let group const searchGroups = [] @@ -701,10 +737,10 @@ function handleInput( // TODO: strange if (searchGroups.length > 0) { for (const t of tiles) { - const tile = Util.decodeTile(t) - if (searchGroups.includes(tile.group)) { - changeTile(gameId, tile.idx, { group }) - _tileChange(tile.idx) + const piece = Util.decodePiece(t) + if (searchGroups.includes(piece.group)) { + changeTile(gameId, piece.idx, { group }) + _tileChange(piece.idx) } } } @@ -732,12 +768,12 @@ function handleInput( _playerChange() evtInfo._last_mouse_down = pos - const tileIdxAtPos = freeTileIdxByPos(gameId, pos) + const tileIdxAtPos = freePieceIdxByPos(gameId, pos) if (tileIdxAtPos >= 0) { let maxZ = getMaxZIndex(gameId) + 1 changeData(gameId, { maxZ }) _dataChange() - const tileIdxs = getGroupedTileIdxs(gameId, tileIdxAtPos) + const tileIdxs = getGroupedPieceIdxs(gameId, tileIdxAtPos) setTilesZIndex(gameId, tileIdxs, getMaxZIndex(gameId)) setTilesOwner(gameId, tileIdxs, playerId) _tileChanges(tileIdxs) @@ -754,7 +790,7 @@ function handleInput( changePlayer(gameId, playerId, {x, y, ts}) _playerChange() } else { - let tileIdx = getFirstOwnedTileIdx(gameId, playerId) + let tileIdx = getFirstOwnedPieceIdx(gameId, playerId) if (tileIdx >= 0) { // player is moving a tile (and hand) changePlayer(gameId, playerId, {x, y, ts}) @@ -762,11 +798,11 @@ function handleInput( // check if pos is on the tile, otherwise dont move // (mouse could be out of table, but tile stays on it) - const tileIdxs = getGroupedTileIdxs(gameId, tileIdx) + const tileIdxs = getGroupedPieceIdxs(gameId, tileIdx) let anyOk = Geometry.pointInBounds(pos, getBounds(gameId)) && Geometry.pointInBounds(evtInfo._last_mouse_down, getBounds(gameId)) for (let idx of tileIdxs) { - const bounds = getTileBounds(gameId, idx) + const bounds = getPieceBounds(gameId, idx) if (Geometry.pointInBounds(pos, bounds)) { anyOk = true break @@ -799,16 +835,16 @@ function handleInput( evtInfo._last_mouse_down = null - let tileIdx = getFirstOwnedTileIdx(gameId, playerId) + let tileIdx = getFirstOwnedPieceIdx(gameId, playerId) if (tileIdx >= 0) { // drop the tile(s) - let tileIdxs = getGroupedTileIdxs(gameId, tileIdx) + let tileIdxs = getGroupedPieceIdxs(gameId, tileIdx) setTilesOwner(gameId, tileIdxs, 0) _tileChanges(tileIdxs) // Check if the tile was dropped near the final location - let tilePos = getTilePos(gameId, tileIdx) - let finalPos = getFinalTilePos(gameId, tileIdx) + let tilePos = getPiecePos(gameId, tileIdx) + let finalPos = getFinalPiecePos(gameId, tileIdx) if (Geometry.pointDistance(finalPos, tilePos) < puzzle.info.snapDistance) { let diff = Geometry.pointSub(finalPos, tilePos) // Snap the tile to the final destination @@ -829,7 +865,7 @@ function handleInput( _playerChange() // check if the puzzle is finished - if (getFinishedTileCount(gameId) === getTileCount(gameId)) { + if (getFinishedPiecesCount(gameId) === getPieceCount(gameId)) { changeData(gameId, { finished: ts }) _dataChange() } @@ -848,17 +884,17 @@ function handleInput( if (areGrouped(gameId, tileIdx, otherTileIdx)) { return false } - const tilePos = getTilePos(gameId, tileIdx) + const tilePos = getPiecePos(gameId, tileIdx) const dstPos = Geometry.pointAdd( - getTilePos(gameId, otherTileIdx), + getPiecePos(gameId, otherTileIdx), {x: off[0] * info.tileSize, y: off[1] * info.tileSize} ) if (Geometry.pointDistance(tilePos, dstPos) < info.snapDistance) { let diff = Geometry.pointSub(dstPos, tilePos) - let tileIdxs = getGroupedTileIdxs(gameId, tileIdx) + let tileIdxs = getGroupedPieceIdxs(gameId, tileIdx) moveTilesDiff(gameId, tileIdxs, diff) groupTiles(gameId, tileIdx, otherTileIdx) - tileIdxs = getGroupedTileIdxs(gameId, tileIdx) + tileIdxs = getGroupedPieceIdxs(gameId, tileIdx) const zIndex = getMaxZIndexByTileIdxs(gameId, tileIdxs) setTilesZIndex(gameId, tileIdxs, zIndex) _tileChanges(tileIdxs) @@ -868,7 +904,7 @@ function handleInput( } let snapped = false - for (let tileIdxTmp of getGroupedTileIdxs(gameId, tileIdx)) { + for (let tileIdxTmp of getGroupedPieceIdxs(gameId, tileIdx)) { let othersIdxs = getSurroundingTilesByIdx(gameId, tileIdxTmp) if ( check(gameId, tileIdxTmp, othersIdxs[0], [0, 1]) // top @@ -916,15 +952,14 @@ function handleInput( } export default { - __createPlayerObject, setGame, exists, playerExists, getActivePlayers, getIdlePlayers, addPlayer, - getFinishedTileCount, - getTileCount, + getFinishedPiecesCount, + getPieceCount, getImageUrl, setImageUrl, get, @@ -936,7 +971,7 @@ export default { getPlayerIdByIndex, changePlayer, setPlayer, - setTile, + setPiece, setPuzzleData, getTableWidth, getTableHeight, @@ -944,11 +979,11 @@ export default { getRng, getPuzzleWidth, getPuzzleHeight, - getTilesSortedByZIndex, - getFirstOwnedTile, - getTileDrawOffset, - getTileDrawSize, - getFinalTilePos, + getPiecesSortedByZIndex, + getFirstOwnedPiece, + getPieceDrawOffset, + getPieceDrawSize, + getFinalPiecePos, getStartTs, getFinishTs, handleInput, diff --git a/src/common/Rng.ts b/src/common/Rng.ts index e18f80c..af72dfc 100644 --- a/src/common/Rng.ts +++ b/src/common/Rng.ts @@ -1,4 +1,4 @@ -interface RngSerialized { +export interface RngSerialized { rand_high: number, rand_low: number, } diff --git a/src/common/Types.ts b/src/common/Types.ts new file mode 100644 index 0000000..279f5f7 --- /dev/null +++ b/src/common/Types.ts @@ -0,0 +1,6 @@ +// @see https://stackoverflow.com/a/59906630/392905 +type ArrayLengthMutationKeys = 'splice' | 'push' | 'pop' | 'shift' | 'unshift' | number +type ArrayItems> = T extends Array ? TItems : never +export type FixedLengthArray = + Pick> + & { [Symbol.iterator]: () => IterableIterator< ArrayItems > } diff --git a/src/common/Util.ts b/src/common/Util.ts index f691b75..c988b9d 100644 --- a/src/common/Util.ts +++ b/src/common/Util.ts @@ -1,4 +1,14 @@ -import { EncodedPiece, EncodedPieceShape, EncodedPlayer, Piece, PieceShape, Player } from './GameCommon' +import { + EncodedGame, + EncodedPiece, + EncodedPieceShape, + EncodedPlayer, + Game, + Piece, + PieceShape, + Player, + ScoreMode +} from './GameCommon' import { Point } from './Geometry' import { Rng } from './Rng' @@ -58,11 +68,11 @@ function decodeShape(data: EncodedPieceShape): PieceShape { } } -function encodeTile(data: Piece): EncodedPiece { +function encodePiece(data: Piece): EncodedPiece { return [data.idx, data.pos.x, data.pos.y, data.z, data.owner, data.group] } -function decodeTile(data: EncodedPiece): Piece { +function decodePiece(data: EncodedPiece): Piece { return { idx: data[0], pos: { @@ -103,25 +113,19 @@ function decodePlayer(data: EncodedPlayer): Player { } } -function encodeGame(data: any): Array { - if (Array.isArray(data)) { - return data - } +function encodeGame(data: Game): EncodedGame { return [ data.id, - data.rng.type, + data.rng.type || '', Rng.serialize(data.rng.obj), data.puzzle, data.players, data.evtInfos, - data.scoreMode, + data.scoreMode || ScoreMode.FINAL, ] } -function decodeGame(data: any) { - if (!Array.isArray(data)) { - return data - } +function decodeGame(data: EncodedGame): Game { return { id: data[0], rng: { @@ -174,8 +178,8 @@ export default { encodeShape, decodeShape, - encodeTile, - decodeTile, + encodePiece, + decodePiece, encodePlayer, decodePlayer, diff --git a/src/frontend/Communication.ts b/src/frontend/Communication.ts index 7b62fff..d84e629 100644 --- a/src/frontend/Communication.ts +++ b/src/frontend/Communication.ts @@ -1,6 +1,7 @@ "use strict" -import { logger } from '../common/Util' +import { EncodedGame, ReplayData } from '../common/GameCommon' +import Util, { logger } from '../common/Util' import Protocol from './../common/Protocol' const log = logger('Communication.js') @@ -51,7 +52,7 @@ function connect( address: string, gameId: string, clientId: string -): Promise { +): Promise { clientSeq = 0 events = {} setConnectionState(CONN_STATE_CONNECTING) @@ -100,9 +101,10 @@ async function requestReplayData( gameId: string, offset: number, size: number -): Promise<{ log: Array, game: any }> { - const res = await fetch(`/api/replay-data?gameId=${gameId}&offset=${offset}&size=${size}`) - const json: { log: Array, game: any } = await res.json() +): Promise { + const args = { gameId, offset, size } + const res = await fetch(`/api/replay-data${Util.asQueryArgs(args)}`) + const json: ReplayData = await res.json() return json } diff --git a/src/frontend/PuzzleGraphics.ts b/src/frontend/PuzzleGraphics.ts index 52f37ad..c4c7716 100644 --- a/src/frontend/PuzzleGraphics.ts +++ b/src/frontend/PuzzleGraphics.ts @@ -94,7 +94,7 @@ async function createPuzzleTileBitmaps( const ctx2 = c2.getContext('2d') as CanvasRenderingContext2D for (const t of tiles) { - const tile = Util.decodeTile(t) + const tile = Util.decodePiece(t) const srcRect = srcRectByIdx(info, tile.idx) const path = pathForShape(Util.decodeShape(info.shapes[tile.idx])) diff --git a/src/frontend/game.ts b/src/frontend/game.ts index cd69b1a..1857908 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -7,11 +7,12 @@ import Debug from './Debug' import Communication from './Communication' import Util from './../common/Util' import PuzzleGraphics from './PuzzleGraphics' -import Game, { Player, Piece } from './../common/GameCommon' +import Game, { Game as GameType, Player, Piece, EncodedGame, ReplayData, Timestamp } from './../common/GameCommon' import fireworksController from './Fireworks' import Protocol from '../common/Protocol' import Time from '../common/Time' import { Dim, Point } from '../common/Geometry' +import { FixedLengthArray } from '../common/Types' declare global { interface Window { @@ -19,13 +20,6 @@ declare global { } } -// @see https://stackoverflow.com/a/59906630/392905 -type ArrayLengthMutationKeys = 'splice' | 'push' | 'pop' | 'shift' | 'unshift' | number -type ArrayItems> = T extends Array ? TItems : never -type FixedLengthArray = - Pick> - & { [Symbol.iterator]: () => IterableIterator< ArrayItems > } - // @ts-ignore const images = import.meta.globEager('./*.png') @@ -50,15 +44,17 @@ interface Hud { interface Replay { final: boolean requesting: boolean - log: Array - logPointer: number, - logIdx: number + log: Array // current log entries + logPointer: number // pointer to current item in the log array speeds: Array speedIdx: number paused: boolean lastRealTs: number lastGameTs: number gameStartTs: number + // + dataOffset: number + dataSize: number } const shouldDrawPiece = (piece: Piece) => { @@ -267,51 +263,61 @@ export async function main( requesting: true, log: [], logPointer: 0, - logIdx: 0, speeds: [0.5, 1, 2, 5, 10, 20, 50, 100, 250, 500], speedIdx: 1, paused: false, lastRealTs: 0, lastGameTs: 0, gameStartTs: 0, + dataOffset: 0, + dataSize: 10000, } Communication.onConnectionStateChange((state) => { HUD.setConnectionState(state) }) + const queryNextReplayBatch = async ( + gameId: string + ): Promise => { + REPLAY.requesting = true + const replay: ReplayData = await Communication.requestReplayData( + gameId, + REPLAY.dataOffset, + REPLAY.dataSize + ) + REPLAY.dataOffset += REPLAY.dataSize + REPLAY.requesting = false + return replay + } + const getNextReplayBatch = async ( - gameId: string, - offset: number, - size: number + gameId: string ) => { - const replay: { - game: any, - log: Array - } = await Communication.requestReplayData(gameId, offset, size) + const replay: ReplayData = await queryNextReplayBatch(gameId) // 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 < 10000) { + if (replay.log.length < REPLAY.dataSize) { REPLAY.final = true } - REPLAY.requesting = false } + let TIME: () => number = () => 0 const connect = async () => { if (MODE === MODE_PLAY) { - const game = await Communication.connect(wsAddress, gameId, clientId) - const gameObject = Util.decodeGame(game) + const game: EncodedGame = await Communication.connect(wsAddress, gameId, clientId) + const gameObject: GameType = Util.decodeGame(game) Game.setGame(gameObject.id, gameObject) TIME = () => Time.timestamp() } else if (MODE === MODE_REPLAY) { - const replay: { - game: any, - log: Array - } = await Communication.requestReplayData(gameId, 0, 10000) - const gameObject = Util.decodeGame(replay.game) + const replay: ReplayData = await queryNextReplayBatch(gameId) + if (!replay.game) { + throw '[ 2021-05-29 no game received ]' + } + const gameObject: GameType = Util.decodeGame(replay.game) Game.setGame(gameObject.id, gameObject) REPLAY.requesting = false REPLAY.log = replay.log @@ -329,8 +335,8 @@ export async function main( await connect() - const TILE_DRAW_OFFSET = Game.getTileDrawOffset(gameId) - const TILE_DRAW_SIZE = Game.getTileDrawSize(gameId) + const PIECE_DRAW_OFFSET = Game.getPieceDrawOffset(gameId) + const PIECE_DRAW_SIZE = Game.getPieceDrawSize(gameId) const PUZZLE_WIDTH = Game.getPuzzleWidth(gameId) const PUZZLE_HEIGHT = Game.getPuzzleHeight(gameId) const TABLE_WIDTH = Game.getTableWidth(gameId) @@ -345,8 +351,8 @@ export async function main( h: PUZZLE_HEIGHT, } const PIECE_DIM = { - w: TILE_DRAW_SIZE, - h: TILE_DRAW_SIZE, + w: PIECE_DRAW_SIZE, + h: PIECE_DRAW_SIZE, } const bitmaps = await PuzzleGraphics.loadPuzzleBitmaps(Game.getPuzzle(gameId)) @@ -380,8 +386,8 @@ export async function main( } updateTimerElements() - HUD.setPiecesDone(Game.getFinishedTileCount(gameId)) - HUD.setPiecesTotal(Game.getTileCount(gameId)) + HUD.setPiecesDone(Game.getFinishedPiecesCount(gameId)) + HUD.setPiecesTotal(Game.getPieceCount(gameId)) const ts = TIME() HUD.setActivePlayers(Game.getActivePlayers(gameId, ts)) HUD.setIdlePlayers(Game.getIdlePlayers(gameId, ts)) @@ -470,8 +476,8 @@ export async function main( } } break; case Protocol.CHANGE_TILE: { - const t = Util.decodeTile(changeData) - Game.setTile(gameId, t.idx, t) + const t = Util.decodePiece(changeData) + Game.setPiece(gameId, t.idx, t) RERENDER = true } break; case Protocol.CHANGE_DATA: { @@ -494,8 +500,7 @@ export async function main( if (REPLAY.logPointer + 1 >= REPLAY.log.length) { REPLAY.lastRealTs = realTs - REPLAY.requesting = true - getNextReplayBatch(gameId, REPLAY.logIdx, 10000) + getNextReplayBatch(gameId) return } @@ -519,7 +524,7 @@ export async function main( } const logEntry = REPLAY.log[nextIdx] - const nextTs = REPLAY.gameStartTs + logEntry[logEntry.length - 1] + const nextTs: Timestamp = REPLAY.gameStartTs + logEntry[logEntry.length - 1] if (nextTs > maxGameTs) { break } @@ -546,7 +551,6 @@ export async function main( RERENDER = true } REPLAY.logPointer = nextIdx - REPLAY.logIdx++ } while (true) REPLAY.lastRealTs = realTs REPLAY.lastGameTs = maxGameTs @@ -572,7 +576,7 @@ export async function main( RERENDER = true viewport.move(diffX, diffY) } else if (type === Protocol.INPUT_EV_MOUSE_MOVE) { - if (_last_mouse_down && !Game.getFirstOwnedTile(gameId, clientId)) { + if (_last_mouse_down && !Game.getFirstOwnedPiece(gameId, clientId)) { // move the cam const pos = { x: evt[1], y: evt[2] } const mouse = viewport.worldToViewport(pos) @@ -692,7 +696,7 @@ export async function main( // DRAW TILES // --------------------------------------------------------------- - const tiles = Game.getTilesSortedByZIndex(gameId) + const tiles = Game.getPiecesSortedByZIndex(gameId) if (window.DEBUG) Debug.checkpoint('get tiles done') dim = viewport.worldDimToViewportRaw(PIECE_DIM) @@ -702,8 +706,8 @@ export async function main( } bmp = bitmaps[tile.idx] pos = viewport.worldToViewportRaw({ - x: TILE_DRAW_OFFSET + tile.pos.x, - y: TILE_DRAW_OFFSET + tile.pos.y, + x: PIECE_DRAW_OFFSET + tile.pos.x, + y: PIECE_DRAW_OFFSET + tile.pos.y, }) ctx.drawImage(bmp, 0, 0, bmp.width, bmp.height, @@ -743,7 +747,7 @@ export async function main( // --------------------------------------------------------------- HUD.setActivePlayers(Game.getActivePlayers(gameId, ts)) HUD.setIdlePlayers(Game.getIdlePlayers(gameId, ts)) - HUD.setPiecesDone(Game.getFinishedTileCount(gameId)) + HUD.setPiecesDone(Game.getFinishedPiecesCount(gameId)) if (window.DEBUG) Debug.checkpoint('HUD done') // --------------------------------------------------------------- diff --git a/src/server/Game.ts b/src/server/Game.ts index e6cd689..50c17aa 100644 --- a/src/server/Game.ts +++ b/src/server/Game.ts @@ -1,18 +1,18 @@ -import GameCommon, { ScoreMode } from './../common/GameCommon' +import GameCommon, { Game, ScoreMode, Timestamp } from './../common/GameCommon' import Util from './../common/Util' import { Rng } from './../common/Rng' import GameLog from './GameLog' -import { createPuzzle } from './Puzzle' +import { createPuzzle, PuzzleCreationImageInfo } from './Puzzle' import Protocol from './../common/Protocol' import GameStorage from './GameStorage' async function createGameObject( gameId: string, targetTiles: number, - image: { file: string, url: string }, + image: PuzzleCreationImageInfo, ts: number, scoreMode: ScoreMode -) { +): Promise { const seed = Util.hash(gameId + ' ' + ts) const rng = new Rng(seed) return { @@ -28,11 +28,17 @@ async function createGameObject( async function createGame( gameId: string, targetTiles: number, - image: { file: string, url: string }, + image: PuzzleCreationImageInfo, ts: number, scoreMode: ScoreMode ): Promise { - const gameObject = await createGameObject(gameId, targetTiles, image, ts, scoreMode) + const gameObject = await createGameObject( + gameId, + targetTiles, + image, + ts, + scoreMode + ) GameLog.create(gameId) GameLog.log(gameId, Protocol.LOG_HEADER, 1, targetTiles, image, ts, scoreMode) @@ -41,7 +47,7 @@ async function createGame( GameStorage.setDirty(gameId) } -function addPlayer(gameId: string, playerId: string, ts: number): void { +function addPlayer(gameId: string, playerId: string, ts: Timestamp): void { const idx = GameCommon.getPlayerIndexById(gameId, playerId) const diff = ts - GameCommon.getStartTs(gameId) if (idx === -1) { @@ -74,14 +80,4 @@ export default { createGame, addPlayer, handleInput, - getAllGames: GameCommon.getAllGames, - getActivePlayers: GameCommon.getActivePlayers, - getFinishedTileCount: GameCommon.getFinishedTileCount, - getImageUrl: GameCommon.getImageUrl, - getTileCount: GameCommon.getTileCount, - exists: GameCommon.exists, - playerExists: GameCommon.playerExists, - get: GameCommon.get, - getStartTs: GameCommon.getStartTs, - getFinishTs: GameCommon.getFinishTs, } diff --git a/src/server/GameLog.ts b/src/server/GameLog.ts index 7fb614a..ca1d306 100644 --- a/src/server/GameLog.ts +++ b/src/server/GameLog.ts @@ -8,19 +8,19 @@ const log = logger('GameLog.js') const filename = (gameId: string) => `${DATA_DIR}/log_${gameId}.log` -const create = (gameId: string) => { +const create = (gameId: string): void => { const file = filename(gameId) if (!fs.existsSync(file)) { fs.appendFileSync(file, '') } } -const exists = (gameId: string) => { +const exists = (gameId: string): boolean => { const file = filename(gameId) return fs.existsSync(file) } -const _log = (gameId: string, ...args: Array) => { +const _log = (gameId: string, ...args: Array): void => { const file = filename(gameId) if (!fs.existsSync(file)) { return diff --git a/src/server/GameStorage.ts b/src/server/GameStorage.ts index 91a50eb..52357c8 100644 --- a/src/server/GameStorage.ts +++ b/src/server/GameStorage.ts @@ -41,7 +41,7 @@ function loadGame(gameId: string): void { } if (typeof game.puzzle.data.finished === 'undefined') { const unfinished = game.puzzle.tiles - .map(Util.decodeTile) + .map(Util.decodePiece) .find((t: Piece) => t.owner !== -1) game.puzzle.data.finished = unfinished ? 0 : Time.timestamp() } diff --git a/src/server/Puzzle.ts b/src/server/Puzzle.ts index d3eb518..e800977 100644 --- a/src/server/Puzzle.ts +++ b/src/server/Puzzle.ts @@ -2,7 +2,12 @@ import Util from './../common/Util' import { Rng } from './../common/Rng' import Images from './Images' import { EncodedPiece, EncodedPieceShape, PieceShape, Puzzle } from '../common/GameCommon' -import { Point } from '../common/Geometry' +import { Dim, Point } from '../common/Geometry' + +export interface PuzzleCreationImageInfo { + file: string + url: string +} interface PuzzleCreationInfo { width: number @@ -22,7 +27,7 @@ const TILE_SIZE = 64 async function createPuzzle( rng: Rng, targetTiles: number, - image: { file: string, url: string }, + image: PuzzleCreationImageInfo, ts: number ): Promise { const imagePath = image.file @@ -33,11 +38,7 @@ async function createPuzzle( if (!dim.w || !dim.h) { throw `[ 2021-05-16 invalid dimension for path ${imagePath} ]` } - const info: PuzzleCreationInfo = determinePuzzleInfo( - dim.w, - dim.h, - targetTiles - ) + const info: PuzzleCreationInfo = determinePuzzleInfo(dim, targetTiles) let tiles = new Array(info.tiles) for (let i = 0; i < tiles.length; i++) { @@ -98,7 +99,7 @@ async function createPuzzle( positions = rng.shuffle(positions) const pieces: Array = tiles.map(tile => { - return Util.encodeTile({ + return Util.encodePiece({ idx: tile.idx, // index of tile in the array group: 0, // if grouped with other tiles z: 0, // z index of the tile @@ -181,9 +182,12 @@ function determinePuzzleTileShapes( return shapes.map(Util.encodeShape) } -const determineTilesXY = (w: number, h: number, targetTiles: number) => { - const w_ = w < h ? (w * h) : (w * w) - const h_ = w < h ? (h * h) : (w * h) +const determineTilesXY = ( + dim: Dim, + targetTiles: number +): { tilesX: number, tilesY: number } => { + const w_ = dim.w < dim.h ? (dim.w * dim.h) : (dim.w * dim.w) + const h_ = dim.w < dim.h ? (dim.h * dim.h) : (dim.w * dim.h) let size = 0 let tiles = 0 do { @@ -198,11 +202,10 @@ const determineTilesXY = (w: number, h: number, targetTiles: number) => { } const determinePuzzleInfo = ( - w: number, - h: number, + dim: Dim, targetTiles: number ): PuzzleCreationInfo => { - const {tilesX, tilesY} = determineTilesXY(w, h, targetTiles) + const {tilesX, tilesY} = determineTilesXY(dim, targetTiles) const tiles = tilesX * tilesY const tileSize = TILE_SIZE const width = tilesX * tileSize diff --git a/src/server/main.ts b/src/server/main.ts index a25ece8..df2e599 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -19,7 +19,7 @@ import { PUBLIC_DIR, UPLOAD_DIR, } from './Dirs' -import { GameSettings, ScoreMode } from '../common/GameCommon' +import GameCommon, { Game as GameType, GameSettings, ScoreMode } from '../common/GameCommon' import GameStorage from './GameStorage' import Db from './Db' @@ -81,7 +81,7 @@ app.get('/api/replay-data', async (req, res) => { return } const log = await GameLog.get(gameId, offset, size) - let game = null + let game: GameType|null = null if (offset === 0) { // also need the game game = await Game.createGameObject( @@ -107,15 +107,15 @@ app.get('/api/newgame-data', (req, res) => { app.get('/api/index-data', (req, res) => { const ts = Time.timestamp() const games = [ - ...Game.getAllGames().map((game: any) => ({ + ...GameCommon.getAllGames().map((game: any) => ({ id: game.id, hasReplay: GameLog.exists(game.id), - started: Game.getStartTs(game.id), - finished: Game.getFinishTs(game.id), - tilesFinished: Game.getFinishedTileCount(game.id), - tilesTotal: Game.getTileCount(game.id), - players: Game.getActivePlayers(game.id, ts).length, - imageUrl: Game.getImageUrl(game.id), + started: GameCommon.getStartTs(game.id), + finished: GameCommon.getFinishTs(game.id), + tilesFinished: GameCommon.getFinishedPiecesCount(game.id), + tilesTotal: GameCommon.getPieceCount(game.id), + players: GameCommon.getActivePlayers(game.id, ts).length, + imageUrl: GameCommon.getImageUrl(game.id), })), ] @@ -193,7 +193,7 @@ app.post('/newgame', bodyParser.json(), async (req, res) => { const gameSettings = req.body as GameSettings log.log(gameSettings) const gameId = Util.uniqId() - if (!Game.exists(gameId)) { + if (!GameCommon.exists(gameId)) { const ts = Time.timestamp() await Game.createGame( gameId, @@ -238,13 +238,13 @@ wss.on('message', async ({socket, data} : { socket: WebSocket, data: any }) => { const msgType = msg[0] switch (msgType) { case Protocol.EV_CLIENT_INIT: { - if (!Game.exists(gameId)) { + if (!GameCommon.exists(gameId)) { throw `[game ${gameId} does not exist... ]` } const ts = Time.timestamp() Game.addPlayer(gameId, clientId, ts) GameSockets.addSocket(gameId, socket) - const game = Game.get(gameId) + const game: GameType = GameCommon.get(gameId) notify( [Protocol.EV_SERVER_INIT, Util.encodeGame(game)], [socket] @@ -252,7 +252,7 @@ wss.on('message', async ({socket, data} : { socket: WebSocket, data: any }) => { } break case Protocol.EV_CLIENT_EVENT: { - if (!Game.exists(gameId)) { + if (!GameCommon.exists(gameId)) { throw `[game ${gameId} does not exist... ]` } const clientSeq = msg[1] @@ -260,7 +260,7 @@ wss.on('message', async ({socket, data} : { socket: WebSocket, data: any }) => { const ts = Time.timestamp() let sendGame = false - if (!Game.playerExists(gameId, clientId)) { + if (!GameCommon.playerExists(gameId, clientId)) { Game.addPlayer(gameId, clientId, ts) sendGame = true } @@ -269,7 +269,7 @@ wss.on('message', async ({socket, data} : { socket: WebSocket, data: any }) => { sendGame = true } if (sendGame) { - const game = Game.get(gameId) + const game: GameType = GameCommon.get(gameId) notify( [Protocol.EV_SERVER_INIT, Util.encodeGame(game)], [socket] From d4f02c10df2ad5eb1dc1c2624e140cfa017912b9 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 29 May 2021 17:58:05 +0200 Subject: [PATCH 09/78] add linting, do more type hinting --- .eslintignore | 3 + .eslintrc.cjs | 17 + build/public/assets/index.643c957c.js | 1 - build/public/assets/index.ae0a2617.js | 1 + build/public/index.html | 2 +- build/server/main.js | 1885 +++++++++++---------- package-lock.json | 1845 +++++++++++++++++++- package.json | 9 +- rollup.server.config.js | 21 +- scripts/lint | 5 + scripts/tests | 2 +- src/common/GameCommon.ts | 377 ++--- src/common/Rng.ts | 2 +- src/common/Types.ts | 193 +++ src/common/Util.ts | 26 +- src/frontend/Communication.ts | 26 +- src/frontend/Debug.ts | 4 +- src/frontend/Fireworks.ts | 17 +- src/frontend/PuzzleGraphics.ts | 40 +- src/frontend/components/NewGameDialog.vue | 2 +- src/frontend/game.ts | 26 +- src/frontend/gameloop.ts | 6 +- src/server/Db.ts | 11 +- src/server/Game.ts | 13 +- src/server/GameStorage.ts | 18 +- src/server/Images.ts | 79 +- src/server/Puzzle.ts | 28 +- src/server/WebSocketServer.ts | 18 +- src/server/main.ts | 30 +- 29 files changed, 3353 insertions(+), 1354 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc.cjs delete mode 100644 build/public/assets/index.643c957c.js create mode 100644 build/public/assets/index.ae0a2617.js create mode 100755 scripts/lint diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..6d31f6f --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +node_modules +build +src/frontend/shims-vue.d.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..2080395 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,17 @@ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + plugins: [ + '@typescript-eslint', + ], + parserOptions: { + ecmaVersion: 2020, + }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + ], + rules: { + '@typescript-eslint/no-inferrable-types': 'off' + } +}; diff --git a/build/public/assets/index.643c957c.js b/build/public/assets/index.643c957c.js deleted file mode 100644 index 77b35d4..0000000 --- a/build/public/assets/index.643c957c.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as n,w as l,b as i,r as o,o as a,e as s,t as r,F as d,f as c,g as u,h as g,v as p,i as h,j as m,k as y,l as f,m as w,n as v,p as b,q as x,s as C}from"./vendor.b622ee49.js";var k=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const A={id:"app"},z={key:0,class:"nav"},S=s("Index"),P=s("New game");k.render=function(e,s,r,d,c,u){const g=o("router-link"),p=o("router-view");return a(),t("div",A,[e.showNav?(a(),t("ul",z,[n("li",null,[n(g,{class:"btn",to:{name:"index"}},{default:l((()=>[S])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:l((()=>[P])),_:1})])])):i("",!0),n(p)])};const I=864e5,T=e=>{const t=Math.floor(e/I);e%=I;const n=Math.floor(e/36e5);e%=36e5;const l=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${l}m ${Math.floor(e/1e3)}s`};var _=1e3,D=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>T(t-e),E=T,O=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",l=e,i=t||D();return`${n} ${B(l,i)}`}}});const U={class:"game-info-text"},N=n("br",null,null,-1),M=n("br",null,null,-1),G=n("br",null,null,-1),$=s(" ↪️ Watch replay ");O.render=function(e,d,c,u,g,p){const h=o("router-link");return a(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:l((()=>[n("span",U,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),N,s(" 👥 "+r(e.game.players),1),M,s(" "+r(e.time(e.game.started,e.game.finished)),1),G])])),_:1},8,["to"]),e.game.hasReplay?(a(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:l((()=>[$])),_:1},8,["to"])):i("",!0)],4)};var R=e({components:{GameTeaser:O},data:()=>({gamesRunning:[],gamesFinished:[]}),async created(){const e=await fetch("/api/index-data"),t=await e.json();this.gamesRunning=t.gamesRunning,this.gamesFinished=t.gamesFinished}});const V=n("h1",null,"Running games",-1),j=n("h1",null,"Finished games",-1);R.render=function(e,l,i,s,r,u){const g=o("game-teaser");return a(),t("div",null,[V,(a(!0),t(d,null,c(e.gamesRunning,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128)),j,(a(!0),t(d,null,c(e.gamesFinished,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128))])};var F=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});F.render=function(e,l,i,o,s,r){return a(),t("div",{class:"imageteaser",style:e.style,onClick:l[2]||(l[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:l[1]||(l[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var L=e({name:"image-library",components:{ImageTeaser:F},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});L.render=function(e,n,l,i,s,r){const u=o("image-teaser");return a(),t("div",null,[(a(!0),t(d,null,c(e.images,((n,l)=>(a(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:l},null,8,["image","onClick","onEditClick"])))),128))])};const W={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};W.render=function(e,n,l,i,o,s){return a(),t("div",{style:s.style,title:l.title},null,12,["title"])};var q=e({name:"tags-input",props:{modelValue:{type:Array,required:!0}},emits:{"update:modelValue":null},data:()=>({input:"",values:[]}),created(){this.values=this.modelValue},methods:{onKeyUp(e){if(","===e.key)return this.add(),e.stopPropagation(),!1},add(){const e=this.input.replace(/,/g,"").trim();e&&(this.values.includes(e)||this.values.push(e),this.input="",this.$emit("update:modelValue",this.values))},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const H=m()(((e,l,i,o,s,u)=>(a(),t("div",null,[g(n("input",{class:"input",type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.input=t),placeholder:"Plants, People",onKeydown:l[2]||(l[2]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:l[3]||(l[3]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[p,e.input]]),(a(!0),t(d,null,c(e.values,((n,l)=>(a(),t("span",{key:l,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));q.render=H,q.__scopeId="data-v-771460ae";var Q=e({name:"new-image-dialog",components:{ResponsiveImage:W,TagsInput:q},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const l=new FileReader;l.readAsDataURL(n),l.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const Y={key:0,class:"has-image"},K={key:1},Z={class:"upload"},J=n("span",{class:"btn"},"Upload File",-1),X={class:"area-settings"},ee=n("td",null,[n("label",null,"Title")],-1),te=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ne=n("td",null,[n("label",null,"Tags")],-1),le={class:"area-buttons"},ie=s("🧩 Post to gallery "),oe=n("br",null,null,-1),ae=s(" + set up game");Q.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay new-image-dialog",onClick:l[8]||(l[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[7]||(l[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(a(),t("div",Y,[n("span",{class:"remove btn",onClick:l[1]||(l[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(a(),t("div",K,[n("label",Z,[n("input",{type:"file",style:{display:"none"},onChange:l[2]||(l[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),J])]))],2),n("div",X,[n("table",null,[n("tr",null,[ee,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[3]||(l[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),te,n("tr",null,[ne,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[4]||(l[4]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",le,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:l[5]||(l[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:l[6]||(l[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[ie,oe,ae],8,["disabled"])])])])};var se=e({name:"edit-image-dialog",components:{ResponsiveImage:W,TagsInput:q},props:{image:{type:Object,required:!0}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const re={class:"area-image"},de={class:"has-image"},ce={class:"area-settings"},ue=n("td",null,[n("label",null,"Title")],-1),ge=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),pe=n("td",null,[n("label",null,"Tags")],-1),he={class:"area-buttons"};function me(e,t){const n=e.x-t.x,l=e.y-t.y;return Math.sqrt(n*n+l*l)}function ye(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}se.render=function(e,l,i,s,r,d){const c=o("responsive-image"),h=o("tags-input");return a(),t("div",{class:"overlay edit-image-dialog",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("div",re,[n("div",de,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ce,[n("table",null,[n("tr",null,[ue,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ge,n("tr",null,[pe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[2]||(l[2]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",he,[n("button",{class:"btn",onClick:l[3]||(l[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])};var fe={pointSub:function(e,t){return{x:e.x-t.x,y:e.y-t.y}},pointAdd:function(e,t){return{x:e.x+t.x,y:e.y+t.y}},pointDistance:me,pointInBounds:function(e,t){return e.x>=t.x&&e.x<=t.x+t.w&&e.y>=t.y&&e.y<=t.y+t.h},rectCenter:ye,rectMoved:function(e,t,n){return{x:e.x+t,y:e.y+n,w:e.w,h:e.h}},rectCenterDistance:function(e,t){return me(ye(e),ye(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};var we=1,ve=4,be=2,xe=3,Ce=2,ke=4,Ae=3,ze=9,Se=1,Pe=2,Ie=3,Te=4,_e=5,De=6,Be=7,Ee=8,Oe=10,Ue=1,Ne=2,Me=3;class Ge{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){return this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295,e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),l=t[n];t[n]=t[e],t[e]=l}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new Ge(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const $e=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},Re=(...e)=>{const t=t=>(...n)=>{const l=new Date,i=$e(l.getHours(),"00"),o=$e(l.getMinutes(),"00"),a=$e(l.getSeconds(),"00");console[t](`${i}:${o}:${a}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var Ve,je,Fe,Le,We={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",Ge.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Fe.FINAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:Ge.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}},coordByTileIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(let n in e){const l=[n,e[n]].map(encodeURIComponent);t.push(l.join("="))}return 0===t.length?"":`?${t.join("&")}`}};(je=Ve||(Ve={}))[je.Flat=0]="Flat",je[je.Out=1]="Out",je[je.In=-1]="In",(Le=Fe||(Fe={}))[Le.FINAL=0]="FINAL",Le[Le.ANY=1]="ANY";const qe={};function He(e,t){let n=0;for(let l of qe[e].players){if(We.decodePlayer(l).id===t)return n;n++}return-1}function Qe(e,t){const n=He(e,t);return-1===n?null:We.decodePlayer(qe[e].players[n])}function Ye(e,t,n){const l=He(e,t);-1===l?qe[e].players.push(We.encodePlayer(n)):qe[e].players[l]=We.encodePlayer(n)}function Ke(e,t){return-1!==He(e,t)}function Ze(e){return qe[e]?qe[e].players.map(We.decodePlayer):[]}function Je(e){return qe[e].puzzle.tiles.length}function Xe(e){return qe[e].scoreMode||0}function et(e){return tt(e)===Je(e)}function tt(e){let t=0;for(let n of qe[e].puzzle.tiles)-1===We.decodePiece(n).owner&&t++;return t}function nt(e,t,n){const l=Qe(e,t);if(null!==l){for(let e of Object.keys(n))l[e]=n[e];Ye(e,t,l)}}function lt(e,t){for(let n of Object.keys(t))qe[e].puzzle.data[n]=t[n]}function it(e,t,n){for(let l of Object.keys(n)){const i=We.decodePiece(qe[e].puzzle.tiles[t]);i[l]=n[l],qe[e].puzzle.tiles[t]=We.encodePiece(i)}}const ot=(e,t)=>We.decodePiece(qe[e].puzzle.tiles[t]),at=(e,t)=>ot(e,t).group,st=(e,t)=>{const n=qe[e].puzzle.info,l={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},i=function(e,t){const n=qe[e].puzzle.info,l=We.coordByTileIdx(n,t),i=l.x*n.tileSize,o=l.y*n.tileSize;return{x:i,y:o}}(e,t);return fe.pointAdd(l,i)},rt=(e,t)=>ot(e,t).pos,dt=e=>{const t=At(e),n=zt(e),l=Math.round(t/4),i=Math.round(n/4);return{x:0-l,y:0-i,w:t+2*l,h:n+2*i}},ct=(e,t)=>{const n=ht(e),l=ot(e,t);return{x:l.pos.x,y:l.pos.y,w:n,h:n}},ut=(e,t)=>ot(e,t).z,gt=(e,t)=>{for(let n of qe[e].puzzle.tiles){const e=We.decodePiece(n);if(e.owner===t)return e.idx}return-1},pt=e=>qe[e].puzzle.info.tileDrawSize,ht=e=>qe[e].puzzle.info.tileSize,mt=e=>qe[e].puzzle.data.maxGroup,yt=e=>qe[e].puzzle.data.maxZ;function ft(e,t){const n=qe[e].puzzle.info,l=We.coordByTileIdx(n,t);return[l.y>0?t-n.tilesX:-1,l.x0?t-1:-1]}const wt=(e,t,n)=>{for(let l of t)it(e,l,{z:n})},vt=(e,t,n)=>{const l=rt(e,t);it(e,t,{pos:fe.pointAdd(l,n)})},bt=(e,t,n)=>{const l=pt(e),i=dt(e),o=n;for(let a of t){const t=ot(e,a);t.pos.x+n.xi.x+i.w&&(o.x=Math.min(i.x+i.w-t.pos.x+l,o.x)),t.pos.y+n.yi.y+i.h&&(o.y=Math.min(i.y+i.h-t.pos.y+l,o.y))}for(let a of t)vt(e,a,o)},xt=(e,t,n)=>{for(let l of t)it(e,l,{owner:n})};function Ct(e,t){const n=qe[e].puzzle.tiles,l=We.decodePiece(n[t]),i=[];if(l.group)for(let o of n){const e=We.decodePiece(o);e.group===l.group&&i.push(e.idx)}else i.push(l.idx);return i}const kt=(e,t)=>{const n=Qe(e,t);return n?n.points:0},At=e=>qe[e].puzzle.info.table.width,zt=e=>qe[e].puzzle.info.table.height;var St={setGame:function(e,t){qe[e]=t},exists:function(e){return!!qe[e]||!1},playerExists:Ke,getActivePlayers:function(e,t){const n=t-30*_;return Ze(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return Ze(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Ke(e,t)?nt(e,t,{ts:n}):Ye(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:tt,getPieceCount:Je,getImageUrl:function(e){return qe[e].puzzle.info.imageUrl},setImageUrl:function(e,t){qe[e].puzzle.info.imageUrl=t},get:function(e){return qe[e]},getAllGames:function(){return Object.values(qe).sort(((e,t)=>et(e.id)===et(t.id)?t.puzzle.data.started-e.puzzle.data.started:et(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Qe(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Qe(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Qe(e,t);return n?n.name:null},getPlayerIndexById:He,getPlayerIdByIndex:function(e,t){return qe[e].players.length>t?We.decodePlayer(qe[e].players[t]).id:null},changePlayer:nt,setPlayer:Ye,setPiece:function(e,t,n){qe[e].puzzle.tiles[t]=We.encodePiece(n)},setPuzzleData:function(e,t){qe[e].puzzle.data=t},getTableWidth:At,getTableHeight:zt,getPuzzle:e=>qe[e].puzzle,getRng:e=>qe[e].rng.obj,getPuzzleWidth:e=>qe[e].puzzle.info.width,getPuzzleHeight:e=>qe[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return qe[e].puzzle.tiles.map(We.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=gt(e,t);return n<0?null:qe[e].puzzle.tiles[n]},getPieceDrawOffset:e=>qe[e].puzzle.info.tileDrawOffset,getPieceDrawSize:pt,getFinalPiecePos:st,getStartTs:e=>qe[e].puzzle.data.started,getFinishTs:e=>qe[e].puzzle.data.finished,handleInput:function(e,t,n,l){const i=qe[e].puzzle,o=function(e,t){return t in qe[e].evtInfos?qe[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),a=[],s=()=>{a.push([Ue,i.data])},r=t=>{a.push([Ne,We.encodePiece(ot(e,t))])},d=e=>{for(const t of e)r(t)},c=()=>{const n=Qe(e,t);n&&a.push([Me,We.encodePlayer(n)])},u=n[0];if(u===De){const i=n[1];nt(e,t,{bgcolor:i,ts:l}),c()}else if(u===Be){const i=n[1];nt(e,t,{color:i,ts:l}),c()}else if(u===Ee){const i=`${n[1]}`.substr(0,16);nt(e,t,{name:i,ts:l}),c()}else if(u===Se){const i={x:n[1],y:n[2]};nt(e,t,{d:1,ts:l}),c(),o._last_mouse_down=i;const a=((e,t)=>{let n=qe[e].puzzle.info,l=qe[e].puzzle.tiles,i=-1,o=-1;for(let a=0;ai)&&(i=e.z,o=a)}return o})(e,i);if(a>=0){let n=yt(e)+1;lt(e,{maxZ:n}),s();const l=Ct(e,a);wt(e,l,yt(e)),xt(e,l,t),d(l)}o._last_mouse=i}else if(u===Ie){const i=n[1],a=n[2],s={x:i,y:a};if(null===o._last_mouse_down)nt(e,t,{x:i,y:a,ts:l}),c();else{let n=gt(e,t);if(n>=0){nt(e,t,{x:i,y:a,ts:l}),c();const r=Ct(e,n);let u=fe.pointInBounds(s,dt(e))&&fe.pointInBounds(o._last_mouse_down,dt(e));for(let t of r){const n=ct(e,t);if(fe.pointInBounds(s,n)){u=!0;break}}if(u){const t=i-o._last_mouse_down.x,n=a-o._last_mouse_down.y;bt(e,r,{x:t,y:n}),d(r)}}else nt(e,t,{ts:l}),c();o._last_mouse_down=s}o._last_mouse=s}else if(u===Pe){const a={x:n[1],y:n[2]},u=0;o._last_mouse_down=null;let g=gt(e,t);if(g>=0){let n=Ct(e,g);xt(e,n,0),d(n);let o=rt(e,g),a=st(e,g);if(fe.pointDistance(a,o){for(let n of t)it(e,n,{owner:-1,z:1})})(e,n),d(n);let r=kt(e,t);0===Xe(e)?r+=n.length:1===Xe(e)&&(r+=1),nt(e,t,{d:u,ts:l,points:r}),c(),tt(e)===Je(e)&&(lt(e,{finished:l}),s())}else{const n=(e,t,n,l)=>{let i=qe[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const l=at(e,t),i=at(e,n);return!(!l||l!==i)})(e,t,n))return!1;const o=rt(e,t),a=fe.pointAdd(rt(e,n),{x:l[0]*i.tileSize,y:l[1]*i.tileSize});if(fe.pointDistance(o,a){const l=qe[e].puzzle.tiles,i=at(e,t),o=at(e,n);let a;const d=[];i&&d.push(i),o&&d.push(o),i?a=i:o?a=o:(lt(e,{maxGroup:mt(e)+1}),s(),a=mt(e));if(it(e,t,{group:a}),r(t),it(e,n,{group:a}),r(n),d.length>0)for(const s of l){const t=We.decodePiece(s);d.includes(t.group)&&(it(e,t.idx,{group:a}),r(t.idx))}})(e,t,n),i=Ct(e,t);const c=((e,t)=>{let n=0;for(let l of t){let t=ut(e,l);t>n&&(n=t)}return n})(e,i);return wt(e,i,c),d(i),!0}return!1};let i=!1;for(let t of Ct(e,g)){let l=ft(e,t);if(n(e,t,l[0],[0,1])||n(e,t,l[1],[-1,0])||n(e,t,l[2],[0,-1])||n(e,t,l[3],[1,0])){i=!0;break}}if(i&&1===Xe(e)){const n=kt(e,t)+1;nt(e,t,{d:u,ts:l,points:n}),c()}else nt(e,t,{d:u,ts:l}),c()}}else nt(e,t,{d:u,ts:l}),c();o._last_mouse=a}else if(u===Te){const i=n[1],a=n[2];nt(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else if(u===_e){const i=n[1],a=n[2];nt(e,t,{x:i,y:a,ts:l}),c(),o._last_mouse={x:i,y:a}}else nt(e,t,{ts:l}),c();return function(e,t,n){qe[e].evtInfos[t]=n}(e,t,o),a}},Pt=e({name:"new-game-dialog",components:{ResponsiveImage:W},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Fe.ANY}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const It={class:"area-image"},Tt={class:"has-image"},_t={class:"area-settings"},Dt=n("td",null,[n("label",null,"Pieces")],-1),Bt=n("td",null,[n("label",null,"Scoring: ")],-1),Et=s(" Any (Score when pieces are connected to each other or on final location)"),Ot=n("br",null,null,-1),Ut=s(" Final (Score when pieces are put to their final location)"),Nt={class:"area-buttons"};Pt.render=function(e,l,i,s,r,d){const c=o("responsive-image");return a(),t("div",{class:"overlay new-game-dialog",onClick:l[6]||(l[6]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[5]||(l[5]=u((()=>{}),["stop"]))},[n("div",It,[n("div",Tt,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",_t,[n("table",null,[n("tr",null,[Dt,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Bt,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[2]||(l[2]=t=>e.scoreMode=t),value:"1"},null,512),[[y,e.scoreMode]]),Et]),Ot,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[3]||(l[3]=t=>e.scoreMode=t),value:"0"},null,512),[[y,e.scoreMode]]),Ut])])])])]),n("div",Nt,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:l[4]||(l[4]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};var Mt=e({components:{ImageLibrary:L,NewImageDialog:Q,EditImageDialog:se,NewGameDialog:Pt},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},methods:{toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${We.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const Gt={class:"upload-image-teaser"},$t=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),Rt={key:0},Vt=s(" Tags: "),jt=s(" Sort by: "),Ft=n("option",{value:"date_desc"},"Newest first",-1),Lt=n("option",{value:"date_asc"},"Oldest first",-1),Wt=n("option",{value:"alpha_asc"},"A-Z",-1),qt=n("option",{value:"alpha_desc"},"Z-A",-1);Mt.render=function(e,l,s,u,p,h){const m=o("image-library"),y=o("new-image-dialog"),w=o("edit-image-dialog"),v=o("new-game-dialog");return a(),t("div",null,[n("div",Gt,[n("div",{class:"btn btn-big",onClick:l[1]||(l[1]=t=>e.dialog="new-image")},"Upload your image"),$t]),n("div",null,[e.tags.length>0?(a(),t("label",Rt,[Vt,(a(!0),t(d,null,c(e.tags,((n,l)=>(a(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:l,onClick:t=>e.toggleTag(n)},r(n.title),11,["onClick"])))),128))])):i("",!0),n("label",null,[jt,g(n("select",{"onUpdate:modelValue":l[2]||(l[2]=t=>e.filters.sort=t),onChange:l[3]||(l[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[Ft,Lt,Wt,qt],544),[[f,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(a(),t(y,{key:0,onBgclick:l[4]||(l[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["onPostToGalleryClick","onSetupGameClick"])):i("",!0),"edit-image"===e.dialog?(a(),t(w,{key:1,onBgclick:l[5]||(l[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["onSaveClick","image"])):i("",!0),e.image&&"new-game"===e.dialog?(a(),t(v,{key:2,onBgclick:l[6]||(l[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):i("",!0)])};var Ht=e({name:"scores",props:{activePlayers:{type:Array,required:!0},idlePlayers:{type:Array,required:!0}},computed:{actives(){return this.activePlayers.sort(((e,t)=>t.points-e.points)),this.activePlayers},idles(){return this.idlePlayers.sort(((e,t)=>t.points-e.points)),this.idlePlayers}}});const Qt={class:"scores"},Yt=n("div",null,"Scores",-1),Kt=n("td",null,"⚡",-1),Zt=n("td",null,"💤",-1);Ht.render=function(e,l,i,o,s,u){return a(),t("div",Qt,[Yt,n("table",null,[(a(!0),t(d,null,c(e.actives,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[Kt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(a(!0),t(d,null,c(e.idles,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[Zt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var Jt=e({name:"puzzle-status",props:{finished:{type:Boolean,required:!0},duration:{type:Number,required:!0},piecesDone:{type:Number,required:!0},piecesTotal:{type:Number,required:!0}},computed:{icon(){return this.finished?"🏁":"⏳"},durationStr(){return E(this.duration)}}});const Xt={class:"timer"};Jt.render=function(e,l,i,o,s,d){return a(),t("div",Xt,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),w(e.$slots,"default")])};var en=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:Object},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const tn=n("td",null,[n("label",null,"Background: ")],-1),nn=n("td",null,[n("label",null,"Color: ")],-1),ln=n("td",null,[n("label",null,"Name: ")],-1);en.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("tr",null,[tn,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[1]||(l[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[nn,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[2]||(l[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[ln,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":l[3]||(l[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])])])])};var on=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const an={class:"preview"};on.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay",onClick:l[1]||(l[1]=t=>e.$emit("bgclick"))},[n("div",an,[n("div",{class:"img",style:e.previewStyle},null,4)])])};const sn=Re("Communication.js");let rn,dn=e=>{},cn=e=>{};let un=0;const gn=e=>{un!==e&&(un=e,cn(e))};function pn(e){if(2===un)try{rn.send(JSON.stringify(e))}catch(t){sn.info("unable to send message.. maybe because ws is invalid?")}}let hn,mn;var yn={connect:function(e,t,n){return hn=0,mn={},gn(3),new Promise((l=>{rn=new WebSocket(e,n+"|"+t),rn.onopen=e=>{gn(2),pn([xe])},rn.onmessage=e=>{const t=JSON.parse(e.data),i=t[0];if(i===ve){const e=t[1];l(e)}else{if(i!==we)throw`[ 2021-05-09 invalid connect msgType ${i} ]`;{const e=t[1],l=t[2];if(e===n&&mn[l])return void delete mn[l];dn(t)}}},rn.onerror=e=>{throw gn(1),"[ 2021-05-15 onerror ]"},rn.onclose=e=>{4e3===e.code||1001===e.code?gn(4):gn(1)}}))},requestReplayData:async function(e,t,n){const l={gameId:e,offset:t,size:n},i=await fetch(`/api/replay-data${We.asQueryArgs(l)}`);return await i.json()},disconnect:function(){rn&&rn.close(4e3),hn=0,mn={}},sendClientEvent:function(e){hn++,mn[hn]=e,pn([be,hn,mn[hn]])},onServerChange:function(e){dn=e},onConnectionStateChange:function(e){cn=e},CODE_CUSTOM_DISCONNECT:4e3,CONN_STATE_NOT_CONNECTED:0,CONN_STATE_DISCONNECTED:1,CONN_STATE_CLOSED:4,CONN_STATE_CONNECTED:2,CONN_STATE_CONNECTING:3},fn=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===yn.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===yn.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const wn={key:0,class:"overlay connection-lost"},vn={key:0,class:"overlay-content"},bn=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),xn={key:1,class:"overlay-content"},Cn=n("div",null,"Connecting...",-1);fn.render=function(e,l,o,s,r,d){return e.show?(a(),t("div",wn,[e.lostConnection?(a(),t("div",vn,[bn,n("span",{class:"btn",onClick:l[1]||(l[1]=t=>e.$emit("reconnect"))},"Reconnect")])):i("",!0),e.connecting?(a(),t("div",xn,[Cn])):i("",!0)])):i("",!0)};var kn=e({name:"help-overlay",emits:{bgclick:null}});const An=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),zn=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),Sn=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),Pn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),In=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Tn=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),_n=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),Dn=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Bn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),En=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1);kn.render=function(e,l,i,o,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[An,zn,Sn,Pn,In,Tn,_n,Dn,Bn,En])])};var On=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Un=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Nn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Mn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Gn(){let e=0,t=0,n=1;const l=(l,i)=>{e+=l/n,t+=i/n},i=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},o=l=>({x:l.x/n-e,y:l.y/n-t}),a=l=>({x:(l.x+e)*n,y:(l.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n});return{move:l,canZoom:e=>n!=i(e),zoom:(e,t)=>((e,t)=>{if(n==e)return!1;const i=1-n/e;return l(-t.x*i,-t.y*i),n=e,!0})(i(e),t),worldToViewport:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:a,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=o(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:o}}function $n(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var Rn={createCanvas:$n,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const l=$n(t,n);return l.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(l)},colorizedCanvas:function(e,t,n){const l=$n(e.width,e.height),i=l.getContext("2d");return i.save(),i.drawImage(t,0,0),i.fillStyle=n,i.globalCompositeOperation="source-in",i.fillRect(0,0,t.width,t.height),i.restore(),i.save(),i.globalCompositeOperation="destination-over",i.drawImage(e,0,0),i.restore(),l}};const Vn=Re("Debug.js");let jn=0,Fn=0;var Ln=e=>{jn=performance.now(),Fn=e},Wn=e=>{const t=performance.now(),n=t-jn;n>Fn&&Vn.log(e+": "+n),jn=t};const qn=Re("PuzzleGraphics.js");function Hn(e,t){const n=We.coordByTileIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Qn={loadPuzzleBitmaps:async function(e){const t=await Rn.loadImageToBitmap(e.info.imageUrl),n=await Rn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){qn.log("start createPuzzleTileBitmaps");var l=n.tileSize,i=n.tileMarginWidth,o=n.tileDrawSize,a=l/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0];const r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,o={x:i,y:i},r=fe.pointAdd(o,{x:l,y:0}),c=fe.pointAdd(r,{x:0,y:l}),u=fe.pointSub(c,{x:l,y:0});if(n.moveTo(o.x,o.y),0!==e.top)for(let l=0;l=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Zn=t/2,Jn=t-Zn;const n=1/4*this.canvas.width/(t/2);Yn=-n,Kn=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Xn(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Xn(this.rng));const e=[];for(;this.explodedBombs.length>0;){const t=this.explodedBombs.shift();if(!t)break;t.update(),t.alive&&e.push(t)}this.explodedBombs=e;const t=[];for(;this.readyBombs.length>0;){const e=this.readyBombs.shift();if(!e)break;e.update(this.particles),e.hasExploded?this.explodedBombs.push(e):t.push(e)}this.readyBombs=t;const n=[];for(;this.particles.length>0;){const e=this.particles.shift();if(!e)break;e.update(),e.alive&&n.push(e)}this.particles=n}render(){this.ctx.beginPath(),this.ctx.fillStyle="rgba(0, 0, 0, 0.1)",this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height);for(let e=0;e{const t=e.color+" "+e.d;if(!h[t]){const n=e.d?a:s;if(e.color){const l=e.d?r:d;h[t]=await createImageBitmap(Rn.colorizedCanvas(n,l,e.color))}else h[t]=n}return h[t]},y=function(e,t){return t.width=window.innerWidth,t.height=window.innerHeight,e.appendChild(t),window.addEventListener("resize",(()=>{t.width=window.innerWidth,t.height=window.innerHeight,ol=!0})),t}(i,Rn.createCanvas()),f={final:!1,requesting:!0,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,dataOffset:0,dataSize:1e4};yn.onConnectionStateChange((e=>{o.setConnectionState(e)}));const w=async e=>{f.requesting=!0;const t=await yn.requestReplayData(e,f.dataOffset,f.dataSize);return f.dataOffset+=f.dataSize,f.requesting=!1,t};let v=()=>0;const b=async()=>{if("play"===l){const l=await yn.connect(n,e,t),i=We.decodeGame(l);St.setGame(i.id,i),v=()=>D()}else{if("replay"!==l)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await w(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=We.decodeGame(t.game);St.setGame(n.id,n),f.requesting=!1,f.log=t.log,f.lastRealTs=D(),f.gameStartTs=parseInt(f.log[0][4],10),f.lastGameTs=f.gameStartTs,v=()=>f.lastGameTs}}ol=!0};await b();const x=St.getPieceDrawOffset(e),C=St.getPieceDrawSize(e),k=St.getPuzzleWidth(e),A=St.getPuzzleHeight(e),z=St.getTableWidth(e),S=St.getTableHeight(e),P={x:(z-k)/2,y:(S-A)/2},I={w:k,h:A},T={w:C,h:C},_=await Qn.loadPuzzleBitmaps(St.getPuzzle(e)),B=new tl(y,St.getRng(e));B.init();const E=y.getContext("2d");y.classList.add("loaded");const O=Gn();O.move(-(z-y.width)/2,-(S-y.height)/2);const U=function(e,t,n){let l=[],i=!0,o=!1,a=!1,s=!1,r=!1,d=!1,c=!1,u=!1;const g=(e,t)=>{const l=n.viewportToWorld({x:e,y:t});return[l.x,l.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{i&&("Shift"===t.key?u=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?s=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?r=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?o=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?a=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};e.addEventListener("mousedown",(e=>{0===e.button&&y([Se,...p(e)])})),e.addEventListener("mouseup",(e=>{0===e.button&&y([Pe,...p(e)])})),e.addEventListener("mousemove",(e=>{y([Ie,...p(e)])})),e.addEventListener("wheel",(e=>{if(n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Te:_e;y([t,...p(e)])}})),t.addEventListener("keydown",(e=>m(!0,e))),t.addEventListener("keyup",(e=>m(!1,e))),t.addEventListener("keypress",(e=>{i&&(" "===e.key&&y([Oe]),"F"!==e.key&&"f"!==e.key||(ll=!ll,ol=!0),"G"!==e.key&&"g"!==e.key||(il=!il,ol=!0))}));const y=e=>{l.push(e)};return{addEvent:y,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=u?20:10,t=(o?e:0)-(a?e:0),l=(s?e:0)-(r?e:0);0===t&&0===l||y([ze,t,l]),d&&c||(d?n.canZoom("in")&&y([Te,...h()]):c&&n.canZoom("out")&&y([_e,...h()]))},setHotkeys:e=>{i=e}}}(y,window,O),N=St.getImageUrl(e),M=()=>{const t=St.getStartTs(e),n=St.getFinishTs(e),l=v();o.setFinished(!!n),o.setDuration((n||l)-t)};M(),o.setPiecesDone(St.getFinishedPiecesCount(e)),o.setPiecesTotal(St.getPieceCount(e));const G=v();o.setActivePlayers(St.getActivePlayers(e,G)),o.setIdlePlayers(St.getIdlePlayers(e,G));const $=!!St.getFinishTs(e);let R=$;const V=()=>R&&!$,j=()=>St.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",F=()=>St.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let L="",W="",q=!1;const H=e=>{q=e;const[t,n]=e?[L,"grab"]:[W,"default"];y.style.cursor=`url('${t}') ${u} ${p}, ${n}`},Q=e=>{L=Rn.colorizedCanvas(a,r,e).toDataURL(),W=Rn.colorizedCanvas(s,d,e).toDataURL(),H(q)};Q(F());const Y=()=>{o.setReplaySpeed&&o.setReplaySpeed(f.speeds[f.speedIdx]),o.setReplayPaused&&o.setReplayPaused(f.paused)};if("play"===l?setInterval(M,1e3):"replay"===l&&Y(),"play"===l)yn.onServerChange((n=>{n[0],n[1],n[2];const l=n[3];for(const[i,o]of l)switch(i){case Me:{const n=We.decodePlayer(o);n.id!==t&&(St.setPlayer(e,n.id,n),ol=!0)}break;case Ne:{const t=We.decodePiece(o);St.setPiece(e,t.idx,t),ol=!0}break;case Ue:St.setPuzzleData(e,o),ol=!0}R=!!St.getFinishTs(e)}));else if("replay"===l){let t=setInterval((()=>{const n=D();if(f.requesting)return void(f.lastRealTs=n);if(f.logPointer+1>=f.log.length)return f.lastRealTs=n,void(async e=>{const t=await w(e);f.log=f.log.slice(f.logPointer),f.logPointer=0,f.log.push(...t.log),t.log.length=f.log.length){f.final&&clearInterval(t);break}const l=f.log[n],o=f.gameStartTs+l[l.length-1];if(o>i)break;const a=l.slice();if(a[0]===Ce){const t=a[1];St.addPlayer(e,t,o),ol=!0}else if(a[0]===ke){const t=St.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";St.addPlayer(e,t,o),ol=!0}else if(a[0]===Ae){const t=St.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=a[2];St.handleInput(e,t,n,o),ol=!0}f.logPointer=n}f.lastRealTs=n,f.lastGameTs=i,M()}),50)}let K=null;return(e=>{const t=e.fps||60,n=e.slow||1,l=e.update,i=e.render,o=window.requestAnimationFrame,a=1/t,s=n*a;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>s;)d-=s,l(a);i(d/n),c=r,o(u)};o(u)})({update:()=>{U.createKeyEvents();for(const n of U.consumeAll())if("play"===l){const l=n[0];if(l===ze){const e=n[1],t=n[2];ol=!0,O.move(e,t)}else if(l===Ie){if(K&&!St.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=O.worldToViewport(e),l=Math.round(t.x-K.x),i=Math.round(t.y-K.y);ol=!0,O.move(l,i),K=t}}else if(l===Be)Q(n[1]);else if(l===Se){const e={x:n[1],y:n[2]};K=O.worldToViewport(e),H(!0)}else if(l===Pe)K=null,H(!1);else if(l===Te){const e={x:n[1],y:n[2]};ol=!0,O.zoom("in",O.worldToViewport(e))}else if(l===_e){const e={x:n[1],y:n[2]};ol=!0,O.zoom("out",O.worldToViewport(e))}else l===Oe&&o.togglePreview();const i=v();St.handleInput(e,t,n,i).length>0&&(ol=!0),yn.sendClientEvent(n)}else if("replay"===l){const e=n[0];if(e===ze){const e=n[1],t=n[2];ol=!0,O.move(e,t)}else if(e===Ie){if(K){const e={x:n[1],y:n[2]},t=O.worldToViewport(e),l=Math.round(t.x-K.x),i=Math.round(t.y-K.y);ol=!0,O.move(l,i),K=t}}else if(e===Se){const e={x:n[1],y:n[2]};K=O.worldToViewport(e)}else if(e===Pe)K=null;else if(e===Te){const e={x:n[1],y:n[2]};ol=!0,O.zoom("in",O.worldToViewport(e))}else if(e===_e){const e={x:n[1],y:n[2]};ol=!0,O.zoom("out",O.worldToViewport(e))}else e===Oe&&o.togglePreview()}R=!!St.getFinishTs(e),V()&&(B.update(),ol=!0)},render:async()=>{if(!ol)return;const n=v();let i,a,s;window.DEBUG&&Ln(0),E.fillStyle=j(),E.fillRect(0,0,y.width,y.height),window.DEBUG&&Wn("clear done"),i=O.worldToViewportRaw(P),a=O.worldDimToViewportRaw(I),E.fillStyle="rgba(255, 255, 255, .3)",E.fillRect(i.x,i.y,a.w,a.h),window.DEBUG&&Wn("board done");const r=St.getPiecesSortedByZIndex(e);window.DEBUG&&Wn("get tiles done"),a=O.worldDimToViewportRaw(T);for(const e of r)(-1===e.owner?ll:il)&&(s=_[e.idx],i=O.worldToViewportRaw({x:x+e.pos.x,y:x+e.pos.y}),E.drawImage(s,0,0,s.width,s.height,i.x,i.y,a.w,a.h));window.DEBUG&&Wn("tiles done");const d=[];for(const o of St.getActivePlayers(e,n))c=o,("replay"===l||c.id!==t)&&(s=await m(o),i=O.worldToViewport(o),E.drawImage(s,i.x-u,i.y-p),d.push([`${o.name} (${o.points})`,i.x,i.y+g]));var c;E.fillStyle="white",E.textAlign="center";for(const[e,t,l]of d)E.fillText(e,t,l);window.DEBUG&&Wn("players done"),o.setActivePlayers(St.getActivePlayers(e,n)),o.setIdlePlayers(St.getIdlePlayers(e,n)),o.setPiecesDone(St.getFinishedPiecesCount(e)),window.DEBUG&&Wn("HUD done"),V()&&B.render(),ol=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([De,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([Be,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Ee,e])},replayOnSpeedUp:()=>{f.speedIdx+1{f.speedIdx>=1&&(f.speedIdx--,Y())},replayOnPauseToggle:()=>{f.paused=!f.paused,Y()},previewImageUrl:N,player:{background:j(),color:F(),name:St.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon"},disconnect:yn.disconnect,connect:b}}var sl=e({name:"game",components:{PuzzleStatus:Jt,Scores:Ht,SettingsOverlay:en,PreviewOverlay:on,ConnectionOverlay:fn,HelpOverlay:kn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},disconnect:()=>{},connect:()=>{}}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await al(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},setConnectionState:e=>{this.connectionState=e},togglePreview:()=>{this.toggle("preview",!1)}}))},unmounted(){this.g.disconnect()},methods:{reconnect(){this.g.connect()},toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}}});const rl={id:"game"},dl={class:"menu"},cl={class:"tabs"},ul=s("🧩 Puzzles");sl.render=function(e,i,s,r,d,c){const u=o("settings-overlay"),p=o("preview-overlay"),h=o("help-overlay"),m=o("connection-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",rl,[g(n(u,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(p,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(h,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",dl,[n("div",cl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[ul])),_:1}),n("div",{class:"opener",onClick:i[5]||(i[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var gl=e({name:"replay",components:{PuzzleStatus:Jt,Scores:Ht,SettingsOverlay:en,PreviewOverlay:on,HelpOverlay:kn},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},disconnect:()=>{}},replay:{speed:1,paused:!1}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await al(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"replay",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},setReplaySpeed:e=>{this.replay.speed=e},setReplayPaused:e=>{this.replay.paused=e}}))},unmounted(){this.g.disconnect()},methods:{toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}},computed:{replayText(){return"Replay-Speed: "+this.replay.speed+"x"+(this.replay.paused?" Paused":"")}}});const pl={id:"replay"},hl={class:"menu"},ml={class:"tabs"},yl=s("🧩 Puzzles");gl.render=function(e,i,s,d,c,u){const p=o("settings-overlay"),h=o("preview-overlay"),m=o("help-overlay"),y=o("puzzle-status"),f=o("router-link"),w=o("scores");return a(),t("div",pl,[g(n(p,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(m,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:l((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:i[5]||(i[5]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",hl,[n("div",ml,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[yl])),_:1}),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=b({history:x(),routes:[{name:"index",path:"/",component:R},{name:"new-game",path:"/new-game",component:Mt},{name:"game",path:"/g/:id",component:sl},{name:"replay",path:"/replay/:id",component:gl}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const l=C(k);l.config.globalProperties.$config=t,l.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=We.uniqId(),localStorage.setItem("ID",e)),e}(),l.use(n),l.mount("#app")})(); diff --git a/build/public/assets/index.ae0a2617.js b/build/public/assets/index.ae0a2617.js new file mode 100644 index 0000000..d76b69f --- /dev/null +++ b/build/public/assets/index.ae0a2617.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as o,b as i,r as l,o as a,e as s,t as r,F as c,f as d,g as u,h as g,v as p,i as h,j as m,k as y,l as f,m as w,n as v,p as b,q as x,s as C}from"./vendor.b622ee49.js";var k=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const A={id:"app"},z={key:0,class:"nav"},S=s("Index"),P=s("New game");k.render=function(e,s,r,c,d,u){const g=l("router-link"),p=l("router-view");return a(),t("div",A,[e.showNav?(a(),t("ul",z,[n("li",null,[n(g,{class:"btn",to:{name:"index"}},{default:o((()=>[S])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:o((()=>[P])),_:1})])])):i("",!0),n(p)])};const I=864e5,T=e=>{const t=Math.floor(e/I);e%=I;const n=Math.floor(e/36e5);e%=36e5;const o=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${o}m ${Math.floor(e/1e3)}s`};var _=1e3,D=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>T(t-e),E=T,O=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",o=e,i=t||D();return`${n} ${B(o,i)}`}}});const N={class:"game-info-text"},U=n("br",null,null,-1),M=n("br",null,null,-1),G=n("br",null,null,-1),$=s(" ↪️ Watch replay ");O.render=function(e,c,d,u,g,p){const h=l("router-link");return a(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",N,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),U,s(" 👥 "+r(e.game.players),1),M,s(" "+r(e.time(e.game.started,e.game.finished)),1),G])])),_:1},8,["to"]),e.game.hasReplay?(a(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[$])),_:1},8,["to"])):i("",!0)],4)};var R=e({components:{GameTeaser:O},data:()=>({gamesRunning:[],gamesFinished:[]}),async created(){const e=await fetch("/api/index-data"),t=await e.json();this.gamesRunning=t.gamesRunning,this.gamesFinished=t.gamesFinished}});const V=n("h1",null,"Running games",-1),j=n("h1",null,"Finished games",-1);R.render=function(e,o,i,s,r,u){const g=l("game-teaser");return a(),t("div",null,[V,(a(!0),t(c,null,d(e.gamesRunning,((e,o)=>(a(),t("div",{class:"game-teaser-wrap",key:o},[n(g,{game:e},null,8,["game"])])))),128)),j,(a(!0),t(c,null,d(e.gamesFinished,((e,o)=>(a(),t("div",{class:"game-teaser-wrap",key:o},[n(g,{game:e},null,8,["game"])])))),128))])};var F=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});F.render=function(e,o,i,l,s,r){return a(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var L=e({name:"image-library",components:{ImageTeaser:F},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});L.render=function(e,n,o,i,s,r){const u=l("image-teaser");return a(),t("div",null,[(a(!0),t(c,null,d(e.images,((n,o)=>(a(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])};const W={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};W.render=function(e,n,o,i,l,s){return a(),t("div",{style:s.style,title:o.title},null,12,["title"])};var q=e({name:"tags-input",props:{modelValue:{type:Array,required:!0}},emits:{"update:modelValue":null},data:()=>({input:"",values:[]}),created(){this.values=this.modelValue},methods:{onKeyUp(e){if(","===e.key)return this.add(),e.stopPropagation(),!1},add(){const e=this.input.replace(/,/g,"").trim();e&&(this.values.includes(e)||this.values.push(e),this.input="",this.$emit("update:modelValue",this.values))},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const H=m()(((e,o,i,l,s,u)=>(a(),t("div",null,[g(n("input",{class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onKeydown:o[2]||(o[2]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[3]||(o[3]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[p,e.input]]),(a(!0),t(c,null,d(e.values,((n,o)=>(a(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));q.render=H,q.__scopeId="data-v-771460ae";var Q=e({name:"new-image-dialog",components:{ResponsiveImage:W,TagsInput:q},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const o=new FileReader;o.readAsDataURL(n),o.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const Y={key:0,class:"has-image"},K={key:1},Z={class:"upload"},J=n("span",{class:"btn"},"Upload File",-1),X={class:"area-settings"},ee=n("td",null,[n("label",null,"Title")],-1),te=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ne=n("td",null,[n("label",null,"Tags")],-1),oe={class:"area-buttons"},ie=s("🧩 Post to gallery "),le=n("br",null,null,-1),ae=s(" + set up game");Q.render=function(e,o,i,s,r,c){const d=l("responsive-image"),h=l("tags-input");return a(),t("div",{class:"overlay new-image-dialog",onClick:o[8]||(o[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[7]||(o[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(a(),t("div",Y,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(d,{src:e.previewUrl},null,8,["src"])])):(a(),t("div",K,[n("label",Z,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),J])]))],2),n("div",X,[n("table",null,[n("tr",null,[ee,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[3]||(o[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),te,n("tr",null,[ne,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[4]||(o[4]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",oe,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[5]||(o[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[6]||(o[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[ie,le,ae],8,["disabled"])])])])};var se=e({name:"edit-image-dialog",components:{ResponsiveImage:W,TagsInput:q},props:{image:{type:Object,required:!0}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const re={class:"area-image"},ce={class:"has-image"},de={class:"area-settings"},ue=n("td",null,[n("label",null,"Title")],-1),ge=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),pe=n("td",null,[n("label",null,"Tags")],-1),he={class:"area-buttons"};var me,ye,fe,we;se.render=function(e,o,i,s,r,c){const d=l("responsive-image"),h=l("tags-input");return a(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",re,[n("div",ce,[n(d,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",de,[n("table",null,[n("tr",null,[ue,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ge,n("tr",null,[pe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",he,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])},(ye=me||(me={}))[ye.Flat=0]="Flat",ye[ye.Out=1]="Out",ye[ye.In=-1]="In",(we=fe||(fe={}))[we.FINAL=0]="FINAL",we[we.ANY=1]="ANY";var ve=e({name:"new-game-dialog",components:{ResponsiveImage:W},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:fe.ANY}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const be={class:"area-image"},xe={class:"has-image"},Ce={class:"area-settings"},ke=n("td",null,[n("label",null,"Pieces")],-1),Ae=n("td",null,[n("label",null,"Scoring: ")],-1),ze=s(" Any (Score when pieces are connected to each other or on final location)"),Se=n("br",null,null,-1),Pe=s(" Final (Score when pieces are put to their final location)"),Ie={class:"area-buttons"};ve.render=function(e,o,i,s,r,c){const d=l("responsive-image");return a(),t("div",{class:"overlay new-game-dialog",onClick:o[6]||(o[6]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[5]||(o[5]=u((()=>{}),["stop"]))},[n("div",be,[n("div",xe,[n(d,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ce,[n("table",null,[n("tr",null,[ke,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Ae,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[y,e.scoreMode]]),ze]),Se,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[y,e.scoreMode]]),Pe])])])])]),n("div",Ie,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[4]||(o[4]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};class Te{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new Te(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const _e=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},De=(...e)=>{const t=t=>(...n)=>{const o=new Date,i=_e(o.getHours(),"00"),l=_e(o.getMinutes(),"00"),a=_e(o.getSeconds(),"00");console[t](`${i}:${l}:${a}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var Be={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",Te.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||fe.FINAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:Te.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}},Ee=e({components:{ImageLibrary:L,NewImageDialog:Q,EditImageDialog:se,NewGameDialog:ve},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},methods:{toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${Be.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/newgame",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e)});if(200===t.status){const e=await t.json();this.$router.push({name:"game",params:{id:e.id}})}}}});const Oe={class:"upload-image-teaser"},Ne=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),Ue={key:0},Me=s(" Tags: "),Ge=s(" Sort by: "),$e=n("option",{value:"date_desc"},"Newest first",-1),Re=n("option",{value:"date_asc"},"Oldest first",-1),Ve=n("option",{value:"alpha_asc"},"A-Z",-1),je=n("option",{value:"alpha_desc"},"Z-A",-1);Ee.render=function(e,o,s,u,p,h){const m=l("image-library"),y=l("new-image-dialog"),w=l("edit-image-dialog"),v=l("new-game-dialog");return a(),t("div",null,[n("div",Oe,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),Ne]),n("div",null,[e.tags.length>0?(a(),t("label",Ue,[Me,(a(!0),t(c,null,d(e.tags,((n,o)=>(a(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title),11,["onClick"])))),128))])):i("",!0),n("label",null,[Ge,g(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[$e,Re,Ve,je],544),[[f,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(a(),t(y,{key:0,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["onPostToGalleryClick","onSetupGameClick"])):i("",!0),"edit-image"===e.dialog?(a(),t(w,{key:1,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["onSaveClick","image"])):i("",!0),e.image&&"new-game"===e.dialog?(a(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):i("",!0)])};var Fe=e({name:"scores",props:{activePlayers:{type:Array,required:!0},idlePlayers:{type:Array,required:!0}},computed:{actives(){return this.activePlayers.sort(((e,t)=>t.points-e.points)),this.activePlayers},idles(){return this.idlePlayers.sort(((e,t)=>t.points-e.points)),this.idlePlayers}}});const Le={class:"scores"},We=n("div",null,"Scores",-1),qe=n("td",null,"⚡",-1),He=n("td",null,"💤",-1);Fe.render=function(e,o,i,l,s,u){return a(),t("div",Le,[We,n("table",null,[(a(!0),t(c,null,d(e.actives,((e,o)=>(a(),t("tr",{key:o,style:{color:e.color}},[qe,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(a(!0),t(c,null,d(e.idles,((e,o)=>(a(),t("tr",{key:o,style:{color:e.color}},[He,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var Qe=e({name:"puzzle-status",props:{finished:{type:Boolean,required:!0},duration:{type:Number,required:!0},piecesDone:{type:Number,required:!0},piecesTotal:{type:Number,required:!0}},computed:{icon(){return this.finished?"🏁":"⏳"},durationStr(){return E(this.duration)}}});const Ye={class:"timer"};Qe.render=function(e,o,i,l,s,c){return a(),t("div",Ye,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),w(e.$slots,"default")])};var Ke=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:Object},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const Ze=n("td",null,[n("label",null,"Background: ")],-1),Je=n("td",null,[n("label",null,"Color: ")],-1),Xe=n("td",null,[n("label",null,"Name: ")],-1);Ke.render=function(e,o,i,l,s,r){return a(),t("div",{class:"overlay transparent",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("tr",null,[Ze,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[Je,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[Xe,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])])])])};var et=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const tt={class:"preview"};et.render=function(e,o,i,l,s,r){return a(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",tt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var nt=1,ot=4,it=2,lt=3,at=2,st=4,rt=3,ct=9,dt=1,ut=2,gt=3,pt=4,ht=5,mt=6,yt=7,ft=8,wt=10,vt=1,bt=2,xt=3;const Ct=De("Communication.js");let kt,At=e=>{},zt=e=>{};let St=0;const Pt=e=>{St!==e&&(St=e,zt(e))};function It(e){if(2===St)try{kt.send(JSON.stringify(e))}catch(t){Ct.info("unable to send message.. maybe because ws is invalid?")}}let Tt,_t;var Dt={connect:function(e,t,n){return Tt=0,_t={},Pt(3),new Promise((o=>{kt=new WebSocket(e,n+"|"+t),kt.onopen=()=>{Pt(2),It([lt])},kt.onmessage=e=>{const t=JSON.parse(e.data),i=t[0];if(i===ot){const e=t[1];o(e)}else{if(i!==nt)throw`[ 2021-05-09 invalid connect msgType ${i} ]`;{const e=t[1],o=t[2];if(e===n&&_t[o])return void delete _t[o];At(t)}}},kt.onerror=()=>{throw Pt(1),"[ 2021-05-15 onerror ]"},kt.onclose=e=>{4e3===e.code||1001===e.code?Pt(4):Pt(1)}}))},requestReplayData:async function(e,t,n){const o={gameId:e,offset:t,size:n},i=await fetch(`/api/replay-data${Be.asQueryArgs(o)}`);return await i.json()},disconnect:function(){kt&&kt.close(4e3),Tt=0,_t={}},sendClientEvent:function(e){Tt++,_t[Tt]=e,It([it,Tt,_t[Tt]])},onServerChange:function(e){At=e},onConnectionStateChange:function(e){zt=e},CODE_CUSTOM_DISCONNECT:4e3,CONN_STATE_NOT_CONNECTED:0,CONN_STATE_DISCONNECTED:1,CONN_STATE_CLOSED:4,CONN_STATE_CONNECTED:2,CONN_STATE_CONNECTING:3},Bt=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Dt.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Dt.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const Et={key:0,class:"overlay connection-lost"},Ot={key:0,class:"overlay-content"},Nt=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Ut={key:1,class:"overlay-content"},Mt=n("div",null,"Connecting...",-1);Bt.render=function(e,o,l,s,r,c){return e.show?(a(),t("div",Et,[e.lostConnection?(a(),t("div",Ot,[Nt,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):i("",!0),e.connecting?(a(),t("div",Ut,[Mt])):i("",!0)])):i("",!0)};var Gt=e({name:"help-overlay",emits:{bgclick:null}});const $t=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),Rt=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),Vt=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),jt=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),Ft=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Lt=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),Wt=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),qt=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Ht=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Qt=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1);Gt.render=function(e,o,i,l,s,r){return a(),t("div",{class:"overlay transparent",onClick:o[2]||(o[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:o[1]||(o[1]=u((()=>{}),["stop"]))},[$t,Rt,Vt,jt,Ft,Lt,Wt,qt,Ht,Qt])])};var Yt=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Kt=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Zt=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),Jt=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function Xt(){let e=0,t=0,n=1;const o=(o,i)=>{e+=o/n,t+=i/n},i=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},l=o=>({x:o.x/n-e,y:o.y/n-t}),a=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n});return{move:o,canZoom:e=>n!=i(e),zoom:(e,t)=>((e,t)=>{if(n==e)return!1;const i=1-n/e;return o(-t.x*i,-t.y*i),n=e,!0})(i(e),t),worldToViewport:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:a,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=l(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:l}}function en(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var tn={createCanvas:en,loadImageToBitmap:async function(e){return new Promise((t=>{const n=new Image;n.onload=()=>{createImageBitmap(n).then(t)},n.src=e}))},resizeBitmap:async function(e,t,n){const o=en(t,n);return o.getContext("2d").drawImage(e,0,0,e.width,e.height,0,0,t,n),await createImageBitmap(o)},colorizedCanvas:function(e,t,n){const o=en(e.width,e.height),i=o.getContext("2d");return i.save(),i.drawImage(t,0,0),i.fillStyle=n,i.globalCompositeOperation="source-in",i.fillRect(0,0,t.width,t.height),i.restore(),i.save(),i.globalCompositeOperation="destination-over",i.drawImage(e,0,0),i.restore(),o}};const nn=De("Debug.js");let on=0,ln=0;var an=e=>{on=performance.now(),ln=e},sn=e=>{const t=performance.now(),n=t-on;n>ln&&nn.log(e+": "+n),on=t};function rn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function cn(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var dn={pointSub:function(e,t){return{x:e.x-t.x,y:e.y-t.y}},pointAdd:function(e,t){return{x:e.x+t.x,y:e.y+t.y}},pointDistance:rn,pointInBounds:function(e,t){return e.x>=t.x&&e.x<=t.x+t.w&&e.y>=t.y&&e.y<=t.y+t.h},rectCenter:cn,rectMoved:function(e,t,n){return{x:e.x+t,y:e.y+n,w:e.w,h:e.h}},rectCenterDistance:function(e,t){return rn(cn(e),cn(t))},rectsOverlap:function(e,t){return!(t.x>e.x+e.w||e.x>t.x+t.w||t.y>e.y+e.h||e.y>t.y+t.h)}};const un=De("PuzzleGraphics.js");function gn(e,t){const n=Be.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var pn={loadPuzzleBitmaps:async function(e){const t=await tn.loadImageToBitmap(e.info.imageUrl),n=await tn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){un.log("start createPuzzleTileBitmaps");const o=n.tileSize,i=n.tileMarginWidth,l=n.tileDrawSize,a=o/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),c={};function d(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(c[t])return c[t];const n=new Path2D,l={x:i,y:i},r=dn.pointAdd(l,{x:o,y:0}),d=dn.pointAdd(r,{x:0,y:o}),u=dn.pointSub(d,{x:o,y:0});if(n.moveTo(l.x,l.y),0!==e.top)for(let o=0;oBe.decodePiece(hn[e].puzzle.tiles[t]),In=(e,t)=>Pn(e,t).group,Tn=(e,t)=>{const n=hn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},i=function(e,t){const n=hn[e].puzzle.info,o=Be.coordByPieceIdx(n,t),i=o.x*n.tileSize,l=o.y*n.tileSize;return{x:i,y:l}}(e,t);return dn.pointAdd(o,i)},_n=(e,t)=>Pn(e,t).pos,Dn=e=>{const t=qn(e),n=Hn(e),o=Math.round(t/4),i=Math.round(n/4);return{x:0-o,y:0-i,w:t+2*o,h:n+2*i}},Bn=(e,t)=>{const n=Un(e),o=Pn(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},En=(e,t)=>Pn(e,t).z,On=(e,t)=>{for(const n of hn[e].puzzle.tiles){const e=Be.decodePiece(n);if(e.owner===t)return e.idx}return-1},Nn=e=>hn[e].puzzle.info.tileDrawSize,Un=e=>hn[e].puzzle.info.tileSize,Mn=e=>hn[e].puzzle.data.maxGroup,Gn=e=>hn[e].puzzle.data.maxZ;function $n(e,t){const n=hn[e].puzzle.info,o=Be.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Rn=(e,t,n)=>{for(const o of t)Sn(e,o,{z:n})},Vn=(e,t,n)=>{const o=_n(e,t);Sn(e,t,{pos:dn.pointAdd(o,n)})},jn=(e,t,n)=>{const o=Nn(e),i=Dn(e),l=n;for(const a of t){const t=Pn(e,a);t.pos.x+n.xi.x+i.w&&(l.x=Math.min(i.x+i.w-t.pos.x+o,l.x)),t.pos.y+n.yi.y+i.h&&(l.y=Math.min(i.y+i.h-t.pos.y+o,l.y))}for(const a of t)Vn(e,a,l)},Fn=(e,t,n)=>{for(const o of t)Sn(e,o,{owner:n})};function Ln(e,t){const n=hn[e].puzzle.tiles,o=Be.decodePiece(n[t]),i=[];if(o.group)for(const l of n){const e=Be.decodePiece(l);e.group===o.group&&i.push(e.idx)}else i.push(o.idx);return i}const Wn=(e,t)=>{const n=yn(e,t);return n?n.points:0},qn=e=>hn[e].puzzle.info.table.width,Hn=e=>hn[e].puzzle.info.table.height;var Qn={setGame:function(e,t){hn[e]=t},exists:function(e){return!!hn[e]||!1},playerExists:wn,getActivePlayers:function(e,t){const n=t-30*_;return vn(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return vn(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){wn(e,t)?An(e,t,{ts:n}):fn(e,t,function(e,t){return{id:e,x:0,y:0,d:0,name:null,color:null,bgcolor:null,points:0,ts:t}}(t,n))},getFinishedPiecesCount:kn,getPieceCount:bn,getImageUrl:function(e){return hn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){hn[e].puzzle.info.imageUrl=t},get:function(e){return hn[e]||null},getAllGames:function(){return Object.values(hn).sort(((e,t)=>Cn(e.id)===Cn(t.id)?t.puzzle.data.started-e.puzzle.data.started:Cn(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=yn(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=yn(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=yn(e,t);return n?n.name:null},getPlayerIndexById:mn,getPlayerIdByIndex:function(e,t){return hn[e].players.length>t?Be.decodePlayer(hn[e].players[t]).id:null},changePlayer:An,setPlayer:fn,setPiece:function(e,t,n){hn[e].puzzle.tiles[t]=Be.encodePiece(n)},setPuzzleData:function(e,t){hn[e].puzzle.data=t},getTableWidth:qn,getTableHeight:Hn,getPuzzle:e=>hn[e].puzzle,getRng:e=>hn[e].rng.obj,getPuzzleWidth:e=>hn[e].puzzle.info.width,getPuzzleHeight:e=>hn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return hn[e].puzzle.tiles.map(Be.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=On(e,t);return n<0?null:hn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>hn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:Nn,getFinalPiecePos:Tn,getStartTs:e=>hn[e].puzzle.data.started,getFinishTs:e=>hn[e].puzzle.data.finished,handleInput:function(e,t,n,o){const i=hn[e].puzzle,l=function(e,t){return t in hn[e].evtInfos?hn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),a=[],s=()=>{a.push([vt,i.data])},r=t=>{a.push([bt,Be.encodePiece(Pn(e,t))])},c=e=>{for(const t of e)r(t)},d=()=>{const n=yn(e,t);n&&a.push([xt,Be.encodePlayer(n)])},u=n[0];if(u===mt){const i=n[1];An(e,t,{bgcolor:i,ts:o}),d()}else if(u===yt){const i=n[1];An(e,t,{color:i,ts:o}),d()}else if(u===ft){const i=`${n[1]}`.substr(0,16);An(e,t,{name:i,ts:o}),d()}else if(u===dt){const i={x:n[1],y:n[2]};An(e,t,{d:1,ts:o}),d(),l._last_mouse_down=i;const a=((e,t)=>{const n=hn[e].puzzle.info,o=hn[e].puzzle.tiles;let i=-1,l=-1;for(let a=0;ai)&&(i=e.z,l=a)}return l})(e,i);if(a>=0){const n=Gn(e)+1;zn(e,{maxZ:n}),s();const o=Ln(e,a);Rn(e,o,Gn(e)),Fn(e,o,t),c(o)}l._last_mouse=i}else if(u===gt){const i=n[1],a=n[2],s={x:i,y:a};if(null===l._last_mouse_down)An(e,t,{x:i,y:a,ts:o}),d();else{const n=On(e,t);if(n>=0){An(e,t,{x:i,y:a,ts:o}),d();const r=Ln(e,n);let u=dn.pointInBounds(s,Dn(e))&&dn.pointInBounds(l._last_mouse_down,Dn(e));for(const t of r){const n=Bn(e,t);if(dn.pointInBounds(s,n)){u=!0;break}}if(u){const t=i-l._last_mouse_down.x,n=a-l._last_mouse_down.y;jn(e,r,{x:t,y:n}),c(r)}}else An(e,t,{ts:o}),d();l._last_mouse_down=s}l._last_mouse=s}else if(u===ut){const a={x:n[1],y:n[2]},u=0;l._last_mouse_down=null;const g=On(e,t);if(g>=0){const n=Ln(e,g);Fn(e,n,0),c(n);const l=_n(e,g),a=Tn(e,g);if(dn.pointDistance(a,l){for(const n of t)Sn(e,n,{owner:-1,z:1})})(e,n),c(n);let r=Wn(e,t);xn(e)===fe.FINAL?r+=n.length:xn(e)===fe.ANY&&(r+=1),An(e,t,{d:u,ts:o,points:r}),d(),kn(e)===bn(e)&&(zn(e,{finished:o}),s())}else{const n=(e,t,n,o)=>{const i=hn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=In(e,t),i=In(e,n);return!(!o||o!==i)})(e,t,n))return!1;const l=_n(e,t),a=dn.pointAdd(_n(e,n),{x:o[0]*i.tileSize,y:o[1]*i.tileSize});if(dn.pointDistance(l,a){const o=hn[e].puzzle.tiles,i=In(e,t),l=In(e,n);let a;const c=[];i&&c.push(i),l&&c.push(l),i?a=i:l?a=l:(zn(e,{maxGroup:Mn(e)+1}),s(),a=Mn(e));if(Sn(e,t,{group:a}),r(t),Sn(e,n,{group:a}),r(n),c.length>0)for(const s of o){const t=Be.decodePiece(s);c.includes(t.group)&&(Sn(e,t.idx,{group:a}),r(t.idx))}})(e,t,n),i=Ln(e,t);const d=((e,t)=>{let n=0;for(const o of t){const t=En(e,o);t>n&&(n=t)}return n})(e,i);return Rn(e,i,d),c(i),!0}return!1};let i=!1;for(const t of Ln(e,g)){const o=$n(e,t);if(n(e,t,o[0],[0,1])||n(e,t,o[1],[-1,0])||n(e,t,o[2],[0,-1])||n(e,t,o[3],[1,0])){i=!0;break}}if(i&&xn(e)===fe.ANY){const n=Wn(e,t)+1;An(e,t,{d:u,ts:o,points:n}),d()}else An(e,t,{d:u,ts:o}),d()}}else An(e,t,{d:u,ts:o}),d();l._last_mouse=a}else if(u===pt){const i=n[1],a=n[2];An(e,t,{x:i,y:a,ts:o}),d(),l._last_mouse={x:i,y:a}}else if(u===ht){const i=n[1],a=n[2];An(e,t,{x:i,y:a,ts:o}),d(),l._last_mouse={x:i,y:a}}else An(e,t,{ts:o}),d();return function(e,t,n){hn[e].evtInfos[t]=n}(e,t,l),a}};let Yn=-10,Kn=20,Zn=2,Jn=15;class Xn{constructor(e){this.radius=10,this.previousRadius=10,this.explodingDuration=100,this.hasExploded=!1,this.alive=!0,this.color=function(e){return"rgba("+e.random(0,255)+","+e.random(0,255)+","+e.random(0,255)+", 0.8)"}(e),this.px=window.innerWidth/4+Math.random()*window.innerWidth/2,this.py=window.innerHeight,this.vx=Yn+Math.random()*Kn,this.vy=-1*(Zn+Math.random()*Jn),this.duration=0}update(e){if(this.hasExploded){const e=200-this.radius;this.previousRadius=this.radius,this.radius+=e/10,this.explodingDuration--,0==this.explodingDuration&&(this.alive=!1)}else this.vx+=0,this.vy+=1,this.vy>=0&&e&&this.explode(e),this.px+=this.vx,this.py+=this.vy}draw(e){e.beginPath(),e.arc(this.px,this.py,this.previousRadius,0,2*Math.PI,!1),this.hasExploded||(e.fillStyle=this.color,e.lineWidth=1,e.fill())}explode(e){this.hasExploded=!0;const t=3+Math.floor(3*Math.random());for(let n=0;n{this.resize()}))}setSpeedParams(){let e=0,t=0;for(;e=0;)t+=1,e+=t;Zn=t/2,Jn=t-Zn;const n=1/4*this.canvas.width/(t/2);Yn=-n,Kn=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new Xn(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new Xn(this.rng));const e=[];for(;this.explodedBombs.length>0;){const t=this.explodedBombs.shift();if(!t)break;t.update(),t.alive&&e.push(t)}this.explodedBombs=e;const t=[];for(;this.readyBombs.length>0;){const e=this.readyBombs.shift();if(!e)break;e.update(this.particles),e.hasExploded?this.explodedBombs.push(e):t.push(e)}this.readyBombs=t;const n=[];for(;this.particles.length>0;){const e=this.particles.shift();if(!e)break;e.update(),e.alive&&n.push(e)}this.particles=n}render(){this.ctx.beginPath(),this.ctx.fillStyle="rgba(0, 0, 0, 0.1)",this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height);for(let e=0;e{const t=e.color+" "+e.d;if(!h[t]){const n=e.d?a:s;if(e.color){const o=e.d?r:c;h[t]=await createImageBitmap(tn.colorizedCanvas(n,o,e.color))}else h[t]=n}return h[t]},y=function(e,t){return t.width=window.innerWidth,t.height=window.innerHeight,e.appendChild(t),window.addEventListener("resize",(()=>{t.width=window.innerWidth,t.height=window.innerHeight,lo=!0})),t}(i,tn.createCanvas()),f={final:!1,requesting:!0,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,dataOffset:0,dataSize:1e4};Dt.onConnectionStateChange((e=>{l.setConnectionState(e)}));const w=async e=>{f.requesting=!0;const t=await Dt.requestReplayData(e,f.dataOffset,f.dataSize);return f.dataOffset+=f.dataSize,f.requesting=!1,t};let v=()=>0;const b=async()=>{if("play"===o){const o=await Dt.connect(n,e,t),i=Be.decodeGame(o);Qn.setGame(i.id,i),v=()=>D()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await w(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=Be.decodeGame(t.game);Qn.setGame(n.id,n),f.requesting=!1,f.log=t.log,f.lastRealTs=D(),f.gameStartTs=parseInt(f.log[0][4],10),f.lastGameTs=f.gameStartTs,v=()=>f.lastGameTs}}lo=!0};await b();const x=Qn.getPieceDrawOffset(e),C=Qn.getPieceDrawSize(e),k=Qn.getPuzzleWidth(e),A=Qn.getPuzzleHeight(e),z=Qn.getTableWidth(e),S=Qn.getTableHeight(e),P={x:(z-k)/2,y:(S-A)/2},I={w:k,h:A},T={w:C,h:C},_=await pn.loadPuzzleBitmaps(Qn.getPuzzle(e)),B=new to(y,Qn.getRng(e));B.init();const E=y.getContext("2d");y.classList.add("loaded");const O=Xt();O.move(-(z-y.width)/2,-(S-y.height)/2);const N=function(e,t,n){let o=[],i=!0,l=!1,a=!1,s=!1,r=!1,c=!1,d=!1,u=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{i&&("Shift"===t.key?u=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?s=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?r=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?l=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?a=e:"q"===t.key?d=e:"e"===t.key&&(c=e))};e.addEventListener("mousedown",(e=>{0===e.button&&y([dt,...p(e)])})),e.addEventListener("mouseup",(e=>{0===e.button&&y([ut,...p(e)])})),e.addEventListener("mousemove",(e=>{y([gt,...p(e)])})),e.addEventListener("wheel",(e=>{if(n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?pt:ht;y([t,...p(e)])}})),t.addEventListener("keydown",(e=>m(!0,e))),t.addEventListener("keyup",(e=>m(!1,e))),t.addEventListener("keypress",(e=>{i&&(" "===e.key&&y([wt]),"F"!==e.key&&"f"!==e.key||(oo=!oo,lo=!0),"G"!==e.key&&"g"!==e.key||(io=!io,lo=!0))}));const y=e=>{o.push(e)};return{addEvent:y,consumeAll:()=>{if(0===o.length)return[];const e=o.slice();return o=[],e},createKeyEvents:()=>{const e=u?20:10,t=(l?e:0)-(a?e:0),o=(s?e:0)-(r?e:0);0===t&&0===o||y([ct,t,o]),c&&d||(c?n.canZoom("in")&&y([pt,...h()]):d&&n.canZoom("out")&&y([ht,...h()]))},setHotkeys:e=>{i=e}}}(y,window,O),U=Qn.getImageUrl(e),M=()=>{const t=Qn.getStartTs(e),n=Qn.getFinishTs(e),o=v();l.setFinished(!!n),l.setDuration((n||o)-t)};M(),l.setPiecesDone(Qn.getFinishedPiecesCount(e)),l.setPiecesTotal(Qn.getPieceCount(e));const G=v();l.setActivePlayers(Qn.getActivePlayers(e,G)),l.setIdlePlayers(Qn.getIdlePlayers(e,G));const $=!!Qn.getFinishTs(e);let R=$;const V=()=>R&&!$,j=()=>Qn.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",F=()=>Qn.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let L="",W="",q=!1;const H=e=>{q=e;const[t,n]=e?[L,"grab"]:[W,"default"];y.style.cursor=`url('${t}') ${u} ${p}, ${n}`},Q=e=>{L=tn.colorizedCanvas(a,r,e).toDataURL(),W=tn.colorizedCanvas(s,c,e).toDataURL(),H(q)};Q(F());const Y=()=>{l.setReplaySpeed&&l.setReplaySpeed(f.speeds[f.speedIdx]),l.setReplayPaused&&l.setReplayPaused(f.paused)};if("play"===o?setInterval(M,1e3):"replay"===o&&Y(),"play"===o)Dt.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[i,l]of o)switch(i){case xt:{const n=Be.decodePlayer(l);n.id!==t&&(Qn.setPlayer(e,n.id,n),lo=!0)}break;case bt:{const t=Be.decodePiece(l);Qn.setPiece(e,t.idx,t),lo=!0}break;case vt:Qn.setPuzzleData(e,l),lo=!0}R=!!Qn.getFinishTs(e)}));else if("replay"===o){const t=setInterval((()=>{const n=D();if(f.requesting)return void(f.lastRealTs=n);if(f.logPointer+1>=f.log.length)return f.lastRealTs=n,void(async e=>{const t=await w(e);f.log=f.log.slice(f.logPointer),f.logPointer=0,f.log.push(...t.log),t.log.length=f.log.length){f.final&&clearInterval(t);break}const o=f.log[n],l=f.gameStartTs+o[o.length-1];if(l>i)break;const a=o.slice();if(a[0]===at){const t=a[1];Qn.addPlayer(e,t,l),lo=!0}else if(a[0]===st){const t=Qn.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";Qn.addPlayer(e,t,l),lo=!0}else if(a[0]===rt){const t=Qn.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=a[2];Qn.handleInput(e,t,n,l),lo=!0}f.logPointer=n}f.lastRealTs=n,f.lastGameTs=i,M()}),50)}let K=null;return(e=>{const t=e.fps||60,n=e.slow||1,o=e.update,i=e.render,l=window.requestAnimationFrame,a=1/t,s=n*a;let r,c=0,d=window.performance.now();const u=()=>{for(r=window.performance.now(),c+=Math.min(1,(r-d)/1e3);c>s;)c-=s,o(a);i(c/n),d=r,l(u)};l(u)})({update:()=>{N.createKeyEvents();for(const n of N.consumeAll())if("play"===o){const o=n[0];if(o===ct){const e=n[1],t=n[2];lo=!0,O.move(e,t)}else if(o===gt){if(K&&!Qn.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=O.worldToViewport(e),o=Math.round(t.x-K.x),i=Math.round(t.y-K.y);lo=!0,O.move(o,i),K=t}}else if(o===yt)Q(n[1]);else if(o===dt){const e={x:n[1],y:n[2]};K=O.worldToViewport(e),H(!0)}else if(o===ut)K=null,H(!1);else if(o===pt){const e={x:n[1],y:n[2]};lo=!0,O.zoom("in",O.worldToViewport(e))}else if(o===ht){const e={x:n[1],y:n[2]};lo=!0,O.zoom("out",O.worldToViewport(e))}else o===wt&&l.togglePreview();const i=v();Qn.handleInput(e,t,n,i).length>0&&(lo=!0),Dt.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===ct){const e=n[1],t=n[2];lo=!0,O.move(e,t)}else if(e===gt){if(K){const e={x:n[1],y:n[2]},t=O.worldToViewport(e),o=Math.round(t.x-K.x),i=Math.round(t.y-K.y);lo=!0,O.move(o,i),K=t}}else if(e===dt){const e={x:n[1],y:n[2]};K=O.worldToViewport(e)}else if(e===ut)K=null;else if(e===pt){const e={x:n[1],y:n[2]};lo=!0,O.zoom("in",O.worldToViewport(e))}else if(e===ht){const e={x:n[1],y:n[2]};lo=!0,O.zoom("out",O.worldToViewport(e))}else e===wt&&l.togglePreview()}R=!!Qn.getFinishTs(e),V()&&(B.update(),lo=!0)},render:async()=>{if(!lo)return;const n=v();let i,a,s;window.DEBUG&&an(0),E.fillStyle=j(),E.fillRect(0,0,y.width,y.height),window.DEBUG&&sn("clear done"),i=O.worldToViewportRaw(P),a=O.worldDimToViewportRaw(I),E.fillStyle="rgba(255, 255, 255, .3)",E.fillRect(i.x,i.y,a.w,a.h),window.DEBUG&&sn("board done");const r=Qn.getPiecesSortedByZIndex(e);window.DEBUG&&sn("get tiles done"),a=O.worldDimToViewportRaw(T);for(const e of r)(-1===e.owner?oo:io)&&(s=_[e.idx],i=O.worldToViewportRaw({x:x+e.pos.x,y:x+e.pos.y}),E.drawImage(s,0,0,s.width,s.height,i.x,i.y,a.w,a.h));window.DEBUG&&sn("tiles done");const c=[];for(const l of Qn.getActivePlayers(e,n))d=l,("replay"===o||d.id!==t)&&(s=await m(l),i=O.worldToViewport(l),E.drawImage(s,i.x-u,i.y-p),c.push([`${l.name} (${l.points})`,i.x,i.y+g]));var d;E.fillStyle="white",E.textAlign="center";for(const[e,t,o]of c)E.fillText(e,t,o);window.DEBUG&&sn("players done"),l.setActivePlayers(Qn.getActivePlayers(e,n)),l.setIdlePlayers(Qn.getIdlePlayers(e,n)),l.setPiecesDone(Qn.getFinishedPiecesCount(e)),window.DEBUG&&sn("HUD done"),V()&&B.render(),lo=!1}}),{setHotkeys:e=>{N.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),N.addEvent([mt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),N.addEvent([yt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),N.addEvent([ft,e])},replayOnSpeedUp:()=>{f.speedIdx+1{f.speedIdx>=1&&(f.speedIdx--,Y())},replayOnPauseToggle:()=>{f.paused=!f.paused,Y()},previewImageUrl:U,player:{background:j(),color:F(),name:Qn.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon"},disconnect:Dt.disconnect,connect:b}}var so=e({name:"game",components:{PuzzleStatus:Qe,Scores:Fe,SettingsOverlay:Ke,PreviewOverlay:et,ConnectionOverlay:Bt,HelpOverlay:Gt},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},disconnect:()=>{},connect:()=>{}}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await ao(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"play",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},setConnectionState:e=>{this.connectionState=e},togglePreview:()=>{this.toggle("preview",!1)}}))},unmounted(){this.g.disconnect()},methods:{reconnect(){this.g.connect()},toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}}});const ro={id:"game"},co={class:"menu"},uo={class:"tabs"},go=s("🧩 Puzzles");so.render=function(e,i,s,r,c,d){const u=l("settings-overlay"),p=l("preview-overlay"),h=l("help-overlay"),m=l("connection-overlay"),y=l("puzzle-status"),f=l("router-link"),w=l("scores");return a(),t("div",ro,[g(n(u,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(p,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(h,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",co,[n("div",uo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[go])),_:1}),n("div",{class:"opener",onClick:i[5]||(i[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var po=e({name:"replay",components:{PuzzleStatus:Qe,Scores:Fe,SettingsOverlay:Ke,PreviewOverlay:et,HelpOverlay:Gt},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},disconnect:()=>{}},replay:{speed:1,paused:!1}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await ao(`${this.$route.params.id}`,this.$clientId,this.$config.WS_ADDRESS,"replay",this.$el,{setActivePlayers:e=>{this.activePlayers=e},setIdlePlayers:e=>{this.idlePlayers=e},setFinished:e=>{this.finished=e},setDuration:e=>{this.duration=e},setPiecesDone:e=>{this.piecesDone=e},setPiecesTotal:e=>{this.piecesTotal=e},togglePreview:()=>{this.toggle("preview",!1)},setConnectionState:e=>{this.connectionState=e},setReplaySpeed:e=>{this.replay.speed=e},setReplayPaused:e=>{this.replay.paused=e}}))},unmounted(){this.g.disconnect()},methods:{toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}},computed:{replayText(){return"Replay-Speed: "+this.replay.speed+"x"+(this.replay.paused?" Paused":"")}}});const ho={id:"replay"},mo={class:"menu"},yo={class:"tabs"},fo=s("🧩 Puzzles");po.render=function(e,i,s,c,d,u){const p=l("settings-overlay"),h=l("preview-overlay"),m=l("help-overlay"),y=l("puzzle-status"),f=l("router-link"),w=l("scores");return a(),t("div",ho,[g(n(p,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(m,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:i[5]||(i[5]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",mo,[n("div",yo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[fo])),_:1}),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=b({history:x(),routes:[{name:"index",path:"/",component:R},{name:"new-game",path:"/new-game",component:Ee},{name:"game",path:"/g/:id",component:so},{name:"replay",path:"/replay/:id",component:po}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=C(k);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=Be.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); diff --git a/build/public/index.html b/build/public/index.html index 9d4f95b..a9cd071 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,7 +4,7 @@ 🧩 jigsaw.hyottoko.club - + diff --git a/build/server/main.js b/build/server/main.js index 7438e73..fd0a8d0 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -10,193 +10,9 @@ import { dirname } from 'path'; import sizeOf from 'image-size'; import exif from 'exif'; import sharp from 'sharp'; -import bodyParser from 'body-parser'; import v8 from 'v8'; import bsqlite from 'better-sqlite3'; -function pointSub(a, b) { - return { x: a.x - b.x, y: a.y - b.y }; -} -function pointAdd(a, b) { - return { x: a.x + b.x, y: a.y + b.y }; -} -function pointDistance(a, b) { - const diffX = a.x - b.x; - const diffY = a.y - b.y; - return Math.sqrt(diffX * diffX + diffY * diffY); -} -function pointInBounds(pt, rect) { - return pt.x >= rect.x - && pt.x <= rect.x + rect.w - && pt.y >= rect.y - && pt.y <= rect.y + rect.h; -} -function rectCenter(rect) { - return { - x: rect.x + (rect.w / 2), - y: rect.y + (rect.h / 2), - }; -} -/** - * Returns a rectangle with same dimensions as the given one, but - * location (x/y) moved by x and y. - * - * @param {x, y, w,, h} rect - * @param number x - * @param number y - * @returns {x, y, w, h} - */ -function rectMoved(rect, x, y) { - return { - x: rect.x + x, - y: rect.y + y, - w: rect.w, - h: rect.h, - }; -} -/** - * Returns true if the rectangles overlap, including their borders. - * - * @param {x, y, w, h} rectA - * @param {x, y, w, h} rectB - * @returns bool - */ -function rectsOverlap(rectA, rectB) { - return !(rectB.x > (rectA.x + rectA.w) - || rectA.x > (rectB.x + rectB.w) - || rectB.y > (rectA.y + rectA.h) - || rectA.y > (rectB.y + rectB.h)); -} -function rectCenterDistance(rectA, rectB) { - return pointDistance(rectCenter(rectA), rectCenter(rectB)); -} -var Geometry = { - pointSub, - pointAdd, - pointDistance, - pointInBounds, - rectCenter, - rectMoved, - rectCenterDistance, - rectsOverlap, -}; - -/* -SERVER_CLIENT_MESSAGE_PROTOCOL -NOTE: clients always send game id and their id - when creating sockets (via socket.protocol), so - this doesn't need to be set in each message data - -NOTE: first value in the array is always the type of event/message - when describing them below, the value each has is used - instead of writing EVENT_TYPE or something ismilar - - -EV_CLIENT_EVENT: event triggered by clients and sent to server -[ - EV_CLIENT_EVENT, // constant value, type of event - CLIENT_SEQ, // sequence number sent by client. - EV_DATA, // (eg. mouse input info) -] - -EV_SERVER_EVENT: event sent to clients after recieving a client - event and processing it -[ - EV_SERVER_EVENT, // constant value, type of event - CLIENT_ID, // user who sent the client event - CLIENT_SEQ, // sequence number of the client event msg - CHANGES_TRIGGERED_BY_CLIENT_EVENT, -] - -EV_CLIENT_INIT: event sent by client to enter a game -[ - EV_CLIENT_INIT, // constant value, type of event -] - -EV_SERVER_INIT: event sent to one client after that client - connects to a game -[ - EV_SERVER_INIT, // constant value, type of event - GAME, // complete game instance required by - // client to build client side of the game -] -*/ -const EV_SERVER_EVENT = 1; -const EV_SERVER_INIT = 4; -const EV_CLIENT_EVENT = 2; -const EV_CLIENT_INIT = 3; -const LOG_HEADER = 1; -const LOG_ADD_PLAYER = 2; -const LOG_UPDATE_PLAYER = 4; -const LOG_HANDLE_INPUT = 3; -const INPUT_EV_MOUSE_DOWN = 1; -const INPUT_EV_MOUSE_UP = 2; -const INPUT_EV_MOUSE_MOVE = 3; -const INPUT_EV_ZOOM_IN = 4; -const INPUT_EV_ZOOM_OUT = 5; -const INPUT_EV_BG_COLOR = 6; -const INPUT_EV_PLAYER_COLOR = 7; -const INPUT_EV_PLAYER_NAME = 8; -const INPUT_EV_MOVE = 9; -const INPUT_EV_TOGGLE_PREVIEW = 10; -const CHANGE_DATA = 1; -const CHANGE_TILE = 2; -const CHANGE_PLAYER = 3; -var Protocol = { - EV_SERVER_EVENT, - EV_SERVER_INIT, - EV_CLIENT_EVENT, - EV_CLIENT_INIT, - LOG_HEADER, - LOG_ADD_PLAYER, - LOG_UPDATE_PLAYER, - LOG_HANDLE_INPUT, - INPUT_EV_MOVE, - INPUT_EV_MOUSE_DOWN, - INPUT_EV_MOUSE_UP, - INPUT_EV_MOUSE_MOVE, - INPUT_EV_ZOOM_IN, - INPUT_EV_ZOOM_OUT, - INPUT_EV_BG_COLOR, - INPUT_EV_PLAYER_COLOR, - INPUT_EV_PLAYER_NAME, - INPUT_EV_TOGGLE_PREVIEW, - CHANGE_DATA, - CHANGE_TILE, - CHANGE_PLAYER, -}; - -const MS = 1; -const SEC = MS * 1000; -const MIN = SEC * 60; -const HOUR = MIN * 60; -const DAY = HOUR * 24; -const timestamp = () => { - const d = new Date(); - return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds()); -}; -const durationStr = (duration) => { - const d = Math.floor(duration / DAY); - duration = duration % DAY; - const h = Math.floor(duration / HOUR); - duration = duration % HOUR; - const m = Math.floor(duration / MIN); - duration = duration % MIN; - const s = Math.floor(duration / SEC); - return `${d}d ${h}h ${m}m ${s}s`; -}; -const timeDiffStr = (from, to) => durationStr(to - from); -var Time = { - MS, - SEC, - MIN, - HOUR, - DAY, - timestamp, - timeDiffStr, - durationStr, -}; - var PieceEdge; (function (PieceEdge) { PieceEdge[PieceEdge["Flat"] = 0] = "Flat"; @@ -207,706 +23,7 @@ var ScoreMode; (function (ScoreMode) { ScoreMode[ScoreMode["FINAL"] = 0] = "FINAL"; ScoreMode[ScoreMode["ANY"] = 1] = "ANY"; -})(ScoreMode || (ScoreMode = {})); -const IDLE_TIMEOUT_SEC = 30; -// Map -const GAMES = {}; -function exists$1(gameId) { - return (!!GAMES[gameId]) || false; -} -function __createPlayerObject(id, ts) { - return { - id: id, - x: 0, - y: 0, - d: 0, - name: null, - color: null, - bgcolor: null, - points: 0, - ts: ts, - }; -} -function setGame(gameId, game) { - GAMES[gameId] = game; -} -function getPlayerIndexById(gameId, playerId) { - let i = 0; - for (let player of GAMES[gameId].players) { - if (Util.decodePlayer(player).id === playerId) { - return i; - } - i++; - } - return -1; -} -function getPlayerIdByIndex(gameId, playerIndex) { - if (GAMES[gameId].players.length > playerIndex) { - return Util.decodePlayer(GAMES[gameId].players[playerIndex]).id; - } - return null; -} -function getPlayer(gameId, playerId) { - const idx = getPlayerIndexById(gameId, playerId); - if (idx === -1) { - return null; - } - return Util.decodePlayer(GAMES[gameId].players[idx]); -} -function setPlayer(gameId, playerId, player) { - const idx = getPlayerIndexById(gameId, playerId); - if (idx === -1) { - GAMES[gameId].players.push(Util.encodePlayer(player)); - } - else { - GAMES[gameId].players[idx] = Util.encodePlayer(player); - } -} -function setPiece(gameId, pieceIdx, piece) { - GAMES[gameId].puzzle.tiles[pieceIdx] = Util.encodePiece(piece); -} -function setPuzzleData(gameId, data) { - GAMES[gameId].puzzle.data = data; -} -function playerExists(gameId, playerId) { - const idx = getPlayerIndexById(gameId, playerId); - return idx !== -1; -} -function getActivePlayers(gameId, ts) { - const minTs = ts - IDLE_TIMEOUT_SEC * Time.SEC; - return getAllPlayers(gameId).filter((p) => p.ts >= minTs); -} -function getIdlePlayers(gameId, ts) { - const minTs = ts - IDLE_TIMEOUT_SEC * Time.SEC; - return getAllPlayers(gameId).filter((p) => p.ts < minTs && p.points > 0); -} -function addPlayer$1(gameId, playerId, ts) { - if (!playerExists(gameId, playerId)) { - setPlayer(gameId, playerId, __createPlayerObject(playerId, ts)); - } - else { - changePlayer(gameId, playerId, { ts }); - } -} -function getEvtInfo(gameId, playerId) { - if (playerId in GAMES[gameId].evtInfos) { - return GAMES[gameId].evtInfos[playerId]; - } - return { - _last_mouse: null, - _last_mouse_down: null, - }; -} -function setEvtInfo(gameId, playerId, evtInfo) { - GAMES[gameId].evtInfos[playerId] = evtInfo; -} -function getAllGames() { - return Object.values(GAMES).sort((a, b) => { - // when both have same finished state, sort by started - if (isFinished(a.id) === isFinished(b.id)) { - return b.puzzle.data.started - a.puzzle.data.started; - } - // otherwise, sort: unfinished, finished - return isFinished(a.id) ? 1 : -1; - }); -} -function getAllPlayers(gameId) { - return GAMES[gameId] - ? GAMES[gameId].players.map(Util.decodePlayer) - : []; -} -function get$1(gameId) { - return GAMES[gameId]; -} -function getPieceCount(gameId) { - return GAMES[gameId].puzzle.tiles.length; -} -function getImageUrl(gameId) { - return GAMES[gameId].puzzle.info.imageUrl; -} -function setImageUrl(gameId, imageUrl) { - GAMES[gameId].puzzle.info.imageUrl = imageUrl; -} -function getScoreMode(gameId) { - return GAMES[gameId].scoreMode || ScoreMode.FINAL; -} -function isFinished(gameId) { - return getFinishedPiecesCount(gameId) === getPieceCount(gameId); -} -function getFinishedPiecesCount(gameId) { - let count = 0; - for (let t of GAMES[gameId].puzzle.tiles) { - if (Util.decodePiece(t).owner === -1) { - count++; - } - } - return count; -} -function getPiecesSortedByZIndex(gameId) { - const pieces = GAMES[gameId].puzzle.tiles.map(Util.decodePiece); - return pieces.sort((t1, t2) => t1.z - t2.z); -} -function changePlayer(gameId, playerId, change) { - const player = getPlayer(gameId, playerId); - if (player === null) { - return; - } - for (let k of Object.keys(change)) { - // @ts-ignore - player[k] = change[k]; - } - setPlayer(gameId, playerId, player); -} -function changeData(gameId, change) { - for (let k of Object.keys(change)) { - // @ts-ignore - GAMES[gameId].puzzle.data[k] = change[k]; - } -} -function changeTile(gameId, pieceIdx, change) { - for (let k of Object.keys(change)) { - const piece = Util.decodePiece(GAMES[gameId].puzzle.tiles[pieceIdx]); - // @ts-ignore - piece[k] = change[k]; - GAMES[gameId].puzzle.tiles[pieceIdx] = Util.encodePiece(piece); - } -} -const getPiece = (gameId, pieceIdx) => { - return Util.decodePiece(GAMES[gameId].puzzle.tiles[pieceIdx]); -}; -const getPieceGroup = (gameId, tileIdx) => { - const tile = getPiece(gameId, tileIdx); - return tile.group; -}; -const getFinalPiecePos = (gameId, tileIdx) => { - const info = GAMES[gameId].puzzle.info; - const boardPos = { - x: (info.table.width - info.width) / 2, - y: (info.table.height - info.height) / 2 - }; - const srcPos = srcPosByTileIdx(gameId, tileIdx); - return Geometry.pointAdd(boardPos, srcPos); -}; -const getPiecePos = (gameId, tileIdx) => { - const tile = getPiece(gameId, tileIdx); - return tile.pos; -}; -// todo: instead, just make the table bigger and use that :) -const getBounds = (gameId) => { - const tw = getTableWidth(gameId); - const th = getTableHeight(gameId); - const overX = Math.round(tw / 4); - const overY = Math.round(th / 4); - return { - x: 0 - overX, - y: 0 - overY, - w: tw + 2 * overX, - h: th + 2 * overY, - }; -}; -const getPieceBounds = (gameId, tileIdx) => { - const s = getPieceSize(gameId); - const tile = getPiece(gameId, tileIdx); - return { - x: tile.pos.x, - y: tile.pos.y, - w: s, - h: s, - }; -}; -const getTileZIndex = (gameId, tileIdx) => { - const tile = getPiece(gameId, tileIdx); - return tile.z; -}; -const getFirstOwnedPieceIdx = (gameId, playerId) => { - for (let t of GAMES[gameId].puzzle.tiles) { - const tile = Util.decodePiece(t); - if (tile.owner === playerId) { - return tile.idx; - } - } - return -1; -}; -const getFirstOwnedPiece = (gameId, playerId) => { - const idx = getFirstOwnedPieceIdx(gameId, playerId); - return idx < 0 ? null : GAMES[gameId].puzzle.tiles[idx]; -}; -const getPieceDrawOffset = (gameId) => { - return GAMES[gameId].puzzle.info.tileDrawOffset; -}; -const getPieceDrawSize = (gameId) => { - return GAMES[gameId].puzzle.info.tileDrawSize; -}; -const getPieceSize = (gameId) => { - return GAMES[gameId].puzzle.info.tileSize; -}; -const getStartTs = (gameId) => { - return GAMES[gameId].puzzle.data.started; -}; -const getFinishTs = (gameId) => { - return GAMES[gameId].puzzle.data.finished; -}; -const getMaxGroup = (gameId) => { - return GAMES[gameId].puzzle.data.maxGroup; -}; -const getMaxZIndex = (gameId) => { - return GAMES[gameId].puzzle.data.maxZ; -}; -const getMaxZIndexByTileIdxs = (gameId, tileIdxs) => { - let maxZ = 0; - for (let tileIdx of tileIdxs) { - let tileZIndex = getTileZIndex(gameId, tileIdx); - if (tileZIndex > maxZ) { - maxZ = tileZIndex; - } - } - return maxZ; -}; -function srcPosByTileIdx(gameId, tileIdx) { - const info = GAMES[gameId].puzzle.info; - const c = Util.coordByTileIdx(info, tileIdx); - const cx = c.x * info.tileSize; - const cy = c.y * info.tileSize; - return { x: cx, y: cy }; -} -function getSurroundingTilesByIdx(gameId, tileIdx) { - const info = GAMES[gameId].puzzle.info; - const c = Util.coordByTileIdx(info, tileIdx); - return [ - // top - (c.y > 0) ? (tileIdx - info.tilesX) : -1, - // right - (c.x < info.tilesX - 1) ? (tileIdx + 1) : -1, - // bottom - (c.y < info.tilesY - 1) ? (tileIdx + info.tilesX) : -1, - // left - (c.x > 0) ? (tileIdx - 1) : -1, - ]; -} -const setTilesZIndex = (gameId, tileIdxs, zIndex) => { - for (let tilesIdx of tileIdxs) { - changeTile(gameId, tilesIdx, { z: zIndex }); - } -}; -const moveTileDiff = (gameId, tileIdx, diff) => { - const oldPos = getPiecePos(gameId, tileIdx); - const pos = Geometry.pointAdd(oldPos, diff); - changeTile(gameId, tileIdx, { pos }); -}; -const moveTilesDiff = (gameId, tileIdxs, diff) => { - const drawSize = getPieceDrawSize(gameId); - const bounds = getBounds(gameId); - const cappedDiff = diff; - for (let tileIdx of tileIdxs) { - const t = getPiece(gameId, tileIdx); - if (t.pos.x + diff.x < bounds.x) { - cappedDiff.x = Math.max(bounds.x - t.pos.x, cappedDiff.x); - } - else if (t.pos.x + drawSize + diff.x > bounds.x + bounds.w) { - cappedDiff.x = Math.min(bounds.x + bounds.w - t.pos.x + drawSize, cappedDiff.x); - } - if (t.pos.y + diff.y < bounds.y) { - cappedDiff.y = Math.max(bounds.y - t.pos.y, cappedDiff.y); - } - else if (t.pos.y + drawSize + diff.y > bounds.y + bounds.h) { - cappedDiff.y = Math.min(bounds.y + bounds.h - t.pos.y + drawSize, cappedDiff.y); - } - } - for (let tileIdx of tileIdxs) { - moveTileDiff(gameId, tileIdx, cappedDiff); - } -}; -const finishTiles = (gameId, tileIdxs) => { - for (let tileIdx of tileIdxs) { - changeTile(gameId, tileIdx, { owner: -1, z: 1 }); - } -}; -const setTilesOwner = (gameId, tileIdxs, owner) => { - for (let tileIdx of tileIdxs) { - changeTile(gameId, tileIdx, { owner }); - } -}; -// get all grouped tiles for a tile -function getGroupedPieceIdxs(gameId, pieceIdx) { - const pieces = GAMES[gameId].puzzle.tiles; - const piece = Util.decodePiece(pieces[pieceIdx]); - const grouped = []; - if (piece.group) { - for (let other of pieces) { - const otherPiece = Util.decodePiece(other); - if (otherPiece.group === piece.group) { - grouped.push(otherPiece.idx); - } - } - } - else { - grouped.push(piece.idx); - } - return grouped; -} -// Returns the index of the puzzle tile with the highest z index -// that is not finished yet and that matches the position -const freePieceIdxByPos = (gameId, pos) => { - let info = GAMES[gameId].puzzle.info; - let pieces = GAMES[gameId].puzzle.tiles; - let maxZ = -1; - let pieceIdx = -1; - for (let idx = 0; idx < pieces.length; idx++) { - const piece = Util.decodePiece(pieces[idx]); - if (piece.owner !== 0) { - continue; - } - const collisionRect = { - x: piece.pos.x, - y: piece.pos.y, - w: info.tileSize, - h: info.tileSize, - }; - if (Geometry.pointInBounds(pos, collisionRect)) { - if (maxZ === -1 || piece.z > maxZ) { - maxZ = piece.z; - pieceIdx = idx; - } - } - } - return pieceIdx; -}; -const getPlayerBgColor = (gameId, playerId) => { - const p = getPlayer(gameId, playerId); - return p ? p.bgcolor : null; -}; -const getPlayerColor = (gameId, playerId) => { - const p = getPlayer(gameId, playerId); - return p ? p.color : null; -}; -const getPlayerName = (gameId, playerId) => { - const p = getPlayer(gameId, playerId); - return p ? p.name : null; -}; -const getPlayerPoints = (gameId, playerId) => { - const p = getPlayer(gameId, playerId); - return p ? p.points : 0; -}; -// determine if two tiles are grouped together -const areGrouped = (gameId, tileIdx1, tileIdx2) => { - const g1 = getPieceGroup(gameId, tileIdx1); - const g2 = getPieceGroup(gameId, tileIdx2); - return !!(g1 && g1 === g2); -}; -const getTableWidth = (gameId) => { - return GAMES[gameId].puzzle.info.table.width; -}; -const getTableHeight = (gameId) => { - return GAMES[gameId].puzzle.info.table.height; -}; -const getPuzzle = (gameId) => { - return GAMES[gameId].puzzle; -}; -const getRng = (gameId) => { - return GAMES[gameId].rng.obj; -}; -const getPuzzleWidth = (gameId) => { - return GAMES[gameId].puzzle.info.width; -}; -const getPuzzleHeight = (gameId) => { - return GAMES[gameId].puzzle.info.height; -}; -function handleInput$1(gameId, playerId, input, ts) { - const puzzle = GAMES[gameId].puzzle; - const evtInfo = getEvtInfo(gameId, playerId); - const changes = []; - const _dataChange = () => { - changes.push([Protocol.CHANGE_DATA, puzzle.data]); - }; - const _tileChange = (tileIdx) => { - changes.push([ - Protocol.CHANGE_TILE, - Util.encodePiece(getPiece(gameId, tileIdx)), - ]); - }; - const _tileChanges = (tileIdxs) => { - for (const tileIdx of tileIdxs) { - _tileChange(tileIdx); - } - }; - const _playerChange = () => { - const player = getPlayer(gameId, playerId); - if (!player) { - return; - } - changes.push([ - Protocol.CHANGE_PLAYER, - Util.encodePlayer(player), - ]); - }; - // put both tiles (and their grouped tiles) in the same group - const groupTiles = (gameId, tileIdx1, tileIdx2) => { - const tiles = GAMES[gameId].puzzle.tiles; - const group1 = getPieceGroup(gameId, tileIdx1); - const group2 = getPieceGroup(gameId, tileIdx2); - let group; - const searchGroups = []; - if (group1) { - searchGroups.push(group1); - } - if (group2) { - searchGroups.push(group2); - } - if (group1) { - group = group1; - } - else if (group2) { - group = group2; - } - else { - const maxGroup = getMaxGroup(gameId) + 1; - changeData(gameId, { maxGroup }); - _dataChange(); - group = getMaxGroup(gameId); - } - changeTile(gameId, tileIdx1, { group }); - _tileChange(tileIdx1); - changeTile(gameId, tileIdx2, { group }); - _tileChange(tileIdx2); - // TODO: strange - if (searchGroups.length > 0) { - for (const t of tiles) { - const piece = Util.decodePiece(t); - if (searchGroups.includes(piece.group)) { - changeTile(gameId, piece.idx, { group }); - _tileChange(piece.idx); - } - } - } - }; - const type = input[0]; - if (type === Protocol.INPUT_EV_BG_COLOR) { - const bgcolor = input[1]; - changePlayer(gameId, playerId, { bgcolor, ts }); - _playerChange(); - } - else if (type === Protocol.INPUT_EV_PLAYER_COLOR) { - const color = input[1]; - changePlayer(gameId, playerId, { color, ts }); - _playerChange(); - } - else if (type === Protocol.INPUT_EV_PLAYER_NAME) { - const name = `${input[1]}`.substr(0, 16); - changePlayer(gameId, playerId, { name, ts }); - _playerChange(); - } - else if (type === Protocol.INPUT_EV_MOUSE_DOWN) { - const x = input[1]; - const y = input[2]; - const pos = { x, y }; - changePlayer(gameId, playerId, { d: 1, ts }); - _playerChange(); - evtInfo._last_mouse_down = pos; - const tileIdxAtPos = freePieceIdxByPos(gameId, pos); - if (tileIdxAtPos >= 0) { - let maxZ = getMaxZIndex(gameId) + 1; - changeData(gameId, { maxZ }); - _dataChange(); - const tileIdxs = getGroupedPieceIdxs(gameId, tileIdxAtPos); - setTilesZIndex(gameId, tileIdxs, getMaxZIndex(gameId)); - setTilesOwner(gameId, tileIdxs, playerId); - _tileChanges(tileIdxs); - } - evtInfo._last_mouse = pos; - } - else if (type === Protocol.INPUT_EV_MOUSE_MOVE) { - const x = input[1]; - const y = input[2]; - const pos = { x, y }; - if (evtInfo._last_mouse_down === null) { - // player is just moving the hand - changePlayer(gameId, playerId, { x, y, ts }); - _playerChange(); - } - else { - let tileIdx = getFirstOwnedPieceIdx(gameId, playerId); - if (tileIdx >= 0) { - // player is moving a tile (and hand) - changePlayer(gameId, playerId, { x, y, ts }); - _playerChange(); - // check if pos is on the tile, otherwise dont move - // (mouse could be out of table, but tile stays on it) - const tileIdxs = getGroupedPieceIdxs(gameId, tileIdx); - let anyOk = Geometry.pointInBounds(pos, getBounds(gameId)) - && Geometry.pointInBounds(evtInfo._last_mouse_down, getBounds(gameId)); - for (let idx of tileIdxs) { - const bounds = getPieceBounds(gameId, idx); - if (Geometry.pointInBounds(pos, bounds)) { - anyOk = true; - break; - } - } - if (anyOk) { - const diffX = x - evtInfo._last_mouse_down.x; - const diffY = y - evtInfo._last_mouse_down.y; - const diff = { x: diffX, y: diffY }; - moveTilesDiff(gameId, tileIdxs, diff); - _tileChanges(tileIdxs); - } - } - else { - // player is just moving map, so no change in position! - changePlayer(gameId, playerId, { ts }); - _playerChange(); - } - evtInfo._last_mouse_down = pos; - } - evtInfo._last_mouse = pos; - } - else if (type === Protocol.INPUT_EV_MOUSE_UP) { - const x = input[1]; - const y = input[2]; - const pos = { x, y }; - const d = 0; - evtInfo._last_mouse_down = null; - let tileIdx = getFirstOwnedPieceIdx(gameId, playerId); - if (tileIdx >= 0) { - // drop the tile(s) - let tileIdxs = getGroupedPieceIdxs(gameId, tileIdx); - setTilesOwner(gameId, tileIdxs, 0); - _tileChanges(tileIdxs); - // Check if the tile was dropped near the final location - let tilePos = getPiecePos(gameId, tileIdx); - let finalPos = getFinalPiecePos(gameId, tileIdx); - if (Geometry.pointDistance(finalPos, tilePos) < puzzle.info.snapDistance) { - let diff = Geometry.pointSub(finalPos, tilePos); - // Snap the tile to the final destination - moveTilesDiff(gameId, tileIdxs, diff); - finishTiles(gameId, tileIdxs); - _tileChanges(tileIdxs); - let points = getPlayerPoints(gameId, playerId); - if (getScoreMode(gameId) === ScoreMode.FINAL) { - points += tileIdxs.length; - } - else if (getScoreMode(gameId) === ScoreMode.ANY) { - points += 1; - } - else ; - changePlayer(gameId, playerId, { d, ts, points }); - _playerChange(); - // check if the puzzle is finished - if (getFinishedPiecesCount(gameId) === getPieceCount(gameId)) { - changeData(gameId, { finished: ts }); - _dataChange(); - } - } - else { - // Snap to other tiles - const check = (gameId, tileIdx, otherTileIdx, off) => { - let info = GAMES[gameId].puzzle.info; - if (otherTileIdx < 0) { - return false; - } - if (areGrouped(gameId, tileIdx, otherTileIdx)) { - return false; - } - const tilePos = getPiecePos(gameId, tileIdx); - const dstPos = Geometry.pointAdd(getPiecePos(gameId, otherTileIdx), { x: off[0] * info.tileSize, y: off[1] * info.tileSize }); - if (Geometry.pointDistance(tilePos, dstPos) < info.snapDistance) { - let diff = Geometry.pointSub(dstPos, tilePos); - let tileIdxs = getGroupedPieceIdxs(gameId, tileIdx); - moveTilesDiff(gameId, tileIdxs, diff); - groupTiles(gameId, tileIdx, otherTileIdx); - tileIdxs = getGroupedPieceIdxs(gameId, tileIdx); - const zIndex = getMaxZIndexByTileIdxs(gameId, tileIdxs); - setTilesZIndex(gameId, tileIdxs, zIndex); - _tileChanges(tileIdxs); - return true; - } - return false; - }; - let snapped = false; - for (let tileIdxTmp of getGroupedPieceIdxs(gameId, tileIdx)) { - let othersIdxs = getSurroundingTilesByIdx(gameId, tileIdxTmp); - if (check(gameId, tileIdxTmp, othersIdxs[0], [0, 1]) // top - || check(gameId, tileIdxTmp, othersIdxs[1], [-1, 0]) // right - || check(gameId, tileIdxTmp, othersIdxs[2], [0, -1]) // bottom - || check(gameId, tileIdxTmp, othersIdxs[3], [1, 0]) // left - ) { - snapped = true; - break; - } - } - if (snapped && getScoreMode(gameId) === ScoreMode.ANY) { - const points = getPlayerPoints(gameId, playerId) + 1; - changePlayer(gameId, playerId, { d, ts, points }); - _playerChange(); - } - else { - changePlayer(gameId, playerId, { d, ts }); - _playerChange(); - } - } - } - else { - changePlayer(gameId, playerId, { d, ts }); - _playerChange(); - } - evtInfo._last_mouse = pos; - } - else if (type === Protocol.INPUT_EV_ZOOM_IN) { - const x = input[1]; - const y = input[2]; - changePlayer(gameId, playerId, { x, y, ts }); - _playerChange(); - evtInfo._last_mouse = { x, y }; - } - else if (type === Protocol.INPUT_EV_ZOOM_OUT) { - const x = input[1]; - const y = input[2]; - changePlayer(gameId, playerId, { x, y, ts }); - _playerChange(); - evtInfo._last_mouse = { x, y }; - } - else { - changePlayer(gameId, playerId, { ts }); - _playerChange(); - } - setEvtInfo(gameId, playerId, evtInfo); - return changes; -} -var GameCommon = { - setGame, - exists: exists$1, - playerExists, - getActivePlayers, - getIdlePlayers, - addPlayer: addPlayer$1, - getFinishedPiecesCount, - getPieceCount, - getImageUrl, - setImageUrl, - get: get$1, - getAllGames, - getPlayerBgColor, - getPlayerColor, - getPlayerName, - getPlayerIndexById, - getPlayerIdByIndex, - changePlayer, - setPlayer, - setPiece, - setPuzzleData, - getTableWidth, - getTableHeight, - getPuzzle, - getRng, - getPuzzleWidth, - getPuzzleHeight, - getPiecesSortedByZIndex, - getFirstOwnedPiece, - getPieceDrawOffset, - getPieceDrawSize, - getFinalPiecePos, - getStartTs, - getFinishTs, - handleInput: handleInput$1, -}; +})(ScoreMode || (ScoreMode = {})); class Rng { constructor(seed) { @@ -916,7 +33,7 @@ class Rng { random(min, max) { this.rand_high = ((this.rand_high << 16) + (this.rand_high >> 16) + this.rand_low) & 0xffffffff; this.rand_low = (this.rand_low + this.rand_high) & 0xffffffff; - var n = (this.rand_high >>> 0) / 0xffffffff; + const n = (this.rand_high >>> 0) / 0xffffffff; return (min + n * (max - min + 1)) | 0; } // get one random item from the given array @@ -976,7 +93,9 @@ const logger = (...pre) => { }; }; // get a unique id -const uniqId = () => Date.now().toString(36) + Math.random().toString(36).substring(2); +const uniqId = () => { + return Date.now().toString(36) + Math.random().toString(36).substring(2); +}; function encodeShape(data) { /* encoded in 1 byte: 00000000 @@ -1063,17 +182,17 @@ function decodeGame(data) { scoreMode: data[6], }; } -function coordByTileIdx(info, tileIdx) { +function coordByPieceIdx(info, pieceIdx) { const wTiles = info.width / info.tileSize; return { - x: tileIdx % wTiles, - y: Math.floor(tileIdx / wTiles), + x: pieceIdx % wTiles, + y: Math.floor(pieceIdx / wTiles), }; } const hash = (str) => { let hash = 0; for (let i = 0; i < str.length; i++) { - let chr = str.charCodeAt(i); + const chr = str.charCodeAt(i); hash = ((hash << 5) - hash) + chr; hash |= 0; // Convert to 32bit integer } @@ -1081,7 +200,7 @@ const hash = (str) => { }; function asQueryArgs(data) { const q = []; - for (let k in data) { + for (const k in data) { const pair = [k, data[k]].map(encodeURIComponent); q.push(pair.join('=')); } @@ -1102,11 +221,11 @@ var Util = { decodePlayer, encodeGame, decodeGame, - coordByTileIdx, + coordByPieceIdx, asQueryArgs, }; -const log$4 = logger('WebSocketServer.js'); +const log$5 = logger('WebSocketServer.js'); /* Example config @@ -1144,12 +263,12 @@ class WebSocketServer { this._websocketserver.on('connection', (socket, request) => { const pathname = new URL(this.config.connectstring).pathname; if (request.url.indexOf(pathname) !== 0) { - log$4.log('bad request url: ', request.url); + log$5.log('bad request url: ', request.url); socket.close(); return; } socket.on('message', (data) => { - log$4.log(`ws`, socket.protocol, data); + log$5.log(`ws`, socket.protocol, data); this.evt.dispatch('message', { socket, data }); }); socket.on('close', () => { @@ -1167,6 +286,888 @@ class WebSocketServer { } } +/* +SERVER_CLIENT_MESSAGE_PROTOCOL +NOTE: clients always send game id and their id + when creating sockets (via socket.protocol), so + this doesn't need to be set in each message data + +NOTE: first value in the array is always the type of event/message + when describing them below, the value each has is used + instead of writing EVENT_TYPE or something ismilar + + +EV_CLIENT_EVENT: event triggered by clients and sent to server +[ + EV_CLIENT_EVENT, // constant value, type of event + CLIENT_SEQ, // sequence number sent by client. + EV_DATA, // (eg. mouse input info) +] + +EV_SERVER_EVENT: event sent to clients after recieving a client + event and processing it +[ + EV_SERVER_EVENT, // constant value, type of event + CLIENT_ID, // user who sent the client event + CLIENT_SEQ, // sequence number of the client event msg + CHANGES_TRIGGERED_BY_CLIENT_EVENT, +] + +EV_CLIENT_INIT: event sent by client to enter a game +[ + EV_CLIENT_INIT, // constant value, type of event +] + +EV_SERVER_INIT: event sent to one client after that client + connects to a game +[ + EV_SERVER_INIT, // constant value, type of event + GAME, // complete game instance required by + // client to build client side of the game +] +*/ +const EV_SERVER_EVENT = 1; +const EV_SERVER_INIT = 4; +const EV_CLIENT_EVENT = 2; +const EV_CLIENT_INIT = 3; +const LOG_HEADER = 1; +const LOG_ADD_PLAYER = 2; +const LOG_UPDATE_PLAYER = 4; +const LOG_HANDLE_INPUT = 3; +const INPUT_EV_MOUSE_DOWN = 1; +const INPUT_EV_MOUSE_UP = 2; +const INPUT_EV_MOUSE_MOVE = 3; +const INPUT_EV_ZOOM_IN = 4; +const INPUT_EV_ZOOM_OUT = 5; +const INPUT_EV_BG_COLOR = 6; +const INPUT_EV_PLAYER_COLOR = 7; +const INPUT_EV_PLAYER_NAME = 8; +const INPUT_EV_MOVE = 9; +const INPUT_EV_TOGGLE_PREVIEW = 10; +const CHANGE_DATA = 1; +const CHANGE_TILE = 2; +const CHANGE_PLAYER = 3; +var Protocol = { + EV_SERVER_EVENT, + EV_SERVER_INIT, + EV_CLIENT_EVENT, + EV_CLIENT_INIT, + LOG_HEADER, + LOG_ADD_PLAYER, + LOG_UPDATE_PLAYER, + LOG_HANDLE_INPUT, + INPUT_EV_MOVE, + INPUT_EV_MOUSE_DOWN, + INPUT_EV_MOUSE_UP, + INPUT_EV_MOUSE_MOVE, + INPUT_EV_ZOOM_IN, + INPUT_EV_ZOOM_OUT, + INPUT_EV_BG_COLOR, + INPUT_EV_PLAYER_COLOR, + INPUT_EV_PLAYER_NAME, + INPUT_EV_TOGGLE_PREVIEW, + CHANGE_DATA, + CHANGE_TILE, + CHANGE_PLAYER, +}; + +function pointSub(a, b) { + return { x: a.x - b.x, y: a.y - b.y }; +} +function pointAdd(a, b) { + return { x: a.x + b.x, y: a.y + b.y }; +} +function pointDistance(a, b) { + const diffX = a.x - b.x; + const diffY = a.y - b.y; + return Math.sqrt(diffX * diffX + diffY * diffY); +} +function pointInBounds(pt, rect) { + return pt.x >= rect.x + && pt.x <= rect.x + rect.w + && pt.y >= rect.y + && pt.y <= rect.y + rect.h; +} +function rectCenter(rect) { + return { + x: rect.x + (rect.w / 2), + y: rect.y + (rect.h / 2), + }; +} +/** + * Returns a rectangle with same dimensions as the given one, but + * location (x/y) moved by x and y. + * + * @param {x, y, w,, h} rect + * @param number x + * @param number y + * @returns {x, y, w, h} + */ +function rectMoved(rect, x, y) { + return { + x: rect.x + x, + y: rect.y + y, + w: rect.w, + h: rect.h, + }; +} +/** + * Returns true if the rectangles overlap, including their borders. + * + * @param {x, y, w, h} rectA + * @param {x, y, w, h} rectB + * @returns bool + */ +function rectsOverlap(rectA, rectB) { + return !(rectB.x > (rectA.x + rectA.w) + || rectA.x > (rectB.x + rectB.w) + || rectB.y > (rectA.y + rectA.h) + || rectA.y > (rectB.y + rectB.h)); +} +function rectCenterDistance(rectA, rectB) { + return pointDistance(rectCenter(rectA), rectCenter(rectB)); +} +var Geometry = { + pointSub, + pointAdd, + pointDistance, + pointInBounds, + rectCenter, + rectMoved, + rectCenterDistance, + rectsOverlap, +}; + +const MS = 1; +const SEC = MS * 1000; +const MIN = SEC * 60; +const HOUR = MIN * 60; +const DAY = HOUR * 24; +const timestamp = () => { + const d = new Date(); + return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds()); +}; +const durationStr = (duration) => { + const d = Math.floor(duration / DAY); + duration = duration % DAY; + const h = Math.floor(duration / HOUR); + duration = duration % HOUR; + const m = Math.floor(duration / MIN); + duration = duration % MIN; + const s = Math.floor(duration / SEC); + return `${d}d ${h}h ${m}m ${s}s`; +}; +const timeDiffStr = (from, to) => durationStr(to - from); +var Time = { + MS, + SEC, + MIN, + HOUR, + DAY, + timestamp, + timeDiffStr, + durationStr, +}; + +const IDLE_TIMEOUT_SEC = 30; +// Map +const GAMES = {}; +function exists$1(gameId) { + return (!!GAMES[gameId]) || false; +} +function __createPlayerObject(id, ts) { + return { + id: id, + x: 0, + y: 0, + d: 0, + name: null, + color: null, + bgcolor: null, + points: 0, + ts: ts, + }; +} +function setGame(gameId, game) { + GAMES[gameId] = game; +} +function getPlayerIndexById(gameId, playerId) { + let i = 0; + for (const player of GAMES[gameId].players) { + if (Util.decodePlayer(player).id === playerId) { + return i; + } + i++; + } + return -1; +} +function getPlayerIdByIndex(gameId, playerIndex) { + if (GAMES[gameId].players.length > playerIndex) { + return Util.decodePlayer(GAMES[gameId].players[playerIndex]).id; + } + return null; +} +function getPlayer(gameId, playerId) { + const idx = getPlayerIndexById(gameId, playerId); + if (idx === -1) { + return null; + } + return Util.decodePlayer(GAMES[gameId].players[idx]); +} +function setPlayer(gameId, playerId, player) { + const idx = getPlayerIndexById(gameId, playerId); + if (idx === -1) { + GAMES[gameId].players.push(Util.encodePlayer(player)); + } + else { + GAMES[gameId].players[idx] = Util.encodePlayer(player); + } +} +function setPiece(gameId, pieceIdx, piece) { + GAMES[gameId].puzzle.tiles[pieceIdx] = Util.encodePiece(piece); +} +function setPuzzleData(gameId, data) { + GAMES[gameId].puzzle.data = data; +} +function playerExists(gameId, playerId) { + const idx = getPlayerIndexById(gameId, playerId); + return idx !== -1; +} +function getActivePlayers(gameId, ts) { + const minTs = ts - IDLE_TIMEOUT_SEC * Time.SEC; + return getAllPlayers(gameId).filter((p) => p.ts >= minTs); +} +function getIdlePlayers(gameId, ts) { + const minTs = ts - IDLE_TIMEOUT_SEC * Time.SEC; + return getAllPlayers(gameId).filter((p) => p.ts < minTs && p.points > 0); +} +function addPlayer$1(gameId, playerId, ts) { + if (!playerExists(gameId, playerId)) { + setPlayer(gameId, playerId, __createPlayerObject(playerId, ts)); + } + else { + changePlayer(gameId, playerId, { ts }); + } +} +function getEvtInfo(gameId, playerId) { + if (playerId in GAMES[gameId].evtInfos) { + return GAMES[gameId].evtInfos[playerId]; + } + return { + _last_mouse: null, + _last_mouse_down: null, + }; +} +function setEvtInfo(gameId, playerId, evtInfo) { + GAMES[gameId].evtInfos[playerId] = evtInfo; +} +function getAllGames() { + return Object.values(GAMES).sort((a, b) => { + // when both have same finished state, sort by started + if (isFinished(a.id) === isFinished(b.id)) { + return b.puzzle.data.started - a.puzzle.data.started; + } + // otherwise, sort: unfinished, finished + return isFinished(a.id) ? 1 : -1; + }); +} +function getAllPlayers(gameId) { + return GAMES[gameId] + ? GAMES[gameId].players.map(Util.decodePlayer) + : []; +} +function get$1(gameId) { + return GAMES[gameId] || null; +} +function getPieceCount(gameId) { + return GAMES[gameId].puzzle.tiles.length; +} +function getImageUrl(gameId) { + return GAMES[gameId].puzzle.info.imageUrl; +} +function setImageUrl(gameId, imageUrl) { + GAMES[gameId].puzzle.info.imageUrl = imageUrl; +} +function getScoreMode(gameId) { + return GAMES[gameId].scoreMode || ScoreMode.FINAL; +} +function isFinished(gameId) { + return getFinishedPiecesCount(gameId) === getPieceCount(gameId); +} +function getFinishedPiecesCount(gameId) { + let count = 0; + for (const t of GAMES[gameId].puzzle.tiles) { + if (Util.decodePiece(t).owner === -1) { + count++; + } + } + return count; +} +function getPiecesSortedByZIndex(gameId) { + const pieces = GAMES[gameId].puzzle.tiles.map(Util.decodePiece); + return pieces.sort((t1, t2) => t1.z - t2.z); +} +function changePlayer(gameId, playerId, change) { + const player = getPlayer(gameId, playerId); + if (player === null) { + return; + } + for (const k of Object.keys(change)) { + // @ts-ignore + player[k] = change[k]; + } + setPlayer(gameId, playerId, player); +} +function changeData(gameId, change) { + for (const k of Object.keys(change)) { + // @ts-ignore + GAMES[gameId].puzzle.data[k] = change[k]; + } +} +function changePiece(gameId, pieceIdx, change) { + for (const k of Object.keys(change)) { + const piece = Util.decodePiece(GAMES[gameId].puzzle.tiles[pieceIdx]); + // @ts-ignore + piece[k] = change[k]; + GAMES[gameId].puzzle.tiles[pieceIdx] = Util.encodePiece(piece); + } +} +const getPiece = (gameId, pieceIdx) => { + return Util.decodePiece(GAMES[gameId].puzzle.tiles[pieceIdx]); +}; +const getPieceGroup = (gameId, tileIdx) => { + const tile = getPiece(gameId, tileIdx); + return tile.group; +}; +const getFinalPiecePos = (gameId, tileIdx) => { + const info = GAMES[gameId].puzzle.info; + const boardPos = { + x: (info.table.width - info.width) / 2, + y: (info.table.height - info.height) / 2 + }; + const srcPos = srcPosByTileIdx(gameId, tileIdx); + return Geometry.pointAdd(boardPos, srcPos); +}; +const getPiecePos = (gameId, tileIdx) => { + const tile = getPiece(gameId, tileIdx); + return tile.pos; +}; +// todo: instead, just make the table bigger and use that :) +const getBounds = (gameId) => { + const tw = getTableWidth(gameId); + const th = getTableHeight(gameId); + const overX = Math.round(tw / 4); + const overY = Math.round(th / 4); + return { + x: 0 - overX, + y: 0 - overY, + w: tw + 2 * overX, + h: th + 2 * overY, + }; +}; +const getPieceBounds = (gameId, tileIdx) => { + const s = getPieceSize(gameId); + const tile = getPiece(gameId, tileIdx); + return { + x: tile.pos.x, + y: tile.pos.y, + w: s, + h: s, + }; +}; +const getPieceZIndex = (gameId, pieceIdx) => { + return getPiece(gameId, pieceIdx).z; +}; +const getFirstOwnedPieceIdx = (gameId, playerId) => { + for (const t of GAMES[gameId].puzzle.tiles) { + const tile = Util.decodePiece(t); + if (tile.owner === playerId) { + return tile.idx; + } + } + return -1; +}; +const getFirstOwnedPiece = (gameId, playerId) => { + const idx = getFirstOwnedPieceIdx(gameId, playerId); + return idx < 0 ? null : GAMES[gameId].puzzle.tiles[idx]; +}; +const getPieceDrawOffset = (gameId) => { + return GAMES[gameId].puzzle.info.tileDrawOffset; +}; +const getPieceDrawSize = (gameId) => { + return GAMES[gameId].puzzle.info.tileDrawSize; +}; +const getPieceSize = (gameId) => { + return GAMES[gameId].puzzle.info.tileSize; +}; +const getStartTs = (gameId) => { + return GAMES[gameId].puzzle.data.started; +}; +const getFinishTs = (gameId) => { + return GAMES[gameId].puzzle.data.finished; +}; +const getMaxGroup = (gameId) => { + return GAMES[gameId].puzzle.data.maxGroup; +}; +const getMaxZIndex = (gameId) => { + return GAMES[gameId].puzzle.data.maxZ; +}; +const getMaxZIndexByPieceIdxs = (gameId, pieceIdxs) => { + let maxZ = 0; + for (const pieceIdx of pieceIdxs) { + const curZ = getPieceZIndex(gameId, pieceIdx); + if (curZ > maxZ) { + maxZ = curZ; + } + } + return maxZ; +}; +function srcPosByTileIdx(gameId, tileIdx) { + const info = GAMES[gameId].puzzle.info; + const c = Util.coordByPieceIdx(info, tileIdx); + const cx = c.x * info.tileSize; + const cy = c.y * info.tileSize; + return { x: cx, y: cy }; +} +function getSurroundingTilesByIdx(gameId, tileIdx) { + const info = GAMES[gameId].puzzle.info; + const c = Util.coordByPieceIdx(info, tileIdx); + return [ + // top + (c.y > 0) ? (tileIdx - info.tilesX) : -1, + // right + (c.x < info.tilesX - 1) ? (tileIdx + 1) : -1, + // bottom + (c.y < info.tilesY - 1) ? (tileIdx + info.tilesX) : -1, + // left + (c.x > 0) ? (tileIdx - 1) : -1, + ]; +} +const setPiecesZIndex = (gameId, tileIdxs, zIndex) => { + for (const tilesIdx of tileIdxs) { + changePiece(gameId, tilesIdx, { z: zIndex }); + } +}; +const moveTileDiff = (gameId, tileIdx, diff) => { + const oldPos = getPiecePos(gameId, tileIdx); + const pos = Geometry.pointAdd(oldPos, diff); + changePiece(gameId, tileIdx, { pos }); +}; +const movePiecesDiff = (gameId, pieceIdxs, diff) => { + const drawSize = getPieceDrawSize(gameId); + const bounds = getBounds(gameId); + const cappedDiff = diff; + for (const pieceIdx of pieceIdxs) { + const t = getPiece(gameId, pieceIdx); + if (t.pos.x + diff.x < bounds.x) { + cappedDiff.x = Math.max(bounds.x - t.pos.x, cappedDiff.x); + } + else if (t.pos.x + drawSize + diff.x > bounds.x + bounds.w) { + cappedDiff.x = Math.min(bounds.x + bounds.w - t.pos.x + drawSize, cappedDiff.x); + } + if (t.pos.y + diff.y < bounds.y) { + cappedDiff.y = Math.max(bounds.y - t.pos.y, cappedDiff.y); + } + else if (t.pos.y + drawSize + diff.y > bounds.y + bounds.h) { + cappedDiff.y = Math.min(bounds.y + bounds.h - t.pos.y + drawSize, cappedDiff.y); + } + } + for (const pieceIdx of pieceIdxs) { + moveTileDiff(gameId, pieceIdx, cappedDiff); + } +}; +const finishPieces = (gameId, pieceIdxs) => { + for (const pieceIdx of pieceIdxs) { + changePiece(gameId, pieceIdx, { owner: -1, z: 1 }); + } +}; +const setTilesOwner = (gameId, pieceIdxs, owner) => { + for (const pieceIdx of pieceIdxs) { + changePiece(gameId, pieceIdx, { owner }); + } +}; +// get all grouped tiles for a tile +function getGroupedPieceIdxs(gameId, pieceIdx) { + const pieces = GAMES[gameId].puzzle.tiles; + const piece = Util.decodePiece(pieces[pieceIdx]); + const grouped = []; + if (piece.group) { + for (const other of pieces) { + const otherPiece = Util.decodePiece(other); + if (otherPiece.group === piece.group) { + grouped.push(otherPiece.idx); + } + } + } + else { + grouped.push(piece.idx); + } + return grouped; +} +// Returns the index of the puzzle tile with the highest z index +// that is not finished yet and that matches the position +const freePieceIdxByPos = (gameId, pos) => { + const info = GAMES[gameId].puzzle.info; + const pieces = GAMES[gameId].puzzle.tiles; + let maxZ = -1; + let pieceIdx = -1; + for (let idx = 0; idx < pieces.length; idx++) { + const piece = Util.decodePiece(pieces[idx]); + if (piece.owner !== 0) { + continue; + } + const collisionRect = { + x: piece.pos.x, + y: piece.pos.y, + w: info.tileSize, + h: info.tileSize, + }; + if (Geometry.pointInBounds(pos, collisionRect)) { + if (maxZ === -1 || piece.z > maxZ) { + maxZ = piece.z; + pieceIdx = idx; + } + } + } + return pieceIdx; +}; +const getPlayerBgColor = (gameId, playerId) => { + const p = getPlayer(gameId, playerId); + return p ? p.bgcolor : null; +}; +const getPlayerColor = (gameId, playerId) => { + const p = getPlayer(gameId, playerId); + return p ? p.color : null; +}; +const getPlayerName = (gameId, playerId) => { + const p = getPlayer(gameId, playerId); + return p ? p.name : null; +}; +const getPlayerPoints = (gameId, playerId) => { + const p = getPlayer(gameId, playerId); + return p ? p.points : 0; +}; +// determine if two tiles are grouped together +const areGrouped = (gameId, tileIdx1, tileIdx2) => { + const g1 = getPieceGroup(gameId, tileIdx1); + const g2 = getPieceGroup(gameId, tileIdx2); + return !!(g1 && g1 === g2); +}; +const getTableWidth = (gameId) => { + return GAMES[gameId].puzzle.info.table.width; +}; +const getTableHeight = (gameId) => { + return GAMES[gameId].puzzle.info.table.height; +}; +const getPuzzle = (gameId) => { + return GAMES[gameId].puzzle; +}; +const getRng = (gameId) => { + return GAMES[gameId].rng.obj; +}; +const getPuzzleWidth = (gameId) => { + return GAMES[gameId].puzzle.info.width; +}; +const getPuzzleHeight = (gameId) => { + return GAMES[gameId].puzzle.info.height; +}; +function handleInput$1(gameId, playerId, input, ts) { + const puzzle = GAMES[gameId].puzzle; + const evtInfo = getEvtInfo(gameId, playerId); + const changes = []; + const _dataChange = () => { + changes.push([Protocol.CHANGE_DATA, puzzle.data]); + }; + const _pieceChange = (pieceIdx) => { + changes.push([ + Protocol.CHANGE_TILE, + Util.encodePiece(getPiece(gameId, pieceIdx)), + ]); + }; + const _pieceChanges = (pieceIdxs) => { + for (const pieceIdx of pieceIdxs) { + _pieceChange(pieceIdx); + } + }; + const _playerChange = () => { + const player = getPlayer(gameId, playerId); + if (!player) { + return; + } + changes.push([ + Protocol.CHANGE_PLAYER, + Util.encodePlayer(player), + ]); + }; + // put both tiles (and their grouped tiles) in the same group + const groupTiles = (gameId, pieceIdx1, pieceIdx2) => { + const pieces = GAMES[gameId].puzzle.tiles; + const group1 = getPieceGroup(gameId, pieceIdx1); + const group2 = getPieceGroup(gameId, pieceIdx2); + let group; + const searchGroups = []; + if (group1) { + searchGroups.push(group1); + } + if (group2) { + searchGroups.push(group2); + } + if (group1) { + group = group1; + } + else if (group2) { + group = group2; + } + else { + const maxGroup = getMaxGroup(gameId) + 1; + changeData(gameId, { maxGroup }); + _dataChange(); + group = getMaxGroup(gameId); + } + changePiece(gameId, pieceIdx1, { group }); + _pieceChange(pieceIdx1); + changePiece(gameId, pieceIdx2, { group }); + _pieceChange(pieceIdx2); + // TODO: strange + if (searchGroups.length > 0) { + for (const p of pieces) { + const piece = Util.decodePiece(p); + if (searchGroups.includes(piece.group)) { + changePiece(gameId, piece.idx, { group }); + _pieceChange(piece.idx); + } + } + } + }; + const type = input[0]; + if (type === Protocol.INPUT_EV_BG_COLOR) { + const bgcolor = input[1]; + changePlayer(gameId, playerId, { bgcolor, ts }); + _playerChange(); + } + else if (type === Protocol.INPUT_EV_PLAYER_COLOR) { + const color = input[1]; + changePlayer(gameId, playerId, { color, ts }); + _playerChange(); + } + else if (type === Protocol.INPUT_EV_PLAYER_NAME) { + const name = `${input[1]}`.substr(0, 16); + changePlayer(gameId, playerId, { name, ts }); + _playerChange(); + } + else if (type === Protocol.INPUT_EV_MOUSE_DOWN) { + const x = input[1]; + const y = input[2]; + const pos = { x, y }; + changePlayer(gameId, playerId, { d: 1, ts }); + _playerChange(); + evtInfo._last_mouse_down = pos; + const tileIdxAtPos = freePieceIdxByPos(gameId, pos); + if (tileIdxAtPos >= 0) { + const maxZ = getMaxZIndex(gameId) + 1; + changeData(gameId, { maxZ }); + _dataChange(); + const tileIdxs = getGroupedPieceIdxs(gameId, tileIdxAtPos); + setPiecesZIndex(gameId, tileIdxs, getMaxZIndex(gameId)); + setTilesOwner(gameId, tileIdxs, playerId); + _pieceChanges(tileIdxs); + } + evtInfo._last_mouse = pos; + } + else if (type === Protocol.INPUT_EV_MOUSE_MOVE) { + const x = input[1]; + const y = input[2]; + const pos = { x, y }; + if (evtInfo._last_mouse_down === null) { + // player is just moving the hand + changePlayer(gameId, playerId, { x, y, ts }); + _playerChange(); + } + else { + const pieceIdx = getFirstOwnedPieceIdx(gameId, playerId); + if (pieceIdx >= 0) { + // player is moving a tile (and hand) + changePlayer(gameId, playerId, { x, y, ts }); + _playerChange(); + // check if pos is on the tile, otherwise dont move + // (mouse could be out of table, but tile stays on it) + const pieceIdxs = getGroupedPieceIdxs(gameId, pieceIdx); + let anyOk = Geometry.pointInBounds(pos, getBounds(gameId)) + && Geometry.pointInBounds(evtInfo._last_mouse_down, getBounds(gameId)); + for (const idx of pieceIdxs) { + const bounds = getPieceBounds(gameId, idx); + if (Geometry.pointInBounds(pos, bounds)) { + anyOk = true; + break; + } + } + if (anyOk) { + const diffX = x - evtInfo._last_mouse_down.x; + const diffY = y - evtInfo._last_mouse_down.y; + const diff = { x: diffX, y: diffY }; + movePiecesDiff(gameId, pieceIdxs, diff); + _pieceChanges(pieceIdxs); + } + } + else { + // player is just moving map, so no change in position! + changePlayer(gameId, playerId, { ts }); + _playerChange(); + } + evtInfo._last_mouse_down = pos; + } + evtInfo._last_mouse = pos; + } + else if (type === Protocol.INPUT_EV_MOUSE_UP) { + const x = input[1]; + const y = input[2]; + const pos = { x, y }; + const d = 0; + evtInfo._last_mouse_down = null; + const pieceIdx = getFirstOwnedPieceIdx(gameId, playerId); + if (pieceIdx >= 0) { + // drop the tile(s) + const pieceIdxs = getGroupedPieceIdxs(gameId, pieceIdx); + setTilesOwner(gameId, pieceIdxs, 0); + _pieceChanges(pieceIdxs); + // Check if the tile was dropped near the final location + const tilePos = getPiecePos(gameId, pieceIdx); + const finalPos = getFinalPiecePos(gameId, pieceIdx); + if (Geometry.pointDistance(finalPos, tilePos) < puzzle.info.snapDistance) { + const diff = Geometry.pointSub(finalPos, tilePos); + // Snap the tile to the final destination + movePiecesDiff(gameId, pieceIdxs, diff); + finishPieces(gameId, pieceIdxs); + _pieceChanges(pieceIdxs); + let points = getPlayerPoints(gameId, playerId); + if (getScoreMode(gameId) === ScoreMode.FINAL) { + points += pieceIdxs.length; + } + else if (getScoreMode(gameId) === ScoreMode.ANY) { + points += 1; + } + else ; + changePlayer(gameId, playerId, { d, ts, points }); + _playerChange(); + // check if the puzzle is finished + if (getFinishedPiecesCount(gameId) === getPieceCount(gameId)) { + changeData(gameId, { finished: ts }); + _dataChange(); + } + } + else { + // Snap to other tiles + const check = (gameId, tileIdx, otherTileIdx, off) => { + const info = GAMES[gameId].puzzle.info; + if (otherTileIdx < 0) { + return false; + } + if (areGrouped(gameId, tileIdx, otherTileIdx)) { + return false; + } + const tilePos = getPiecePos(gameId, tileIdx); + const dstPos = Geometry.pointAdd(getPiecePos(gameId, otherTileIdx), { x: off[0] * info.tileSize, y: off[1] * info.tileSize }); + if (Geometry.pointDistance(tilePos, dstPos) < info.snapDistance) { + const diff = Geometry.pointSub(dstPos, tilePos); + let pieceIdxs = getGroupedPieceIdxs(gameId, tileIdx); + movePiecesDiff(gameId, pieceIdxs, diff); + groupTiles(gameId, tileIdx, otherTileIdx); + pieceIdxs = getGroupedPieceIdxs(gameId, tileIdx); + const zIndex = getMaxZIndexByPieceIdxs(gameId, pieceIdxs); + setPiecesZIndex(gameId, pieceIdxs, zIndex); + _pieceChanges(pieceIdxs); + return true; + } + return false; + }; + let snapped = false; + for (const pieceIdxTmp of getGroupedPieceIdxs(gameId, pieceIdx)) { + const othersIdxs = getSurroundingTilesByIdx(gameId, pieceIdxTmp); + if (check(gameId, pieceIdxTmp, othersIdxs[0], [0, 1]) // top + || check(gameId, pieceIdxTmp, othersIdxs[1], [-1, 0]) // right + || check(gameId, pieceIdxTmp, othersIdxs[2], [0, -1]) // bottom + || check(gameId, pieceIdxTmp, othersIdxs[3], [1, 0]) // left + ) { + snapped = true; + break; + } + } + if (snapped && getScoreMode(gameId) === ScoreMode.ANY) { + const points = getPlayerPoints(gameId, playerId) + 1; + changePlayer(gameId, playerId, { d, ts, points }); + _playerChange(); + } + else { + changePlayer(gameId, playerId, { d, ts }); + _playerChange(); + } + } + } + else { + changePlayer(gameId, playerId, { d, ts }); + _playerChange(); + } + evtInfo._last_mouse = pos; + } + else if (type === Protocol.INPUT_EV_ZOOM_IN) { + const x = input[1]; + const y = input[2]; + changePlayer(gameId, playerId, { x, y, ts }); + _playerChange(); + evtInfo._last_mouse = { x, y }; + } + else if (type === Protocol.INPUT_EV_ZOOM_OUT) { + const x = input[1]; + const y = input[2]; + changePlayer(gameId, playerId, { x, y, ts }); + _playerChange(); + evtInfo._last_mouse = { x, y }; + } + else { + changePlayer(gameId, playerId, { ts }); + _playerChange(); + } + setEvtInfo(gameId, playerId, evtInfo); + return changes; +} +var GameCommon = { + setGame, + exists: exists$1, + playerExists, + getActivePlayers, + getIdlePlayers, + addPlayer: addPlayer$1, + getFinishedPiecesCount, + getPieceCount, + getImageUrl, + setImageUrl, + get: get$1, + getAllGames, + getPlayerBgColor, + getPlayerColor, + getPlayerName, + getPlayerIndexById, + getPlayerIdByIndex, + changePlayer, + setPlayer, + setPiece, + setPuzzleData, + getTableWidth, + getTableHeight, + getPuzzle, + getRng, + getPuzzleWidth, + getPuzzleHeight, + getPiecesSortedByZIndex, + getFirstOwnedPiece, + getPieceDrawOffset, + getPieceDrawSize, + getFinalPiecePos, + getStartTs, + getFinishTs, + handleInput: handleInput$1, +}; + const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const BASE_DIR = `${__dirname}/../..`; @@ -1234,6 +1235,7 @@ var GameLog = { get, }; +const log$4 = logger('Images.ts'); const resizeImage = async (filename) => { if (!filename.toLowerCase().match(/\.(jpe?g|webp|png)$/)) { return; @@ -1257,19 +1259,21 @@ const resizeImage = async (filename) => { [150, 100], [375, 210], ]; - for (let [w, h] of sizes) { - console.log(w, h, imagePath); - await sharpImg.resize(w, h, { fit: 'contain' }).toFile(`${imageOutPath}-${w}x${h}.webp`); + for (const [w, h] of sizes) { + log$4.info(w, h, imagePath); + await sharpImg + .resize(w, h, { fit: 'contain' }) + .toFile(`${imageOutPath}-${w}x${h}.webp`); } }; async function getExifOrientation(imagePath) { - return new Promise((resolve, reject) => { - new exif.ExifImage({ image: imagePath }, function (error, exifData) { + return new Promise((resolve) => { + new exif.ExifImage({ image: imagePath }, (error, exifData) => { if (error) { resolve(0); } else { - resolve(exifData.image.Orientation); + resolve(exifData.image.Orientation || 0); } }); }); @@ -1279,7 +1283,11 @@ const getTags = (db, imageId) => { select * from categories c inner join image_x_category ixc on c.id = ixc.category_id where ixc.image_id = ?`; - return db._getMany(query, [imageId]); + return db._getMany(query, [imageId]).map(row => ({ + id: parseInt(row.number, 10) || 0, + slug: row.slug, + title: row.tittle, + })); }; const imageFromDb = (db, imageId) => { const i = db.get('images', { id: imageId }); @@ -1293,8 +1301,8 @@ const imageFromDb = (db, imageId) => { created: i.created * 1000, }; }; -const allImagesFromDb = (db, tagSlugs, sort) => { - const sortMap = { +const allImagesFromDb = (db, tagSlugs, orderBy) => { + const orderByMap = { alpha_asc: [{ filename: 1 }], alpha_desc: [{ filename: -1 }], date_asc: [{ created: 1 }], @@ -1319,7 +1327,7 @@ inner join images i on i.id = ixc.image_id ${where.sql}; } wheresRaw['id'] = { '$in': ids }; } - const images = db.getMany('images', wheresRaw, sortMap[sort]); + const images = db.getMany('images', wheresRaw, orderByMap[orderBy]); return images.map(i => ({ id: i.id, filename: i.filename, @@ -1368,7 +1376,7 @@ const allImagesFromDisk = (tags, sort) => { return images; }; async function getDimensions(imagePath) { - let dimensions = sizeOf(imagePath); + const dimensions = sizeOf(imagePath); const orientation = await getExifOrientation(imagePath); // when image is rotated to the left or right, switch width/height // https://jdhao.github.io/2019/07/31/image_rotation_exif_info/ @@ -1403,15 +1411,15 @@ async function createPuzzle(rng, targetTiles, image, ts) { throw `[ 2021-05-16 invalid dimension for path ${imagePath} ]`; } const info = determinePuzzleInfo(dim, targetTiles); - let tiles = new Array(info.tiles); - for (let i = 0; i < tiles.length; i++) { - tiles[i] = { idx: i }; + const rawPieces = new Array(info.tiles); + for (let i = 0; i < rawPieces.length; i++) { + rawPieces[i] = { idx: i }; } const shapes = determinePuzzleTileShapes(rng, info); let positions = new Array(info.tiles); - for (let tile of tiles) { - const coord = Util.coordByTileIdx(info, tile.idx); - positions[tile.idx] = { + for (const piece of rawPieces) { + const coord = Util.coordByPieceIdx(info, piece.idx); + positions[piece.idx] = { // instead of info.tileSize, we use info.tileDrawSize // to spread the tiles a bit x: coord.x * info.tileSize * 1.5, @@ -1421,7 +1429,7 @@ async function createPuzzle(rng, targetTiles, image, ts) { const tableWidth = info.width * 3; const tableHeight = info.height * 3; const off = info.tileSize * 1.5; - let last = { + const last = { x: info.width - (1 * off), y: info.height - (2 * off), }; @@ -1430,7 +1438,7 @@ async function createPuzzle(rng, targetTiles, image, ts) { let diffX = off; let diffY = 0; let index = 0; - for (let pos of positions) { + for (const pos of positions) { pos.x = last.x; pos.y = last.y; last.x += diffX; @@ -1456,9 +1464,9 @@ async function createPuzzle(rng, targetTiles, image, ts) { } // then shuffle the positions positions = rng.shuffle(positions); - const pieces = tiles.map(tile => { + const pieces = rawPieces.map(piece => { return Util.encodePiece({ - idx: tile.idx, + idx: piece.idx, group: 0, z: 0, // who owns the tile @@ -1469,7 +1477,7 @@ async function createPuzzle(rng, targetTiles, image, ts) { // physical current position of the tile (x/y in pixels) // this position is the initial position only and is the // value that changes when moving a tile - pos: positions[tile.idx], + pos: positions[piece.idx], }); }); // Complete puzzle object @@ -1520,7 +1528,7 @@ function determinePuzzleTileShapes(rng, info) { const tabs = [-1, 1]; const shapes = new Array(info.tiles); for (let i = 0; i < info.tiles; i++) { - let coord = Util.coordByTileIdx(info, i); + const coord = Util.coordByPieceIdx(info, i); shapes[i] = { top: coord.y === 0 ? 0 : shapes[i - info.tilesX].bottom * -1, right: coord.x === info.tilesX - 1 ? 0 : rng.choice(tabs), @@ -1566,12 +1574,12 @@ const determinePuzzleInfo = (dim, targetTiles) => { }; const log$3 = logger('GameStorage.js'); -const DIRTY_GAMES = {}; +const dirtyGames = {}; function setDirty(gameId) { - DIRTY_GAMES[gameId] = true; + dirtyGames[gameId] = true; } function setClean(gameId) { - delete DIRTY_GAMES[gameId]; + delete dirtyGames[gameId]; } function loadGames() { const files = fs.readdirSync(DATA_DIR); @@ -1620,13 +1628,17 @@ function loadGame(gameId) { GameCommon.setGame(gameObject.id, gameObject); } function persistGames() { - for (const gameId of Object.keys(DIRTY_GAMES)) { + for (const gameId of Object.keys(dirtyGames)) { persistGame(gameId); } } function persistGame(gameId) { const game = GameCommon.get(gameId); - if (game.id in DIRTY_GAMES) { + if (!game) { + log$3.error(`[ERROR] unable to persist non existing game ${gameId}`); + return; + } + if (game.id in dirtyGames) { setClean(game.id); } fs.writeFileSync(`${DATA_DIR}/${game.id}.json`, JSON.stringify({ @@ -1790,7 +1802,7 @@ class Db { let prop = '$nin'; if (where[k][prop]) { if (where[k][prop].length > 0) { - wheres.push(k + ' NOT IN (' + where[k][prop].map((_) => '?') + ')'); + wheres.push(k + ' NOT IN (' + where[k][prop].map(() => '?') + ')'); values.push(...where[k][prop]); } continue; @@ -1798,7 +1810,7 @@ class Db { prop = '$in'; if (where[k][prop]) { if (where[k][prop].length > 0) { - wheres.push(k + ' IN (' + where[k][prop].map((_) => '?') + ')'); + wheres.push(k + ' IN (' + where[k][prop].map(() => '?') + ')'); values.push(...where[k][prop]); } continue; @@ -1866,7 +1878,7 @@ class Db { const values = keys.map(k => data[k]); const sql = 'INSERT INTO ' + table + ' (' + keys.join(',') + ')' - + ' VALUES (' + keys.map(k => '?').join(',') + ')'; + + ' VALUES (' + keys.map(() => '?').join(',') + ')'; return this.run(sql, values).lastInsertRowid; } update(table, data, whereRaw = {}) { @@ -1977,7 +1989,7 @@ const setImageTags = (db, imageId, tags) => { } }); }; -app.post('/api/save-image', bodyParser.json(), (req, res) => { +app.post('/api/save-image', express.json(), (req, res) => { const data = req.body; db.update('images', { title: data.title, @@ -2015,7 +2027,7 @@ app.post('/api/upload', (req, res) => { res.send(Images.imageFromDb(db, imageId)); }); }); -app.post('/newgame', bodyParser.json(), async (req, res) => { +app.post('/newgame', express.json(), async (req, res) => { const gameSettings = req.body; log.log(gameSettings); const gameId = Util.uniqId(); @@ -2029,15 +2041,14 @@ app.use('/uploads/', express.static(UPLOAD_DIR)); app.use('/', express.static(PUBLIC_DIR)); const wss = new WebSocketServer(config.ws); const notify = (data, sockets) => { - // TODO: throttle? - for (let socket of sockets) { + for (const socket of sockets) { wss.notifyOne(data, socket); } }; wss.on('close', async ({ socket }) => { try { const proto = socket.protocol.split('|'); - const clientId = proto[0]; + // const clientId = proto[0] const gameId = proto[1]; GameSockets.removeSocket(gameId, socket); } @@ -2062,6 +2073,9 @@ wss.on('message', async ({ socket, data }) => { Game.addPlayer(gameId, clientId, ts); GameSockets.addSocket(gameId, socket); const game = GameCommon.get(gameId); + if (!game) { + throw `[game ${gameId} does not exist (anymore)... ]`; + } notify([Protocol.EV_SERVER_INIT, Util.encodeGame(game)], [socket]); } break; @@ -2084,6 +2098,9 @@ wss.on('message', async ({ socket, data }) => { } if (sendGame) { const game = GameCommon.get(gameId); + if (!game) { + throw `[game ${gameId} does not exist (anymore)... ]`; + } notify([Protocol.EV_SERVER_INIT, Util.encodeGame(game)], [socket]); } const changes = Game.handleInput(gameId, clientId, clientEvtData, ts); @@ -2101,7 +2118,7 @@ const server = app.listen(port, hostname, () => log.log(`server running on http: wss.listen(); const memoryUsageHuman = () => { const totalHeapSize = v8.getHeapStatistics().total_available_size; - let totalHeapSizeInGB = (totalHeapSize / 1024 / 1024 / 1024).toFixed(2); + const totalHeapSizeInGB = (totalHeapSize / 1024 / 1024 / 1024).toFixed(2); log.log(`Total heap size (bytes) ${totalHeapSize}, (GB ~${totalHeapSizeInGB})`); const used = process.memoryUsage().heapUsed / 1024 / 1024; log.log(`Mem: ${Math.round(used * 100) / 100}M`); @@ -2130,9 +2147,9 @@ const gracefulShutdown = (signal) => { process.once('SIGUSR2', function () { gracefulShutdown('SIGUSR2'); }); -process.once('SIGINT', function (code) { +process.once('SIGINT', function () { gracefulShutdown('SIGINT'); }); -process.once('SIGTERM', function (code) { +process.once('SIGTERM', function () { gracefulShutdown('SIGTERM'); }); diff --git a/package-lock.json b/package-lock.json index 7246c08..199e26a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,6 @@ "": { "dependencies": { "better-sqlite3": "^7.4.0", - "body-parser": "^1.19.0", "exif": "^0.6.0", "express": "^4.17.1", "image-size": "^0.9.3", @@ -24,15 +23,18 @@ "@types/multer": "^1.4.5", "@types/sharp": "^0.28.1", "@types/ws": "^7.4.4", + "@typescript-eslint/eslint-plugin": "^4.25.0", + "@typescript-eslint/parser": "^4.25.0", "@vitejs/plugin-vue": "^1.2.2", "@vuedx/typescript-plugin-vue": "^0.6.3", "compression": "^1.7.4", + "eslint": "^7.27.0", "jest": "^26.6.3", "rollup": "^2.48.0", "rollup-plugin-typescript2": "^0.30.0", "rollup-plugin-vue": "^6.0.0-beta.10", "ts-node": "^9.1.1", - "typescript": "^4.2.4", + "typescript": "^4.3.2", "vite": "^2.3.2" }, "engines": { @@ -539,6 +541,62 @@ "node": ">=0.1.95" } }, + "node_modules/@eslint/eslintrc": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.1.tgz", + "integrity": "sha512-5v7TDE9plVhvxQeWLXDTvFvJBdH6pEsdnl2g/dAptmuFEPedQ4Erq5rsDsX+mvAM610IhNaO2W5V1dOOnDKxkQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "dependencies": { + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/@intlify/core": { "version": "9.1.6", "resolved": "https://registry.npmjs.org/@intlify/core/-/core-9.1.6.tgz", @@ -1568,6 +1626,12 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/json-schema": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", + "dev": true + }, "node_modules/@types/micromatch": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.1.tgz", @@ -1671,6 +1735,229 @@ "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", "dev": true }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.25.0.tgz", + "integrity": "sha512-Qfs3dWkTMKkKwt78xp2O/KZQB8MPS1UQ5D3YW2s6LQWBE1074BE+Rym+b1pXZIX3M3fSvPUDaCvZLKV2ylVYYQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/experimental-utils": "4.25.0", + "@typescript-eslint/scope-manager": "4.25.0", + "debug": "^4.1.1", + "functional-red-black-tree": "^1.0.1", + "lodash": "^4.17.15", + "regexpp": "^3.0.0", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^4.0.0", + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.25.0.tgz", + "integrity": "sha512-f0doRE76vq7NEEU0tw+ajv6CrmPelw5wLoaghEHkA2dNLFb3T/zJQqGPQ0OYt5XlZaS13MtnN+GTPCuUVg338w==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.25.0", + "@typescript-eslint/types": "4.25.0", + "@typescript-eslint/typescript-estree": "4.25.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.25.0.tgz", + "integrity": "sha512-OZFa1SKyEJpAhDx8FcbWyX+vLwh7OEtzoo2iQaeWwxucyfbi0mT4DijbOSsTgPKzGHr6GrF2V5p/CEpUH/VBxg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "4.25.0", + "@typescript-eslint/types": "4.25.0", + "@typescript-eslint/typescript-estree": "4.25.0", + "debug": "^4.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.25.0.tgz", + "integrity": "sha512-2NElKxMb/0rya+NJG1U71BuNnp1TBd1JgzYsldsdA83h/20Tvnf/HrwhiSlNmuq6Vqa0EzidsvkTArwoq+tH6w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.25.0", + "@typescript-eslint/visitor-keys": "4.25.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.25.0.tgz", + "integrity": "sha512-+CNINNvl00OkW6wEsi32wU5MhHti2J25TJsJJqgQmJu3B3dYDBcmOxcE5w9cgoM13TrdE/5ND2HoEnBohasxRQ==", + "dev": true, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.25.0.tgz", + "integrity": "sha512-1B8U07TGNAFMxZbSpF6jqiDs1cVGO0izVkf18Q/SPcUAc9LhHxzvSowXDTvkHMWUVuPpagupaW63gB6ahTXVlg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.25.0", + "@typescript-eslint/visitor-keys": "4.25.0", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.25.0.tgz", + "integrity": "sha512-AmkqV9dDJVKP/TcZrbf6s6i1zYXt5Hl8qOLrRDTFfRNae4+LB8A4N3i+FLZPW85zIxRy39BgeWOfMS3HoH5ngg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.25.0", + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@vitejs/plugin-vue": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.2.2.tgz", @@ -2025,6 +2312,15 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/acorn-walk": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", @@ -2062,6 +2358,15 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -2210,6 +2515,15 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -2246,6 +2560,15 @@ "node": ">=0.10.0" } }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -3610,6 +3933,30 @@ "node": ">= 10.14.2" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/domexception": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", @@ -3696,6 +4043,18 @@ "once": "^1.4.0" } }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3760,6 +4119,366 @@ "source-map": "~0.6.1" } }, + "node_modules/eslint": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.27.0.tgz", + "integrity": "sha512-JZuR6La2ZF0UD384lcbnd0Cgg6QJjiCwhMD6eU4h/VGPcVGwawNNzKU41tgokGXnfjOOyI6QIffthhJTPzzuRA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.1", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", + "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -3773,6 +4492,30 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/estraverse": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", @@ -4252,6 +4995,18 @@ "bser": "2.1.1" } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -4329,6 +5084,25 @@ "node": ">=8" } }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", + "dev": true + }, "node_modules/for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -4445,6 +5219,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "node_modules/gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -4595,6 +5375,35 @@ "node": ">=4" } }, + "node_modules/globby": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", + "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", @@ -4861,6 +5670,15 @@ } ] }, + "node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/image-size": { "version": "0.9.7", "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.9.7.tgz", @@ -4875,6 +5693,31 @@ "node": ">=10.18.0" } }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/import-local": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", @@ -7113,6 +7956,12 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -7257,6 +8106,24 @@ "dev": true, "peer": true }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, "node_modules/lru_map": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", @@ -8042,6 +8909,18 @@ "node": ">=6" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -8136,6 +9015,15 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -8398,6 +9286,15 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/prompts": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", @@ -8611,6 +9508,18 @@ "node": ">=0.10.0" } }, + "node_modules/regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, "node_modules/remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -8753,6 +9662,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -9509,6 +10427,65 @@ "node": ">=8" } }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -10127,6 +11104,68 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/table": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.5.0.tgz", + "integrity": "sha512-Y2l399Tt1AguU3BPRP9Fn4eN+Or+StUGWCUpbnFyXSo8NZ9S4uj+AG2pjs5apK+ZMOwYOz1+a+VKvKH7CudXgQ==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/table/node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tar": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", @@ -10260,6 +11299,12 @@ "node": ">=8" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -10418,6 +11463,21 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -10495,9 +11555,9 @@ } }, "node_modules/typescript": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", - "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", + "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -10649,6 +11709,12 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, "node_modules/v8-to-istanbul": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", @@ -11522,6 +12588,46 @@ "minimist": "^1.2.0" } }, + "@eslint/eslintrc": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.1.tgz", + "integrity": "sha512-5v7TDE9plVhvxQeWLXDTvFvJBdH6pEsdnl2g/dAptmuFEPedQ4Erq5rsDsX+mvAM610IhNaO2W5V1dOOnDKxkQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, "@intlify/core": { "version": "9.1.6", "resolved": "https://registry.npmjs.org/@intlify/core/-/core-9.1.6.tgz", @@ -12355,6 +13461,12 @@ "@types/istanbul-lib-report": "*" } }, + "@types/json-schema": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", + "dev": true + }, "@types/micromatch": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.1.tgz", @@ -12458,6 +13570,141 @@ "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", "dev": true }, + "@typescript-eslint/eslint-plugin": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.25.0.tgz", + "integrity": "sha512-Qfs3dWkTMKkKwt78xp2O/KZQB8MPS1UQ5D3YW2s6LQWBE1074BE+Rym+b1pXZIX3M3fSvPUDaCvZLKV2ylVYYQ==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "4.25.0", + "@typescript-eslint/scope-manager": "4.25.0", + "debug": "^4.1.1", + "functional-red-black-tree": "^1.0.1", + "lodash": "^4.17.15", + "regexpp": "^3.0.0", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@typescript-eslint/experimental-utils": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.25.0.tgz", + "integrity": "sha512-f0doRE76vq7NEEU0tw+ajv6CrmPelw5wLoaghEHkA2dNLFb3T/zJQqGPQ0OYt5XlZaS13MtnN+GTPCuUVg338w==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.25.0", + "@typescript-eslint/types": "4.25.0", + "@typescript-eslint/typescript-estree": "4.25.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.25.0.tgz", + "integrity": "sha512-OZFa1SKyEJpAhDx8FcbWyX+vLwh7OEtzoo2iQaeWwxucyfbi0mT4DijbOSsTgPKzGHr6GrF2V5p/CEpUH/VBxg==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "4.25.0", + "@typescript-eslint/types": "4.25.0", + "@typescript-eslint/typescript-estree": "4.25.0", + "debug": "^4.1.1" + } + }, + "@typescript-eslint/scope-manager": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.25.0.tgz", + "integrity": "sha512-2NElKxMb/0rya+NJG1U71BuNnp1TBd1JgzYsldsdA83h/20Tvnf/HrwhiSlNmuq6Vqa0EzidsvkTArwoq+tH6w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.25.0", + "@typescript-eslint/visitor-keys": "4.25.0" + } + }, + "@typescript-eslint/types": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.25.0.tgz", + "integrity": "sha512-+CNINNvl00OkW6wEsi32wU5MhHti2J25TJsJJqgQmJu3B3dYDBcmOxcE5w9cgoM13TrdE/5ND2HoEnBohasxRQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.25.0.tgz", + "integrity": "sha512-1B8U07TGNAFMxZbSpF6jqiDs1cVGO0izVkf18Q/SPcUAc9LhHxzvSowXDTvkHMWUVuPpagupaW63gB6ahTXVlg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.25.0", + "@typescript-eslint/visitor-keys": "4.25.0", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.25.0.tgz", + "integrity": "sha512-AmkqV9dDJVKP/TcZrbf6s6i1zYXt5Hl8qOLrRDTFfRNae4+LB8A4N3i+FLZPW85zIxRy39BgeWOfMS3HoH5ngg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.25.0", + "eslint-visitor-keys": "^2.0.0" + } + }, "@vitejs/plugin-vue": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-1.2.2.tgz", @@ -12757,6 +14004,13 @@ } } }, + "acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true, + "requires": {} + }, "acorn-walk": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", @@ -12784,6 +14038,12 @@ "uri-js": "^4.2.2" } }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, "ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -12910,6 +14170,12 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -12937,6 +14203,12 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -14003,6 +15275,24 @@ "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", "dev": true }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "domexception": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", @@ -14073,6 +15363,15 @@ "once": "^1.4.0" } }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -14118,12 +15417,293 @@ "source-map": "~0.6.1" } }, + "eslint": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.27.0.tgz", + "integrity": "sha512-JZuR6La2ZF0UD384lcbnd0Cgg6QJjiCwhMD6eU4h/VGPcVGwawNNzKU41tgokGXnfjOOyI6QIffthhJTPzzuRA==", + "dev": true, + "requires": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.1", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "globals": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", + "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "dependencies": { + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + } + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, "estraverse": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", @@ -14527,6 +16107,15 @@ "bser": "2.1.1" } }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -14591,6 +16180,22 @@ "path-exists": "^4.0.0" } }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", + "dev": true + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -14676,6 +16281,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -14792,6 +16403,28 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, + "globby": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", + "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + } + } + }, "graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", @@ -14993,6 +16626,12 @@ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, "image-size": { "version": "0.9.7", "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.9.7.tgz", @@ -15001,6 +16640,24 @@ "queue": "6.0.2" } }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, "import-local": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", @@ -16708,6 +18365,12 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -16824,6 +18487,24 @@ "dev": true, "peer": true }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, "lru_map": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", @@ -17451,6 +19132,15 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -17526,6 +19216,12 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -17718,6 +19414,12 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, "prompts": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", @@ -17876,6 +19578,12 @@ "safe-regex": "^1.1.0" } }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -17984,6 +19692,12 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -18583,6 +20297,49 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + } + } + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -19088,6 +20845,57 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "table": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", + "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.5.0.tgz", + "integrity": "sha512-Y2l399Tt1AguU3BPRP9Fn4eN+Or+StUGWCUpbnFyXSo8NZ9S4uj+AG2pjs5apK+ZMOwYOz1+a+VKvKH7CudXgQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + } + } + }, "tar": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", @@ -19189,6 +20997,12 @@ "minimatch": "^3.0.4" } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, "thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -19310,6 +21124,15 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -19369,9 +21192,9 @@ } }, "typescript": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", - "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", + "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==", "dev": true }, "union-value": { @@ -19488,6 +21311,12 @@ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, "v8-to-istanbul": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", diff --git a/package.json b/package.json index d36af29..11aaf70 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "type": "module", "dependencies": { "better-sqlite3": "^7.4.0", - "body-parser": "^1.19.0", "exif": "^0.6.0", "express": "^4.17.1", "image-size": "^0.9.3", @@ -20,15 +19,18 @@ "@types/multer": "^1.4.5", "@types/sharp": "^0.28.1", "@types/ws": "^7.4.4", + "@typescript-eslint/eslint-plugin": "^4.25.0", + "@typescript-eslint/parser": "^4.25.0", "@vitejs/plugin-vue": "^1.2.2", "@vuedx/typescript-plugin-vue": "^0.6.3", "compression": "^1.7.4", + "eslint": "^7.27.0", "jest": "^26.6.3", "rollup": "^2.48.0", "rollup-plugin-typescript2": "^0.30.0", "rollup-plugin-vue": "^6.0.0-beta.10", "ts-node": "^9.1.1", - "typescript": "^4.2.4", + "typescript": "^4.3.2", "vite": "^2.3.2" }, "engines": { @@ -37,6 +39,7 @@ }, "scripts": { "rollup": "rollup", - "vite": "vite" + "vite": "vite", + "eslint": "eslint" } } diff --git a/rollup.server.config.js b/rollup.server.config.js index 117fee1..091ff79 100644 --- a/rollup.server.config.js +++ b/rollup.server.config.js @@ -8,17 +8,20 @@ export default { format: 'es', }, external: [ - "express", - "multer", - "body-parser", - "v8", - "fs", - "ws", - "image-size", + "better-sqlite3", + "compression", "exif", - "sharp", - "url", + "express", + "fs", + "image-size", + "multer", "path", + "readline", + "sharp", + "stream", + "url", + "v8", + "ws", ], plugins: [typescript()], }; diff --git a/scripts/lint b/scripts/lint new file mode 100755 index 0000000..055d373 --- /dev/null +++ b/scripts/lint @@ -0,0 +1,5 @@ +#!/bin/sh -e + +cd "$RUN_DIR" + +npm run eslint src diff --git a/scripts/tests b/scripts/tests index a047361..ac15ff6 100755 --- a/scripts/tests +++ b/scripts/tests @@ -1,3 +1,3 @@ -#!/bin/sh +#!/bin/sh -e node --experimental-vm-modules node_modules/.bin/jest diff --git a/src/common/GameCommon.ts b/src/common/GameCommon.ts index e9e2d30..629248a 100644 --- a/src/common/GameCommon.ts +++ b/src/common/GameCommon.ts @@ -1,172 +1,31 @@ import Geometry, { Point, Rect } from './Geometry' import Protocol from './Protocol' -import { Rng, RngSerialized } from './Rng' +import { Rng } from './Rng' import Time from './Time' -import { FixedLengthArray } from './Types' -import Util from './Util' - -export type Timestamp = number - -export type EncodedPlayer = FixedLengthArray<[ - string, - number, - number, - 0|1, - string|null, - string|null, - string|null, - number, - Timestamp, -]> - -export type EncodedPiece = FixedLengthArray<[ - number, - number, - number, - number, - string|number, - number, -]> - -export type EncodedPieceShape = number - -export type EncodedGame = FixedLengthArray<[ - string, - string, - RngSerialized, +import { + Change, + EncodedPiece, + EvtInfo, + Game, + Input, + Piece, + PieceChange, + Player, + PlayerChange, Puzzle, - Array, - Record, + PuzzleData, + PuzzleDataChange, ScoreMode, -]> - -export interface ReplayData { - log: any[], - game: EncodedGame|null -} - -export interface Tag { - id: number - slug: string - title: string -} - -interface GameRng { - obj: Rng - type?: string -} - -export interface Game { - id: string - players: Array - puzzle: Puzzle - evtInfos: Record - scoreMode?: ScoreMode - rng: GameRng -} - -export interface Image { - id: number - filename: string - file: string - url: string - title: string - tags: Array - created: number -} - -export interface GameSettings { - tiles: number - image: Image - scoreMode: ScoreMode -} - -export interface Puzzle { - tiles: Array - data: PuzzleData - info: PuzzleInfo -} - -interface PuzzleData { - started: number - finished: number - maxGroup: number - maxZ: number -} - -interface PuzzleTable { - width: number - height: number -} - -enum PieceEdge { - Flat = 0, - Out = 1, - In = -1, -} -export interface PieceShape { - top: PieceEdge - bottom: PieceEdge - left: PieceEdge - right: PieceEdge -} - -export interface Piece { - owner: string|number - idx: number - pos: Point - z: number - group: number -} - -export interface PuzzleInfo { - table: PuzzleTable - targetTiles: number, - imageUrl: string - - width: number - height: number - tileSize: number - tileDrawSize: number - tileMarginWidth: number - tileDrawOffset: number - snapDistance: number - - tiles: number - tilesX: number - tilesY: number - - shapes: Array -} - -export interface Player { - id: string - x: number - y: number - d: 0|1 - name: string|null - color: string|null - bgcolor: string|null - points: number - ts: Timestamp -} - -interface EvtInfo { - _last_mouse: Point|null - _last_mouse_down: Point|null -} - -export enum ScoreMode { - FINAL = 0, - ANY = 1, -} + Timestamp +} from './Types' +import Util from './Util' const IDLE_TIMEOUT_SEC = 30 // Map const GAMES: Record = {} -function exists(gameId: string) { +function exists(gameId: string): boolean { return (!!GAMES[gameId]) || false } @@ -190,7 +49,7 @@ function setGame(gameId: string, game: Game): void { function getPlayerIndexById(gameId: string, playerId: string): number { let i = 0; - for (let player of GAMES[gameId].players) { + for (const player of GAMES[gameId].players) { if (Util.decodePlayer(player).id === playerId) { return i } @@ -293,8 +152,8 @@ function getAllPlayers(gameId: string): Array { : [] } -function get(gameId: string) { - return GAMES[gameId] +function get(gameId: string): Game|null { + return GAMES[gameId] || null } function getPieceCount(gameId: string): number { @@ -319,7 +178,7 @@ function isFinished(gameId: string): boolean { function getFinishedPiecesCount(gameId: string): number { let count = 0 - for (let t of GAMES[gameId].puzzle.tiles) { + for (const t of GAMES[gameId].puzzle.tiles) { if (Util.decodePiece(t).owner === -1) { count++ } @@ -335,29 +194,33 @@ function getPiecesSortedByZIndex(gameId: string): Piece[] { function changePlayer( gameId: string, playerId: string, - change: any + change: PlayerChange ): void { const player = getPlayer(gameId, playerId) if (player === null) { return } - for (let k of Object.keys(change)) { + for (const k of Object.keys(change)) { // @ts-ignore player[k] = change[k] } setPlayer(gameId, playerId, player) } -function changeData(gameId: string, change: any): void { - for (let k of Object.keys(change)) { +function changeData(gameId: string, change: PuzzleDataChange): void { + for (const k of Object.keys(change)) { // @ts-ignore GAMES[gameId].puzzle.data[k] = change[k] } } -function changeTile(gameId: string, pieceIdx: number, change: any): void { - for (let k of Object.keys(change)) { +function changePiece( + gameId: string, + pieceIdx: number, + change: PieceChange +): void { + for (const k of Object.keys(change)) { const piece = Util.decodePiece(GAMES[gameId].puzzle.tiles[pieceIdx]) // @ts-ignore piece[k] = change[k] @@ -415,13 +278,12 @@ const getPieceBounds = (gameId: string, tileIdx: number): Rect => { } } -const getTileZIndex = (gameId: string, tileIdx: number): number => { - const tile = getPiece(gameId, tileIdx) - return tile.z +const getPieceZIndex = (gameId: string, pieceIdx: number): number => { + return getPiece(gameId, pieceIdx).z } const getFirstOwnedPieceIdx = (gameId: string, playerId: string): number => { - for (let t of GAMES[gameId].puzzle.tiles) { + for (const t of GAMES[gameId].puzzle.tiles) { const tile = Util.decodePiece(t) if (tile.owner === playerId) { return tile.idx @@ -430,7 +292,10 @@ const getFirstOwnedPieceIdx = (gameId: string, playerId: string): number => { return -1 } -const getFirstOwnedPiece = (gameId: string, playerId: string): EncodedPiece|null => { +const getFirstOwnedPiece = ( + gameId: string, + playerId: string +): EncodedPiece|null => { const idx = getFirstOwnedPieceIdx(gameId, playerId) return idx < 0 ? null : GAMES[gameId].puzzle.tiles[idx] } @@ -463,12 +328,12 @@ const getMaxZIndex = (gameId: string): number => { return GAMES[gameId].puzzle.data.maxZ } -const getMaxZIndexByTileIdxs = (gameId: string, tileIdxs: Array): number => { +const getMaxZIndexByPieceIdxs = (gameId: string, pieceIdxs: Array): number => { let maxZ = 0 - for (let tileIdx of tileIdxs) { - let tileZIndex = getTileZIndex(gameId, tileIdx) - if (tileZIndex > maxZ) { - maxZ = tileZIndex + for (const pieceIdx of pieceIdxs) { + const curZ = getPieceZIndex(gameId, pieceIdx) + if (curZ > maxZ) { + maxZ = curZ } } return maxZ @@ -477,7 +342,7 @@ const getMaxZIndexByTileIdxs = (gameId: string, tileIdxs: Array): number function srcPosByTileIdx(gameId: string, tileIdx: number): Point { const info = GAMES[gameId].puzzle.info - const c = Util.coordByTileIdx(info, tileIdx) + const c = Util.coordByPieceIdx(info, tileIdx) const cx = c.x * info.tileSize const cy = c.y * info.tileSize @@ -487,7 +352,7 @@ function srcPosByTileIdx(gameId: string, tileIdx: number): Point { function getSurroundingTilesByIdx(gameId: string, tileIdx: number) { const info = GAMES[gameId].puzzle.info - const c = Util.coordByTileIdx(info, tileIdx) + const c = Util.coordByPieceIdx(info, tileIdx) return [ // top @@ -501,29 +366,29 @@ function getSurroundingTilesByIdx(gameId: string, tileIdx: number) { ] } -const setTilesZIndex = (gameId: string, tileIdxs: Array, zIndex: number): void => { - for (let tilesIdx of tileIdxs) { - changeTile(gameId, tilesIdx, { z: zIndex }) +const setPiecesZIndex = (gameId: string, tileIdxs: Array, zIndex: number): void => { + for (const tilesIdx of tileIdxs) { + changePiece(gameId, tilesIdx, { z: zIndex }) } } const moveTileDiff = (gameId: string, tileIdx: number, diff: Point): void => { const oldPos = getPiecePos(gameId, tileIdx) const pos = Geometry.pointAdd(oldPos, diff) - changeTile(gameId, tileIdx, { pos }) + changePiece(gameId, tileIdx, { pos }) } -const moveTilesDiff = ( +const movePiecesDiff = ( gameId: string, - tileIdxs: Array, + pieceIdxs: Array, diff: Point ): void => { const drawSize = getPieceDrawSize(gameId) const bounds = getBounds(gameId) const cappedDiff = diff - for (let tileIdx of tileIdxs) { - const t = getPiece(gameId, tileIdx) + for (const pieceIdx of pieceIdxs) { + const t = getPiece(gameId, pieceIdx) if (t.pos.x + diff.x < bounds.x) { cappedDiff.x = Math.max(bounds.x - t.pos.x, cappedDiff.x) } else if (t.pos.x + drawSize + diff.x > bounds.x + bounds.w) { @@ -536,24 +401,24 @@ const moveTilesDiff = ( } } - for (let tileIdx of tileIdxs) { - moveTileDiff(gameId, tileIdx, cappedDiff) + for (const pieceIdx of pieceIdxs) { + moveTileDiff(gameId, pieceIdx, cappedDiff) } } -const finishTiles = (gameId: string, tileIdxs: Array): void => { - for (let tileIdx of tileIdxs) { - changeTile(gameId, tileIdx, { owner: -1, z: 1 }) +const finishPieces = (gameId: string, pieceIdxs: Array): void => { + for (const pieceIdx of pieceIdxs) { + changePiece(gameId, pieceIdx, { owner: -1, z: 1 }) } } const setTilesOwner = ( gameId: string, - tileIdxs: Array, + pieceIdxs: Array, owner: string|number ): void => { - for (let tileIdx of tileIdxs) { - changeTile(gameId, tileIdx, { owner }) + for (const pieceIdx of pieceIdxs) { + changePiece(gameId, pieceIdx, { owner }) } } @@ -564,7 +429,7 @@ function getGroupedPieceIdxs(gameId: string, pieceIdx: number): number[] { const grouped = [] if (piece.group) { - for (let other of pieces) { + for (const other of pieces) { const otherPiece = Util.decodePiece(other) if (otherPiece.group === piece.group) { grouped.push(otherPiece.idx) @@ -579,8 +444,8 @@ function getGroupedPieceIdxs(gameId: string, pieceIdx: number): number[] { // Returns the index of the puzzle tile with the highest z index // that is not finished yet and that matches the position const freePieceIdxByPos = (gameId: string, pos: Point): number => { - let info = GAMES[gameId].puzzle.info - let pieces = GAMES[gameId].puzzle.tiles + const info = GAMES[gameId].puzzle.info + const pieces = GAMES[gameId].puzzle.tiles let maxZ = -1 let pieceIdx = -1 @@ -664,28 +529,28 @@ const getPuzzleHeight = (gameId: string): number => { function handleInput( gameId: string, playerId: string, - input: any, - ts: number -): Array> { + input: Input, + ts: Timestamp +): Array { const puzzle = GAMES[gameId].puzzle const evtInfo = getEvtInfo(gameId, playerId) - const changes = [] as Array> + const changes: Array = [] const _dataChange = (): void => { changes.push([Protocol.CHANGE_DATA, puzzle.data]) } - const _tileChange = (tileIdx: number): void => { + const _pieceChange = (pieceIdx: number): void => { changes.push([ Protocol.CHANGE_TILE, - Util.encodePiece(getPiece(gameId, tileIdx)), + Util.encodePiece(getPiece(gameId, pieceIdx)), ]) } - const _tileChanges = (tileIdxs: Array): void => { - for (const tileIdx of tileIdxs) { - _tileChange(tileIdx) + const _pieceChanges = (pieceIdxs: Array): void => { + for (const pieceIdx of pieceIdxs) { + _pieceChange(pieceIdx) } } @@ -703,12 +568,12 @@ function handleInput( // put both tiles (and their grouped tiles) in the same group const groupTiles = ( gameId: string, - tileIdx1: number, - tileIdx2: number + pieceIdx1: number, + pieceIdx2: number ): void => { - const tiles = GAMES[gameId].puzzle.tiles - const group1 = getPieceGroup(gameId, tileIdx1) - const group2 = getPieceGroup(gameId, tileIdx2) + const pieces = GAMES[gameId].puzzle.tiles + const group1 = getPieceGroup(gameId, pieceIdx1) + const group2 = getPieceGroup(gameId, pieceIdx2) let group const searchGroups = [] @@ -729,18 +594,18 @@ function handleInput( group = getMaxGroup(gameId) } - changeTile(gameId, tileIdx1, { group }) - _tileChange(tileIdx1) - changeTile(gameId, tileIdx2, { group }) - _tileChange(tileIdx2) + changePiece(gameId, pieceIdx1, { group }) + _pieceChange(pieceIdx1) + changePiece(gameId, pieceIdx2, { group }) + _pieceChange(pieceIdx2) // TODO: strange if (searchGroups.length > 0) { - for (const t of tiles) { - const piece = Util.decodePiece(t) + for (const p of pieces) { + const piece = Util.decodePiece(p) if (searchGroups.includes(piece.group)) { - changeTile(gameId, piece.idx, { group }) - _tileChange(piece.idx) + changePiece(gameId, piece.idx, { group }) + _pieceChange(piece.idx) } } } @@ -770,13 +635,13 @@ function handleInput( const tileIdxAtPos = freePieceIdxByPos(gameId, pos) if (tileIdxAtPos >= 0) { - let maxZ = getMaxZIndex(gameId) + 1 + const maxZ = getMaxZIndex(gameId) + 1 changeData(gameId, { maxZ }) _dataChange() const tileIdxs = getGroupedPieceIdxs(gameId, tileIdxAtPos) - setTilesZIndex(gameId, tileIdxs, getMaxZIndex(gameId)) + setPiecesZIndex(gameId, tileIdxs, getMaxZIndex(gameId)) setTilesOwner(gameId, tileIdxs, playerId) - _tileChanges(tileIdxs) + _pieceChanges(tileIdxs) } evtInfo._last_mouse = pos @@ -790,18 +655,18 @@ function handleInput( changePlayer(gameId, playerId, {x, y, ts}) _playerChange() } else { - let tileIdx = getFirstOwnedPieceIdx(gameId, playerId) - if (tileIdx >= 0) { + const pieceIdx = getFirstOwnedPieceIdx(gameId, playerId) + if (pieceIdx >= 0) { // player is moving a tile (and hand) changePlayer(gameId, playerId, {x, y, ts}) _playerChange() // check if pos is on the tile, otherwise dont move // (mouse could be out of table, but tile stays on it) - const tileIdxs = getGroupedPieceIdxs(gameId, tileIdx) + const pieceIdxs = getGroupedPieceIdxs(gameId, pieceIdx) let anyOk = Geometry.pointInBounds(pos, getBounds(gameId)) && Geometry.pointInBounds(evtInfo._last_mouse_down, getBounds(gameId)) - for (let idx of tileIdxs) { + for (const idx of pieceIdxs) { const bounds = getPieceBounds(gameId, idx) if (Geometry.pointInBounds(pos, bounds)) { anyOk = true @@ -813,9 +678,9 @@ function handleInput( const diffY = y - evtInfo._last_mouse_down.y const diff = { x: diffX, y: diffY } - moveTilesDiff(gameId, tileIdxs, diff) + movePiecesDiff(gameId, pieceIdxs, diff) - _tileChanges(tileIdxs) + _pieceChanges(pieceIdxs) } } else { // player is just moving map, so no change in position! @@ -835,26 +700,26 @@ function handleInput( evtInfo._last_mouse_down = null - let tileIdx = getFirstOwnedPieceIdx(gameId, playerId) - if (tileIdx >= 0) { + const pieceIdx = getFirstOwnedPieceIdx(gameId, playerId) + if (pieceIdx >= 0) { // drop the tile(s) - let tileIdxs = getGroupedPieceIdxs(gameId, tileIdx) - setTilesOwner(gameId, tileIdxs, 0) - _tileChanges(tileIdxs) + const pieceIdxs = getGroupedPieceIdxs(gameId, pieceIdx) + setTilesOwner(gameId, pieceIdxs, 0) + _pieceChanges(pieceIdxs) // Check if the tile was dropped near the final location - let tilePos = getPiecePos(gameId, tileIdx) - let finalPos = getFinalPiecePos(gameId, tileIdx) + const tilePos = getPiecePos(gameId, pieceIdx) + const finalPos = getFinalPiecePos(gameId, pieceIdx) if (Geometry.pointDistance(finalPos, tilePos) < puzzle.info.snapDistance) { - let diff = Geometry.pointSub(finalPos, tilePos) + const diff = Geometry.pointSub(finalPos, tilePos) // Snap the tile to the final destination - moveTilesDiff(gameId, tileIdxs, diff) - finishTiles(gameId, tileIdxs) - _tileChanges(tileIdxs) + movePiecesDiff(gameId, pieceIdxs, diff) + finishPieces(gameId, pieceIdxs) + _pieceChanges(pieceIdxs) let points = getPlayerPoints(gameId, playerId) if (getScoreMode(gameId) === ScoreMode.FINAL) { - points += tileIdxs.length + points += pieceIdxs.length } else if (getScoreMode(gameId) === ScoreMode.ANY) { points += 1 } else { @@ -877,7 +742,7 @@ function handleInput( otherTileIdx: number, off: Array ): boolean => { - let info = GAMES[gameId].puzzle.info + const info = GAMES[gameId].puzzle.info if (otherTileIdx < 0) { return false } @@ -890,27 +755,27 @@ function handleInput( {x: off[0] * info.tileSize, y: off[1] * info.tileSize} ) if (Geometry.pointDistance(tilePos, dstPos) < info.snapDistance) { - let diff = Geometry.pointSub(dstPos, tilePos) - let tileIdxs = getGroupedPieceIdxs(gameId, tileIdx) - moveTilesDiff(gameId, tileIdxs, diff) + const diff = Geometry.pointSub(dstPos, tilePos) + let pieceIdxs = getGroupedPieceIdxs(gameId, tileIdx) + movePiecesDiff(gameId, pieceIdxs, diff) groupTiles(gameId, tileIdx, otherTileIdx) - tileIdxs = getGroupedPieceIdxs(gameId, tileIdx) - const zIndex = getMaxZIndexByTileIdxs(gameId, tileIdxs) - setTilesZIndex(gameId, tileIdxs, zIndex) - _tileChanges(tileIdxs) + pieceIdxs = getGroupedPieceIdxs(gameId, tileIdx) + const zIndex = getMaxZIndexByPieceIdxs(gameId, pieceIdxs) + setPiecesZIndex(gameId, pieceIdxs, zIndex) + _pieceChanges(pieceIdxs) return true } return false } let snapped = false - for (let tileIdxTmp of getGroupedPieceIdxs(gameId, tileIdx)) { - let othersIdxs = getSurroundingTilesByIdx(gameId, tileIdxTmp) + for (const pieceIdxTmp of getGroupedPieceIdxs(gameId, pieceIdx)) { + const othersIdxs = getSurroundingTilesByIdx(gameId, pieceIdxTmp) if ( - check(gameId, tileIdxTmp, othersIdxs[0], [0, 1]) // top - || check(gameId, tileIdxTmp, othersIdxs[1], [-1, 0]) // right - || check(gameId, tileIdxTmp, othersIdxs[2], [0, -1]) // bottom - || check(gameId, tileIdxTmp, othersIdxs[3], [1, 0]) // left + check(gameId, pieceIdxTmp, othersIdxs[0], [0, 1]) // top + || check(gameId, pieceIdxTmp, othersIdxs[1], [-1, 0]) // right + || check(gameId, pieceIdxTmp, othersIdxs[2], [0, -1]) // bottom + || check(gameId, pieceIdxTmp, othersIdxs[3], [1, 0]) // left ) { snapped = true break diff --git a/src/common/Rng.ts b/src/common/Rng.ts index af72dfc..349e698 100644 --- a/src/common/Rng.ts +++ b/src/common/Rng.ts @@ -15,7 +15,7 @@ export class Rng { random (min: number, max: number): number { this.rand_high = ((this.rand_high << 16) + (this.rand_high >> 16) + this.rand_low) & 0xffffffff; this.rand_low = (this.rand_low + this.rand_high) & 0xffffffff; - var n = (this.rand_high >>> 0) / 0xffffffff; + const n = (this.rand_high >>> 0) / 0xffffffff; return (min + n * (max-min+1))|0; } diff --git a/src/common/Types.ts b/src/common/Types.ts index 279f5f7..f00c3e2 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -1,6 +1,199 @@ +import { Point } from "./Geometry" +import { Rng, RngSerialized } from "./Rng" + // @see https://stackoverflow.com/a/59906630/392905 type ArrayLengthMutationKeys = 'splice' | 'push' | 'pop' | 'shift' | 'unshift' | number type ArrayItems> = T extends Array ? TItems : never export type FixedLengthArray = Pick> & { [Symbol.iterator]: () => IterableIterator< ArrayItems > } + +export type Timestamp = number + +export type Input = any +export type Change = Array + +export type GameEvent = Array + +export type ClientEvent = Array + +export type EncodedPlayer = FixedLengthArray<[ + string, + number, + number, + 0|1, + string|null, + string|null, + string|null, + number, + Timestamp, +]> + +export type EncodedPiece = FixedLengthArray<[ + number, + number, + number, + number, + string|number, + number, +]> + +export type EncodedPieceShape = number + +export type EncodedGame = FixedLengthArray<[ + string, + string, + RngSerialized, + Puzzle, + Array, + Record, + ScoreMode, +]> + +export interface ReplayData { + log: any[], + game: EncodedGame|null +} + +export interface Tag { + id: number + slug: string + title: string +} + +interface GameRng { + obj: Rng + type?: string +} + +export interface Game { + id: string + players: Array + puzzle: Puzzle + evtInfos: Record + scoreMode?: ScoreMode + rng: GameRng +} + +export interface Image { + id: number + filename: string + file: string + url: string + title: string + tags: Array + created: number +} + +export interface GameSettings { + tiles: number + image: Image + scoreMode: ScoreMode +} + +export interface Puzzle { + tiles: Array + data: PuzzleData + info: PuzzleInfo +} + +export interface PuzzleData { + started: number + finished: number + maxGroup: number + maxZ: number +} + +export interface PuzzleDataChange { + started?: number + finished?: number + maxGroup?: number + maxZ?: number +} + +interface PuzzleTable { + width: number + height: number +} + +enum PieceEdge { + Flat = 0, + Out = 1, + In = -1, +} +export interface PieceShape { + top: PieceEdge + bottom: PieceEdge + left: PieceEdge + right: PieceEdge +} + +export interface Piece { + owner: string|number + idx: number + pos: Point + z: number + group: number +} + +export interface PieceChange { + owner?: string|number + idx?: number + pos?: Point + z?: number + group?: number +} + +export interface PuzzleInfo { + table: PuzzleTable + targetTiles: number, + imageUrl: string + + width: number + height: number + tileSize: number + tileDrawSize: number + tileMarginWidth: number + tileDrawOffset: number + snapDistance: number + + tiles: number + tilesX: number + tilesY: number + + shapes: Array +} + +export interface Player { + id: string + x: number + y: number + d: 0|1 + name: string|null + color: string|null + bgcolor: string|null + points: number + ts: Timestamp +} + +export interface PlayerChange { + id?: string + x?: number + y?: number + d?: 0|1 + name?: string|null + color?: string|null + bgcolor?: string|null + points?: number + ts?: Timestamp +} + +export interface EvtInfo { + _last_mouse: Point|null + _last_mouse_down: Point|null +} + +export enum ScoreMode { + FINAL = 0, + ANY = 1, +} diff --git a/src/common/Util.ts b/src/common/Util.ts index c988b9d..020c568 100644 --- a/src/common/Util.ts +++ b/src/common/Util.ts @@ -1,3 +1,4 @@ +import { PuzzleCreationInfo } from '../server/Puzzle' import { EncodedGame, EncodedPiece, @@ -7,19 +8,20 @@ import { Piece, PieceShape, Player, + PuzzleInfo, ScoreMode -} from './GameCommon' +} from './Types' import { Point } from './Geometry' import { Rng } from './Rng' -const slug = (str: string) => { +const slug = (str: string): string => { let tmp = str.toLowerCase() tmp = tmp.replace(/[^a-z0-9]+/g, '-') tmp = tmp.replace(/^-|-$/, '') return tmp } -const pad = (x: any, pad: string) => { +const pad = (x: number, pad: string): string => { const str = `${x}` if (str.length >= pad.length) { return str @@ -43,7 +45,9 @@ export const logger = (...pre: Array) => { } // get a unique id -export const uniqId = () => Date.now().toString(36) + Math.random().toString(36).substring(2) +export const uniqId = (): string => { + return Date.now().toString(36) + Math.random().toString(36).substring(2) +} function encodeShape(data: PieceShape): EncodedPieceShape { /* encoded in 1 byte: @@ -139,11 +143,11 @@ function decodeGame(data: EncodedGame): Game { } } -function coordByTileIdx(info: any, tileIdx: number): Point { +function coordByPieceIdx(info: PuzzleInfo|PuzzleCreationInfo, pieceIdx: number): Point { const wTiles = info.width / info.tileSize return { - x: tileIdx % wTiles, - y: Math.floor(tileIdx / wTiles), + x: pieceIdx % wTiles, + y: Math.floor(pieceIdx / wTiles), } } @@ -151,16 +155,16 @@ const hash = (str: string): number => { let hash = 0 for (let i = 0; i < str.length; i++) { - let chr = str.charCodeAt(i); + const chr = str.charCodeAt(i); hash = ((hash << 5) - hash) + chr; hash |= 0; // Convert to 32bit integer } return hash; } -function asQueryArgs(data: any) { +function asQueryArgs(data: Record): string { const q = [] - for (let k in data) { + for (const k in data) { const pair = [k, data[k]].map(encodeURIComponent) q.push(pair.join('=')) } @@ -187,7 +191,7 @@ export default { encodeGame, decodeGame, - coordByTileIdx, + coordByPieceIdx, asQueryArgs, } diff --git a/src/frontend/Communication.ts b/src/frontend/Communication.ts index d84e629..2b52b77 100644 --- a/src/frontend/Communication.ts +++ b/src/frontend/Communication.ts @@ -1,6 +1,6 @@ "use strict" -import { EncodedGame, ReplayData } from '../common/GameCommon' +import { ClientEvent, EncodedGame, GameEvent, ReplayData } from '../common/Types' import Util, { logger } from '../common/Util' import Protocol from './../common/Protocol' @@ -16,25 +16,29 @@ const CONN_STATE_CONNECTING = 3 // connecting const CONN_STATE_CLOSED = 4 // not connected (closed on purpose) let ws: WebSocket -let changesCallback = (msg: Array) => {} -let connectionStateChangeCallback = (state: number) => {} +let changesCallback = (msg: Array) => { + // empty +} +let connectionStateChangeCallback = (state: number) => { + // empty +} // TODO: change these to something like on(EVT, cb) -function onServerChange(callback: (msg: Array) => void) { +function onServerChange(callback: (msg: Array) => void): void { changesCallback = callback } -function onConnectionStateChange(callback: (state: number) => void) { +function onConnectionStateChange(callback: (state: number) => void): void { connectionStateChangeCallback = callback } let connectionState = CONN_STATE_NOT_CONNECTED -const setConnectionState = (state: number) => { +const setConnectionState = (state: number): void => { if (connectionState !== state) { connectionState = state connectionStateChangeCallback(state) } } -function send(message: Array): void { +function send(message: ClientEvent): void { if (connectionState === CONN_STATE_CONNECTED) { try { ws.send(JSON.stringify(message)) @@ -46,7 +50,7 @@ function send(message: Array): void { let clientSeq: number -let events: Record +let events: Record function connect( address: string, @@ -58,7 +62,7 @@ function connect( setConnectionState(CONN_STATE_CONNECTING) return new Promise(resolve => { ws = new WebSocket(address, clientId + '|' + gameId) - ws.onopen = (e) => { + ws.onopen = () => { setConnectionState(CONN_STATE_CONNECTED) send([Protocol.EV_CLIENT_INIT]) } @@ -82,7 +86,7 @@ function connect( } } - ws.onerror = (e) => { + ws.onerror = () => { setConnectionState(CONN_STATE_DISCONNECTED) throw `[ 2021-05-15 onerror ]` } @@ -116,7 +120,7 @@ function disconnect(): void { events = {} } -function sendClientEvent(evt: any): void { +function sendClientEvent(evt: GameEvent): void { // when sending event, increase number of sent events // and add the event locally clientSeq++; diff --git a/src/frontend/Debug.ts b/src/frontend/Debug.ts index 5580b9a..f8b9ac7 100644 --- a/src/frontend/Debug.ts +++ b/src/frontend/Debug.ts @@ -7,12 +7,12 @@ const log = logger('Debug.js') let _pt = 0 let _mindiff = 0 -const checkpoint_start = (mindiff: number) => { +const checkpoint_start = (mindiff: number): void => { _pt = performance.now() _mindiff = mindiff } -const checkpoint = (label: string) => { +const checkpoint = (label: string): void => { const now = performance.now() const diff = now - _pt if (diff > _mindiff) { diff --git a/src/frontend/Fireworks.ts b/src/frontend/Fireworks.ts index 7dae028..2b84891 100644 --- a/src/frontend/Fireworks.ts +++ b/src/frontend/Fireworks.ts @@ -1,7 +1,6 @@ "use strict" import { Rng } from '../common/Rng' -import Util from '../common/Util' let minVx = -10 let deltaVx = 20 @@ -108,11 +107,11 @@ class Bomb { } class Particle { - px: any - py: any + px: number + py: number vx: number vy: number - color: any + color: string duration: number alive: boolean radius: number @@ -171,7 +170,7 @@ class Controller { }) } - setSpeedParams() { + setSpeedParams(): void { let heightReached = 0 let vy = 0 @@ -188,11 +187,11 @@ class Controller { deltaVx = 2 * vx } - resize() { + resize(): void { this.setSpeedParams() } - init() { + init(): void { this.readyBombs = [] this.explodedBombs = [] this.particles = [] @@ -202,7 +201,7 @@ class Controller { } } - update() { + update(): void { if (Math.random() * 100 < percentChanceNewBomb) { this.readyBombs.push(new Bomb(this.rng)) } @@ -250,7 +249,7 @@ class Controller { this.particles = aliveParticles } - render() { + render(): void { this.ctx.beginPath() this.ctx.fillStyle = 'rgba(0, 0, 0, 0.1)' // Ghostly effect this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height) diff --git a/src/frontend/PuzzleGraphics.ts b/src/frontend/PuzzleGraphics.ts index c4c7716..6debdbe 100644 --- a/src/frontend/PuzzleGraphics.ts +++ b/src/frontend/PuzzleGraphics.ts @@ -3,22 +3,22 @@ import Geometry, { Rect } from '../common/Geometry' import Graphics from './Graphics' import Util, { logger } from './../common/Util' -import { Puzzle, PuzzleInfo, PieceShape } from './../common/GameCommon' +import { Puzzle, PuzzleInfo, PieceShape, EncodedPiece } from './../common/GameCommon' const log = logger('PuzzleGraphics.js') async function createPuzzleTileBitmaps( img: ImageBitmap, - tiles: Array, + pieces: EncodedPiece[], info: PuzzleInfo ): Promise> { log.log('start createPuzzleTileBitmaps') - var tileSize = info.tileSize - var tileMarginWidth = info.tileMarginWidth - var tileDrawSize = info.tileDrawSize - var tileRatio = tileSize / 100.0 + const tileSize = info.tileSize + const tileMarginWidth = info.tileMarginWidth + const tileDrawSize = info.tileDrawSize + const tileRatio = tileSize / 100.0 - var curvyCoords = [ + const curvyCoords = [ 0, 0, 40, 15, 37, 5, 37, 5, 40, 0, 38, -5, 38, -5, 20, -20, 50, -20, @@ -27,7 +27,7 @@ async function createPuzzleTileBitmaps( 63, 5, 65, 15, 100, 0 ]; - const bitmaps: Array = new Array(tiles.length) + const bitmaps: Array = new Array(pieces.length) const paths: Record = {} function pathForShape(shape: PieceShape) { @@ -65,9 +65,9 @@ async function createPuzzleTileBitmaps( } if (shape.bottom !== 0) { for (let i = 0; i < curvyCoords.length / 6; i++) { - let p1 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 0] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 1] * tileRatio }) - let p2 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 2] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 3] * tileRatio }) - let p3 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 4] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 5] * tileRatio }) + const p1 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 0] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 1] * tileRatio }) + const p2 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 2] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 3] * tileRatio }) + const p3 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 4] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 5] * tileRatio }) path.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); } } else { @@ -75,9 +75,9 @@ async function createPuzzleTileBitmaps( } if (shape.left !== 0) { for (let i = 0; i < curvyCoords.length / 6; i++) { - let p1 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 1] * tileRatio, y: curvyCoords[i * 6 + 0] * tileRatio }) - let p2 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 3] * tileRatio, y: curvyCoords[i * 6 + 2] * tileRatio }) - let p3 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 5] * tileRatio, y: curvyCoords[i * 6 + 4] * tileRatio }) + const p1 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 1] * tileRatio, y: curvyCoords[i * 6 + 0] * tileRatio }) + const p2 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 3] * tileRatio, y: curvyCoords[i * 6 + 2] * tileRatio }) + const p3 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 5] * tileRatio, y: curvyCoords[i * 6 + 4] * tileRatio }) path.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); } } else { @@ -93,10 +93,10 @@ async function createPuzzleTileBitmaps( const c2 = Graphics.createCanvas(tileDrawSize, tileDrawSize) const ctx2 = c2.getContext('2d') as CanvasRenderingContext2D - for (const t of tiles) { - const tile = Util.decodePiece(t) - const srcRect = srcRectByIdx(info, tile.idx) - const path = pathForShape(Util.decodeShape(info.shapes[tile.idx])) + for (const p of pieces) { + const piece = Util.decodePiece(p) + const srcRect = srcRectByIdx(info, piece.idx) + const path = pathForShape(Util.decodeShape(info.shapes[piece.idx])) ctx.clearRect(0, 0, tileDrawSize, tileDrawSize) @@ -195,7 +195,7 @@ async function createPuzzleTileBitmaps( ctx2.restore() ctx.drawImage(c2, 0, 0) - bitmaps[tile.idx] = await createImageBitmap(c) + bitmaps[piece.idx] = await createImageBitmap(c) } log.log('end createPuzzleTileBitmaps') @@ -203,7 +203,7 @@ async function createPuzzleTileBitmaps( } function srcRectByIdx(puzzleInfo: PuzzleInfo, idx: number): Rect { - const c = Util.coordByTileIdx(puzzleInfo, idx) + const c = Util.coordByPieceIdx(puzzleInfo, idx) return { x: c.x * puzzleInfo.tileSize, y: c.y * puzzleInfo.tileSize, diff --git a/src/frontend/components/NewGameDialog.vue b/src/frontend/components/NewGameDialog.vue index 0eff20a..04a7eff 100644 --- a/src/frontend/components/NewGameDialog.vue +++ b/src/frontend/components/NewGameDialog.vue @@ -36,7 +36,7 @@ + diff --git a/build/server/main.js b/build/server/main.js index fd0a8d0..5cdd983 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -2027,7 +2027,7 @@ app.post('/api/upload', (req, res) => { res.send(Images.imageFromDb(db, imageId)); }); }); -app.post('/newgame', express.json(), async (req, res) => { +app.post('/api/newgame', express.json(), async (req, res) => { const gameSettings = req.body; log.log(gameSettings); const gameId = Util.uniqId(); diff --git a/src/frontend/Communication.ts b/src/frontend/Communication.ts index 2b52b77..0e1e9ea 100644 --- a/src/frontend/Communication.ts +++ b/src/frontend/Communication.ts @@ -16,19 +16,31 @@ const CONN_STATE_CONNECTING = 3 // connecting const CONN_STATE_CLOSED = 4 // not connected (closed on purpose) let ws: WebSocket + +let missedMessages: Array = [] let changesCallback = (msg: Array) => { - // empty -} -let connectionStateChangeCallback = (state: number) => { - // empty + missedMessages.push(msg) +} + +let missedStateChanges: Array = [] +let connectionStateChangeCallback = (state: number) => { + missedStateChanges.push(state) } -// TODO: change these to something like on(EVT, cb) function onServerChange(callback: (msg: Array) => void): void { changesCallback = callback + for (const missedMessage of missedMessages) { + changesCallback(missedMessage) + } + missedMessages = [] } + function onConnectionStateChange(callback: (state: number) => void): void { connectionStateChangeCallback = callback + for (const missedStateChange of missedStateChanges) { + connectionStateChangeCallback(missedStateChange) + } + missedStateChanges = [] } let connectionState = CONN_STATE_NOT_CONNECTED @@ -48,7 +60,6 @@ function send(message: ClientEvent): void { } } - let clientSeq: number let events: Record diff --git a/src/server/main.ts b/src/server/main.ts index 024645d..4737326 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -189,7 +189,7 @@ app.post('/api/upload', (req, res) => { }) }) -app.post('/newgame', express.json(), async (req, res) => { +app.post('/api/newgame', express.json(), async (req, res) => { const gameSettings = req.body as GameSettings log.log(gameSettings) const gameId = Util.uniqId() From 21d7db5677d95fe6cdec968e78e44c716ca2f757 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 29 May 2021 23:14:19 +0200 Subject: [PATCH 12/78] sound when pieces connect --- build/public/assets/click.550555f3.mp3 | Bin 0 -> 6783 bytes build/public/assets/index.4d785533.js | 1 - build/public/assets/index.d7fe3ee6.js | 1 + ...{vendor.b622ee49.js => vendor.18cd2d7e.js} | 4 +- build/public/index.html | 4 +- build/server/main.js | 10 ++++- src/common/GameCommon.ts | 9 ++++- src/common/Protocol.ts | 2 + src/frontend/click.mp3 | Bin 0 -> 6783 bytes src/frontend/components/HelpOverlay.vue | 1 + src/frontend/components/SettingsOverlay.vue | 4 ++ src/frontend/game.ts | 35 +++++++++++++++++- src/frontend/views/Game.vue | 6 +++ src/frontend/views/Replay.vue | 6 +++ 14 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 build/public/assets/click.550555f3.mp3 delete mode 100644 build/public/assets/index.4d785533.js create mode 100644 build/public/assets/index.d7fe3ee6.js rename build/public/assets/{vendor.b622ee49.js => vendor.18cd2d7e.js} (74%) create mode 100644 src/frontend/click.mp3 diff --git a/build/public/assets/click.550555f3.mp3 b/build/public/assets/click.550555f3.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..af958b7f947a40692fd0f3f03a0f84708936ed06 GIT binary patch literal 6783 zcmeI0XHb((|L+3?h=52{K#&$f73sZ54<(e)rFVjWz)eR@=phv89iPzlNhKU zo2ur3I)D1~2jnm~WI0x)^XN>Ti>`wCxmnl5{H2KAn?^Jad; zb#ZP5?K3Cac!`ItR^bnPUHr=b#UL^2^@}gXg-81wf!gq%9Fyy-tu^?yy8bzTW88W* z{OLZQSz}}KoZTDA7d-R>8KSM<)l=M-BA1PJ9r)f%IKTsUu7Yqpv)l7C%t4o_l9tW? zkX!EgRGePMRjNC81kTXyHQ3@m!Ht!UsEG&w@Bjip7HUFZ+SOglEGHti_&$ui1AbR8 zTWCfc2ue1c{RuzaL}IK5H6j`|(5=9p%x^`|5ZWg!0z|1c1!E=yLvtTLu%&vWLco{t zx;Gf)f#nXmZ&QQs#Ue1n1!Mc*5yIPv=M*JL35^mBEc9j>6H8QHq{+-uXoHtsWxYth~%q2 zu{~7*hh&4Zk@*yP$#2zyUP8y?rWujk+@+jmZT!NlH8VfXKhKX%vPOD!mp1Wy7i=^} z21zxU_OGNbmued|v^%H|WI8X(6(wiw1#Gx0(tIi;0gw>@i16?MC;^tt;BNAW)<2s& z*y??TVw!j$=U~zBGD0cL#HIpvqAhlf4&&x^N@;*MWLERZbz4Xbk%h!xBc)D~WHW^# z#;=M}hn}}TA-XpdHd4xx@~t&Jl)12Yk-vU_(aGgIn=j3gMevcQn@>?o#e@2Ecjj#yrck1boMf1HqdiV)(tkh*8k|WvRXgq+A-XgQ}R4y~s1yo1A zqQ=%3FcPh7>Sp{KA#xiN?AHMxeS7`aFTf$E!*jS1>kyu(0>krmRLG>zmb}n`E*EWm znxyxHv@xGypW(oTl)0Pc`58`VcH6`zWs~Y%?VwJYU2LZ2vsB|>-Mjw1INZ&6kIC9> z>i`=7`@e!IUL--jmK|Y)gq+7l-^Gt0>&*)ChX5IDKq^PC788rY7#l<=q!thy!<;Um z9{GSCjVfiOGNDnnRvs9&b><`?E6bp;iJ(NOed!vmxo-!72sDu-sOy11wdyfBj1s>i z?=k4vWHr*)))oOH@aXZ-0$93dvEU>EJTiMm3Zvdve6NbAxhW3%T)hgLa_`DM?#Zfr4r6sF!vxmS z;Z=82D3@7KuS#7^bSG5&z-Em^H_WVw^1&dMJMu%YE=+y8&w^|GL<=%=GL{;dX&|gu zATxF~>~oH^g!Wc-$MrPy2b;ISN~c+&uSB1T=D03rzMoHDh&8I8N;bgyGM+xv=N{xLhitvKCg!XSpJ<;d;ov~FtgovMjqwyZS=hCQ0t!t z&vJ#NwmOx%+eFBgoTbn z4fwE=sq1~)#h7iR>3BVfH4%N6?Ng&;{Hm`^)xv!oq#>C>&qxx#>(Sp<^d3mV| zuZ$|BI^KOP&<@&kXsZj7U-w^&47ylsH9puC7haqzn<awM~qb;b)=>oSq>{t1^`FtfpddUAMr@8CX;-{(D)9qHIE;oE3W~5>oO{af{U8_bq}cK&|Lv ztDgiesm>)~qVwx{7B+mCKVy3F9zf-a`Cuk80d=KdenCwtN=<0d<6wBQIQbtMg+inV zD=%EIH8+qnTpf2=WB(N&^3r(>X$Js!BU+d@asjj!Z{&Am?+r{BJ6#87_&>BPA*()C zVEB;~ai8BOvY^POir;W^`=kGRv?l*YTToM8feI`?Y$fy5tQ?0s)xlObX$F3SKzLqO zVHmj7R4Y3%N(+xjxa_BEg@*LURfiG+8HOh6P$5YP8u5$I z4IgVx)A|$H7pk07#~Lgjx2` z(As>&Lxo<;_X^jdxZ^aGXE$tAX5LU8l$F;T=uly0TZpz%)o5IZp1a4bg;T`OGY9wL zlTM8U?c*+gZ9NsUu-kw2aP@-_D!ldJuXxAR%Rk6zEefs^GctTux9x;l#V4dVO*i>pU?Oou5fnyvw}j*cb@qQ>%ZR)6q4vODpaP&AbnL z`)Xw$_Xk(z?EkOhhEc!`EuBlP`}rQvn{oEGui?uU3OnHOfwkhzm%kZ27k(6(9D?q? zCKCFx-GSTJOF$9C9oZk@>1KsjC}MK4pl?H5WGFf!y@$T~l2I0 zC2u)v3vPI7&MDu5jgQ^^zG5SjsCdczRkYM;>~gu?t1; zdDSm@dmp(`WD=6h?55`rcX{!J$?EzNBP0gUZjUTl<0V3UQqI>oIiBfBeT8Sj$nruP zO^<;+hymA4yCvqui-3cG@*_6n*#iV zahfz{-5KF`d+?5{4z7WPh6DX zKEkYHdgOlRLUJu9Ew8gI6DJ??+7@#iv$jVV8k7netq{?Z$>^3R1ypyO--H*cOpr96 zhjZ{n%JA2bbX&S?+{nqFX`YpphcQ8~C3@nXooDU1inU(0!?E%B4u1C@@+w+;63KA& zZOg!o3Oyu09!(?kO1q#K5W{z;$@c?QWF$N=7IcTu0Q0eRK;W* zz4|t>ING*ex977=I(`cmx-(`D=dF0vuUbb7pLc2)d)`a$h^28R=}~88?F}Ho{ z%k)QH&Ypc^{MT`-C}4^9SkOHUp;u%V%=XZ9xL&CIP7!6)@%xgigZ%LeKIm^tSy#7@ zuhL^!W1qN@CRS>QS{^QmmGu!9vWDo57NN=lWgx%bBm)-Q=-X& zr@y6lTC@Iy>VN*xcD1lsfG%y205EnKYOzehnkNHMo2Vo4n01}_F^LW_CIy3XBanR1O;!@6ud}-UxdM-Qt~(JsfK#}c4v<;-3~MAAtHCln_af|_e(FTi&o;wv8agV3cqux{ z&)Wz?K7D1Gmc?D>+Fzi6=K9(}WJ+Crq^3BVa1*^Xaq(S&VTE6i;(&;|)+qZtXE{7D zr3ec=N>uyoQblPgWzKf|zNzyblt%G2u(Ke_jeVk+uRKveYqwJRX^KO zIc14n$k$XA4qK_l^PdH{#6C^ZkP>!P)$sSWt3%LSvvzfSmH%cRrrDR5CMn1&&$0K- zO~beP=DJOGzb&gX^PYg3m3iZ7yRu}R>)um{!%yo0UQjZKf$2aUpC5}F2hr6qjDY3F zhNfntds)L&Z{GI(8+$fTz!7cDqjabTYWNq@A=Hv`mz{SP?1>R8Es_l-4-h?d(f~t9 zSoHV`KBv(%vM|Ou!t&$r2gtZQ#QeUm`x=u~AWhMP)hQm^(wJdN(p zuc*9;#f1JTygm;?2CfE(AUBNk5?#gYHL_Y{Wrq0#*J*gy@NC z7C1SJ@40+(IncSe`HHAz-si*&_b3aqL06#h9{5pRU3rP1c+)2s0(0#a8%dI!KI6xZ z;Z3nLK*G7Fo>8W|$!?62&z_pGyG)ysk}?kT1-48enqMZ_8a|>EC1OxP%aA;d(qkI9 z^8xF31lFxx;ch8h8-1)T-ytG90(&;R+~BiuxIrnrP{N4_xaoKy-f<-0&5*NCNeHbgQ+RRuyCW_ zqDhGQdmt~kJa}#$r+kP4qMS5??mYYvTOMF#gSOYJ%;DWBe1VaZ{MT^^(Kp_UXPcFN zS({VuD{{^u8Z&(X7PcU;@4cumf|3+2x~uz0DVUT|!hL&Ojq#XbLX+aE^R)DOkE3RY zYwK2()rJo8xc<%6?>PGjmJb|1u^XJ#~jK%9oeDqwSwC8%7T0SJ{D+5RkT3hH^{7on+lZRepPt!ONPuq=58H z*@W>oz~6TT31x#u3cP#ds*r)^B4T!2Zj8TteVd&JbL%YLObt$+o{Q=nhLgTXZdrBU z+rtD%ayso-uLo>YtYeXQ6m15>L;0#n2nr&aBD8?Qct^#YZKXk5F#AYd&9HR|?RW-i zC5a@9L5Ua^97FEY#haxB=hYQ0lio{O7@0EL?Q&%Ho(N7-T@49{(_n3cir12{JJ`_< z303ZSsL_||o6=J{_eiiUl^A~xG}Zbh%rjSf0Y1N6n&~hs9$f6OYJul&sjg1zaKhMp%HUG1_BBkHak;K+xA`Dh#Y}lZhZ5`!&&ONFtdLBGN z?UCKLeOu7tbUbAa4)~`gN|3j{7Vi6 z[S])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:o((()=>[P])),_:1})])])):i("",!0),n(p)])};const I=864e5,T=e=>{const t=Math.floor(e/I);e%=I;const n=Math.floor(e/36e5);e%=36e5;const o=Math.floor(e/6e4);e%=6e4;return`${t}d ${n}h ${o}m ${Math.floor(e/1e3)}s`};var _=1e3,D=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>T(t-e),E=T,O=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",o=e,i=t||D();return`${n} ${B(o,i)}`}}});const N={class:"game-info-text"},U=n("br",null,null,-1),M=n("br",null,null,-1),G=n("br",null,null,-1),$=s(" ↪️ Watch replay ");O.render=function(e,c,d,u,g,p){const h=l("router-link");return a(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",N,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),U,s(" 👥 "+r(e.game.players),1),M,s(" "+r(e.time(e.game.started,e.game.finished)),1),G])])),_:1},8,["to"]),e.game.hasReplay?(a(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[$])),_:1},8,["to"])):i("",!0)],4)};var R=e({components:{GameTeaser:O},data:()=>({gamesRunning:[],gamesFinished:[]}),async created(){const e=await fetch("/api/index-data"),t=await e.json();this.gamesRunning=t.gamesRunning,this.gamesFinished=t.gamesFinished}});const V=n("h1",null,"Running games",-1),j=n("h1",null,"Finished games",-1);R.render=function(e,o,i,s,r,u){const g=l("game-teaser");return a(),t("div",null,[V,(a(!0),t(c,null,d(e.gamesRunning,((e,o)=>(a(),t("div",{class:"game-teaser-wrap",key:o},[n(g,{game:e},null,8,["game"])])))),128)),j,(a(!0),t(c,null,d(e.gamesFinished,((e,o)=>(a(),t("div",{class:"game-teaser-wrap",key:o},[n(g,{game:e},null,8,["game"])])))),128))])};var F=e({name:"image-teaser",props:{image:{type:Object,required:!0}},computed:{style(){return{backgroundImage:`url("${this.image.url.replace("uploads/","uploads/r/")+"-150x100.webp"}")`}}},emits:{click:null,editClick:null},methods:{onClick(){this.$emit("click")},onEditClick(){this.$emit("editClick")}}});F.render=function(e,o,i,l,s,r){return a(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var L=e({name:"image-library",components:{ImageTeaser:F},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});L.render=function(e,n,o,i,s,r){const u=l("image-teaser");return a(),t("div",null,[(a(!0),t(c,null,d(e.images,((n,o)=>(a(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])};const W={name:"responsive-image",props:{src:String,title:{type:String,default:""},height:{type:String,default:"100%"},width:{type:String,default:"100%"}},computed:{style(){return{display:"inline-block",verticalAlign:"text-bottom",backgroundImage:`url('${this.src}')`,backgroundRepeat:"no-repeat",backgroundSize:"contain",backgroundPosition:"center",width:this.width,height:this.height}}}};W.render=function(e,n,o,i,l,s){return a(),t("div",{style:s.style,title:o.title},null,12,["title"])};var q=e({name:"tags-input",props:{modelValue:{type:Array,required:!0}},emits:{"update:modelValue":null},data:()=>({input:"",values:[]}),created(){this.values=this.modelValue},methods:{onKeyUp(e){if(","===e.key)return this.add(),e.stopPropagation(),!1},add(){const e=this.input.replace(/,/g,"").trim();e&&(this.values.includes(e)||this.values.push(e),this.input="",this.$emit("update:modelValue",this.values))},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const H=m()(((e,o,i,l,s,u)=>(a(),t("div",null,[g(n("input",{class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onKeydown:o[2]||(o[2]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[3]||(o[3]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[p,e.input]]),(a(!0),t(c,null,d(e.values,((n,o)=>(a(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));q.render=H,q.__scopeId="data-v-771460ae";var Q=e({name:"new-image-dialog",components:{ResponsiveImage:W,TagsInput:q},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const o=new FileReader;o.readAsDataURL(n),o.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const Y={key:0,class:"has-image"},K={key:1},Z={class:"upload"},J=n("span",{class:"btn"},"Upload File",-1),X={class:"area-settings"},ee=n("td",null,[n("label",null,"Title")],-1),te=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),ne=n("td",null,[n("label",null,"Tags")],-1),oe={class:"area-buttons"},ie=s("🧩 Post to gallery "),le=n("br",null,null,-1),ae=s(" + set up game");Q.render=function(e,o,i,s,r,c){const d=l("responsive-image"),h=l("tags-input");return a(),t("div",{class:"overlay new-image-dialog",onClick:o[8]||(o[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[7]||(o[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(a(),t("div",Y,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(d,{src:e.previewUrl},null,8,["src"])])):(a(),t("div",K,[n("label",Z,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),J])]))],2),n("div",X,[n("table",null,[n("tr",null,[ee,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[3]||(o[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),te,n("tr",null,[ne,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[4]||(o[4]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",oe,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[5]||(o[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[6]||(o[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[ie,le,ae],8,["disabled"])])])])};var se=e({name:"edit-image-dialog",components:{ResponsiveImage:W,TagsInput:q},props:{image:{type:Object,required:!0}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const re={class:"area-image"},ce={class:"has-image"},de={class:"area-settings"},ue=n("td",null,[n("label",null,"Title")],-1),ge=n("tr",null,[n("td",{colspan:"2"},[n("div",{class:"hint"},"Feel free to leave a credit to the artist/photographer in the title :)")])],-1),pe=n("td",null,[n("label",null,"Tags")],-1),he={class:"area-buttons"};var me,ye,fe,we;se.render=function(e,o,i,s,r,c){const d=l("responsive-image"),h=l("tags-input");return a(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",re,[n("div",ce,[n(d,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",de,[n("table",null,[n("tr",null,[ue,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ge,n("tr",null,[pe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",he,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])},(ye=me||(me={}))[ye.Flat=0]="Flat",ye[ye.Out=1]="Out",ye[ye.In=-1]="In",(we=fe||(fe={}))[we.FINAL=0]="FINAL",we[we.ANY=1]="ANY";var ve=e({name:"new-game-dialog",components:{ResponsiveImage:W},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:fe.ANY}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const be={class:"area-image"},xe={class:"has-image"},Ce={class:"area-settings"},ke=n("td",null,[n("label",null,"Pieces")],-1),Ae=n("td",null,[n("label",null,"Scoring: ")],-1),ze=s(" Any (Score when pieces are connected to each other or on final location)"),Se=n("br",null,null,-1),Pe=s(" Final (Score when pieces are put to their final location)"),Ie={class:"area-buttons"};ve.render=function(e,o,i,s,r,c){const d=l("responsive-image");return a(),t("div",{class:"overlay new-game-dialog",onClick:o[6]||(o[6]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[5]||(o[5]=u((()=>{}),["stop"]))},[n("div",be,[n("div",xe,[n(d,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ce,[n("table",null,[n("tr",null,[ke,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Ae,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[y,e.scoreMode]]),ze]),Se,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[y,e.scoreMode]]),Pe])])])])]),n("div",Ie,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[4]||(o[4]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};class Te{constructor(e){this.rand_high=e||3735929054,this.rand_low=1231121986^e}random(e,t){this.rand_high=(this.rand_high<<16)+(this.rand_high>>16)+this.rand_low&4294967295,this.rand_low=this.rand_low+this.rand_high&4294967295;return e+(this.rand_high>>>0)/4294967295*(t-e+1)|0}choice(e){return e[this.random(0,e.length-1)]}shuffle(e){const t=e.slice();for(let n=0;n<=t.length-2;n++){const e=this.random(n,t.length-1),o=t[n];t[n]=t[e],t[e]=o}return t}static serialize(e){return{rand_high:e.rand_high,rand_low:e.rand_low}}static unserialize(e){const t=new Te(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const _e=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},De=(...e)=>{const t=t=>(...n)=>{const o=new Date,i=_e(o.getHours(),"00"),l=_e(o.getMinutes(),"00"),a=_e(o.getSeconds(),"00");console[t](`${i}:${l}:${a}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var Be={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",Te.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||fe.FINAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:Te.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}},Ee=e({components:{ImageLibrary:L,NewImageDialog:Q,EditImageDialog:se,NewGameDialog:ve},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},methods:{toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${Be.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/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 Oe={class:"upload-image-teaser"},Ne=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),Ue={key:0},Me=s(" Tags: "),Ge=s(" Sort by: "),$e=n("option",{value:"date_desc"},"Newest first",-1),Re=n("option",{value:"date_asc"},"Oldest first",-1),Ve=n("option",{value:"alpha_asc"},"A-Z",-1),je=n("option",{value:"alpha_desc"},"Z-A",-1);Ee.render=function(e,o,s,u,p,h){const m=l("image-library"),y=l("new-image-dialog"),w=l("edit-image-dialog"),v=l("new-game-dialog");return a(),t("div",null,[n("div",Oe,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),Ne]),n("div",null,[e.tags.length>0?(a(),t("label",Ue,[Me,(a(!0),t(c,null,d(e.tags,((n,o)=>(a(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title),11,["onClick"])))),128))])):i("",!0),n("label",null,[Ge,g(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[$e,Re,Ve,je],544),[[f,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(a(),t(y,{key:0,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["onPostToGalleryClick","onSetupGameClick"])):i("",!0),"edit-image"===e.dialog?(a(),t(w,{key:1,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["onSaveClick","image"])):i("",!0),e.image&&"new-game"===e.dialog?(a(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):i("",!0)])};var Fe=e({name:"scores",props:{activePlayers:{type:Array,required:!0},idlePlayers:{type:Array,required:!0}},computed:{actives(){return this.activePlayers.sort(((e,t)=>t.points-e.points)),this.activePlayers},idles(){return this.idlePlayers.sort(((e,t)=>t.points-e.points)),this.idlePlayers}}});const Le={class:"scores"},We=n("div",null,"Scores",-1),qe=n("td",null,"⚡",-1),He=n("td",null,"💤",-1);Fe.render=function(e,o,i,l,s,u){return a(),t("div",Le,[We,n("table",null,[(a(!0),t(c,null,d(e.actives,((e,o)=>(a(),t("tr",{key:o,style:{color:e.color}},[qe,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(a(!0),t(c,null,d(e.idles,((e,o)=>(a(),t("tr",{key:o,style:{color:e.color}},[He,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var Qe=e({name:"puzzle-status",props:{finished:{type:Boolean,required:!0},duration:{type:Number,required:!0},piecesDone:{type:Number,required:!0},piecesTotal:{type:Number,required:!0}},computed:{icon(){return this.finished?"🏁":"⏳"},durationStr(){return E(this.duration)}}});const Ye={class:"timer"};Qe.render=function(e,o,i,l,s,c){return a(),t("div",Ye,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),w(e.$slots,"default")])};var Ke=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:Object},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const Ze=n("td",null,[n("label",null,"Background: ")],-1),Je=n("td",null,[n("label",null,"Color: ")],-1),Xe=n("td",null,[n("label",null,"Name: ")],-1);Ke.render=function(e,o,i,l,s,r){return a(),t("div",{class:"overlay transparent",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content settings",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("tr",null,[Ze,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[Je,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[Xe,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])])])])};var et=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const tt={class:"preview"};et.render=function(e,o,i,l,s,r){return a(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",tt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var nt=1,ot=4,it=2,lt=3,at=2,st=4,rt=3,ct=9,dt=1,ut=2,gt=3,pt=4,ht=5,mt=6,yt=7,ft=8,wt=10,vt=1,bt=2,xt=3;const Ct=De("Communication.js");let kt,At=[],zt=e=>{At.push(e)},St=[],Pt=e=>{St.push(e)};let It=0;const Tt=e=>{It!==e&&(It=e,Pt(e))};function _t(e){if(2===It)try{kt.send(JSON.stringify(e))}catch(t){Ct.info("unable to send message.. maybe because ws is invalid?")}}let Dt,Bt;var Et={connect:function(e,t,n){return Dt=0,Bt={},Tt(3),new Promise((o=>{kt=new WebSocket(e,n+"|"+t),kt.onopen=()=>{Tt(2),_t([lt])},kt.onmessage=e=>{const t=JSON.parse(e.data),i=t[0];if(i===ot){const e=t[1];o(e)}else{if(i!==nt)throw`[ 2021-05-09 invalid connect msgType ${i} ]`;{const e=t[1],o=t[2];if(e===n&&Bt[o])return void delete Bt[o];zt(t)}}},kt.onerror=()=>{throw Tt(1),"[ 2021-05-15 onerror ]"},kt.onclose=e=>{4e3===e.code||1001===e.code?Tt(4):Tt(1)}}))},requestReplayData:async function(e,t,n){const o={gameId:e,offset:t,size:n},i=await fetch(`/api/replay-data${Be.asQueryArgs(o)}`);return await i.json()},disconnect:function(){kt&&kt.close(4e3),Dt=0,Bt={}},sendClientEvent:function(e){Dt++,Bt[Dt]=e,_t([it,Dt,Bt[Dt]])},onServerChange:function(e){zt=e;for(const t of At)zt(t);At=[]},onConnectionStateChange:function(e){Pt=e;for(const t of St)Pt(t);St=[]},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},Ot=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Et.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Et.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const Nt={key:0,class:"overlay connection-lost"},Ut={key:0,class:"overlay-content"},Mt=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Gt={key:1,class:"overlay-content"},$t=n("div",null,"Connecting...",-1);Ot.render=function(e,o,l,s,r,c){return e.show?(a(),t("div",Nt,[e.lostConnection?(a(),t("div",Ut,[Mt,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):i("",!0),e.connecting?(a(),t("div",Gt,[$t])):i("",!0)])):i("",!0)};var Rt=e({name:"help-overlay",emits:{bgclick:null}});const Vt=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),jt=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),Ft=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),Lt=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),Wt=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),qt=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),Ht=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),Qt=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Yt=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Kt=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1);Rt.render=function(e,o,i,l,s,r){return a(),t("div",{class:"overlay transparent",onClick:o[2]||(o[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:o[1]||(o[1]=u((()=>{}),["stop"]))},[Vt,jt,Ft,Lt,Wt,qt,Ht,Qt,Yt,Kt])])};var Zt=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),Jt=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),Xt=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),en=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,i)=>{e+=o/n,t+=i/n},i=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},l=o=>({x:o.x/n-e,y:o.y/n-t}),a=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n});return{move:o,canZoom:e=>n!=i(e),zoom:(e,t)=>((e,t)=>{if(n==e)return!1;const i=1-n/e;return o(-t.x*i,-t.y*i),n=e,!0})(i(e),t),worldToViewport:e=>{const{x:t,y:n}=a(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:a,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=l(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:l}}function nn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var on={createCanvas:nn,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=nn(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=nn(e.width,e.height),i=o.getContext("2d");return i.save(),i.drawImage(t,0,0),i.fillStyle=n,i.globalCompositeOperation="source-in",i.fillRect(0,0,t.width,t.height),i.restore(),i.save(),i.globalCompositeOperation="destination-over",i.drawImage(e,0,0),i.restore(),o}};const ln=De("Debug.js");let an=0,sn=0;var rn=e=>{an=performance.now(),sn=e},cn=e=>{const t=performance.now(),n=t-an;n>sn&&ln.log(e+": "+n),an=t};function dn(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 gn={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:dn,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 dn(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 pn=De("PuzzleGraphics.js");function hn(e,t){const n=Be.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var mn={loadPuzzleBitmaps:async function(e){const t=await on.loadImageToBitmap(e.info.imageUrl),n=await on.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){pn.log("start createPuzzleTileBitmaps");const o=n.tileSize,i=n.tileMarginWidth,l=n.tileDrawSize,a=o/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),c={};function d(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(c[t])return c[t];const n=new Path2D,l={x:i,y:i},r=gn.pointAdd(l,{x:o,y:0}),d=gn.pointAdd(r,{x:0,y:o}),u=gn.pointSub(d,{x:o,y:0});if(n.moveTo(l.x,l.y),0!==e.top)for(let o=0;oBe.decodePiece(yn[e].puzzle.tiles[t]),_n=(e,t)=>Tn(e,t).group,Dn=(e,t)=>{const n=yn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},i=function(e,t){const n=yn[e].puzzle.info,o=Be.coordByPieceIdx(n,t),i=o.x*n.tileSize,l=o.y*n.tileSize;return{x:i,y:l}}(e,t);return gn.pointAdd(o,i)},Bn=(e,t)=>Tn(e,t).pos,En=e=>{const t=Qn(e),n=Yn(e),o=Math.round(t/4),i=Math.round(n/4);return{x:0-o,y:0-i,w:t+2*o,h:n+2*i}},On=(e,t)=>{const n=Gn(e),o=Tn(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Nn=(e,t)=>Tn(e,t).z,Un=(e,t)=>{for(const n of yn[e].puzzle.tiles){const e=Be.decodePiece(n);if(e.owner===t)return e.idx}return-1},Mn=e=>yn[e].puzzle.info.tileDrawSize,Gn=e=>yn[e].puzzle.info.tileSize,$n=e=>yn[e].puzzle.data.maxGroup,Rn=e=>yn[e].puzzle.data.maxZ;function Vn(e,t){const n=yn[e].puzzle.info,o=Be.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const jn=(e,t,n)=>{for(const o of t)In(e,o,{z:n})},Fn=(e,t,n)=>{const o=Bn(e,t);In(e,t,{pos:gn.pointAdd(o,n)})},Ln=(e,t,n)=>{const o=Mn(e),i=En(e),l=n;for(const a of t){const t=Tn(e,a);t.pos.x+n.xi.x+i.w&&(l.x=Math.min(i.x+i.w-t.pos.x+o,l.x)),t.pos.y+n.yi.y+i.h&&(l.y=Math.min(i.y+i.h-t.pos.y+o,l.y))}for(const a of t)Fn(e,a,l)},Wn=(e,t,n)=>{for(const o of t)In(e,o,{owner:n})};function qn(e,t){const n=yn[e].puzzle.tiles,o=Be.decodePiece(n[t]),i=[];if(o.group)for(const l of n){const e=Be.decodePiece(l);e.group===o.group&&i.push(e.idx)}else i.push(o.idx);return i}const Hn=(e,t)=>{const n=wn(e,t);return n?n.points:0},Qn=e=>yn[e].puzzle.info.table.width,Yn=e=>yn[e].puzzle.info.table.height;var Kn={setGame:function(e,t){yn[e]=t},exists:function(e){return!!yn[e]||!1},playerExists:bn,getActivePlayers:function(e,t){const n=t-30*_;return xn(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*_;return xn(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){bn(e,t)?Sn(e,t,{ts:n}):vn(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:Cn,getImageUrl:function(e){return yn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){yn[e].puzzle.info.imageUrl=t},get:function(e){return yn[e]||null},getAllGames:function(){return Object.values(yn).sort(((e,t)=>An(e.id)===An(t.id)?t.puzzle.data.started-e.puzzle.data.started:An(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=wn(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=wn(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=wn(e,t);return n?n.name:null},getPlayerIndexById:fn,getPlayerIdByIndex:function(e,t){return yn[e].players.length>t?Be.decodePlayer(yn[e].players[t]).id:null},changePlayer:Sn,setPlayer:vn,setPiece:function(e,t,n){yn[e].puzzle.tiles[t]=Be.encodePiece(n)},setPuzzleData:function(e,t){yn[e].puzzle.data=t},getTableWidth:Qn,getTableHeight:Yn,getPuzzle:e=>yn[e].puzzle,getRng:e=>yn[e].rng.obj,getPuzzleWidth:e=>yn[e].puzzle.info.width,getPuzzleHeight:e=>yn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return yn[e].puzzle.tiles.map(Be.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Un(e,t);return n<0?null:yn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>yn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:Mn,getFinalPiecePos:Dn,getStartTs:e=>yn[e].puzzle.data.started,getFinishTs:e=>yn[e].puzzle.data.finished,handleInput:function(e,t,n,o){const i=yn[e].puzzle,l=function(e,t){return t in yn[e].evtInfos?yn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),a=[],s=()=>{a.push([vt,i.data])},r=t=>{a.push([bt,Be.encodePiece(Tn(e,t))])},c=e=>{for(const t of e)r(t)},d=()=>{const n=wn(e,t);n&&a.push([xt,Be.encodePlayer(n)])},u=n[0];if(u===mt){const i=n[1];Sn(e,t,{bgcolor:i,ts:o}),d()}else if(u===yt){const i=n[1];Sn(e,t,{color:i,ts:o}),d()}else if(u===ft){const i=`${n[1]}`.substr(0,16);Sn(e,t,{name:i,ts:o}),d()}else if(u===dt){const i={x:n[1],y:n[2]};Sn(e,t,{d:1,ts:o}),d(),l._last_mouse_down=i;const a=((e,t)=>{const n=yn[e].puzzle.info,o=yn[e].puzzle.tiles;let i=-1,l=-1;for(let a=0;ai)&&(i=e.z,l=a)}return l})(e,i);if(a>=0){const n=Rn(e)+1;Pn(e,{maxZ:n}),s();const o=qn(e,a);jn(e,o,Rn(e)),Wn(e,o,t),c(o)}l._last_mouse=i}else if(u===gt){const i=n[1],a=n[2],s={x:i,y:a};if(null===l._last_mouse_down)Sn(e,t,{x:i,y:a,ts:o}),d();else{const n=Un(e,t);if(n>=0){Sn(e,t,{x:i,y:a,ts:o}),d();const r=qn(e,n);let u=gn.pointInBounds(s,En(e))&&gn.pointInBounds(l._last_mouse_down,En(e));for(const t of r){const n=On(e,t);if(gn.pointInBounds(s,n)){u=!0;break}}if(u){const t=i-l._last_mouse_down.x,n=a-l._last_mouse_down.y;Ln(e,r,{x:t,y:n}),c(r)}}else Sn(e,t,{ts:o}),d();l._last_mouse_down=s}l._last_mouse=s}else if(u===ut){const a={x:n[1],y:n[2]},u=0;l._last_mouse_down=null;const g=Un(e,t);if(g>=0){const n=qn(e,g);Wn(e,n,0),c(n);const l=Bn(e,g),a=Dn(e,g);if(gn.pointDistance(a,l){for(const n of t)In(e,n,{owner:-1,z:1})})(e,n),c(n);let r=Hn(e,t);kn(e)===fe.FINAL?r+=n.length:kn(e)===fe.ANY&&(r+=1),Sn(e,t,{d:u,ts:o,points:r}),d(),zn(e)===Cn(e)&&(Pn(e,{finished:o}),s())}else{const n=(e,t,n,o)=>{const i=yn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=_n(e,t),i=_n(e,n);return!(!o||o!==i)})(e,t,n))return!1;const l=Bn(e,t),a=gn.pointAdd(Bn(e,n),{x:o[0]*i.tileSize,y:o[1]*i.tileSize});if(gn.pointDistance(l,a){const o=yn[e].puzzle.tiles,i=_n(e,t),l=_n(e,n);let a;const c=[];i&&c.push(i),l&&c.push(l),i?a=i:l?a=l:(Pn(e,{maxGroup:$n(e)+1}),s(),a=$n(e));if(In(e,t,{group:a}),r(t),In(e,n,{group:a}),r(n),c.length>0)for(const s of o){const t=Be.decodePiece(s);c.includes(t.group)&&(In(e,t.idx,{group:a}),r(t.idx))}})(e,t,n),i=qn(e,t);const d=((e,t)=>{let n=0;for(const o of t){const t=Nn(e,o);t>n&&(n=t)}return n})(e,i);return jn(e,i,d),c(i),!0}return!1};let i=!1;for(const t of qn(e,g)){const o=Vn(e,t);if(n(e,t,o[0],[0,1])||n(e,t,o[1],[-1,0])||n(e,t,o[2],[0,-1])||n(e,t,o[3],[1,0])){i=!0;break}}if(i&&kn(e)===fe.ANY){const n=Hn(e,t)+1;Sn(e,t,{d:u,ts:o,points:n}),d()}else Sn(e,t,{d:u,ts:o}),d()}}else Sn(e,t,{d:u,ts:o}),d();l._last_mouse=a}else if(u===pt){const i=n[1],a=n[2];Sn(e,t,{x:i,y:a,ts:o}),d(),l._last_mouse={x:i,y:a}}else if(u===ht){const i=n[1],a=n[2];Sn(e,t,{x:i,y:a,ts:o}),d(),l._last_mouse={x:i,y:a}}else Sn(e,t,{ts:o}),d();return function(e,t,n){yn[e].evtInfos[t]=n}(e,t,l),a}};let Zn=-10,Jn=20,Xn=2,eo=15;class to{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=Zn+Math.random()*Jn,this.vy=-1*(Xn+Math.random()*eo),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;Xn=t/2,eo=t-Xn;const n=1/4*this.canvas.width/(t/2);Zn=-n,Jn=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new to(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new to(this.rng));const e=[];for(;this.explodedBombs.length>0;){const t=this.explodedBombs.shift();if(!t)break;t.update(),t.alive&&e.push(t)}this.explodedBombs=e;const t=[];for(;this.readyBombs.length>0;){const e=this.readyBombs.shift();if(!e)break;e.update(this.particles),e.hasExploded?this.explodedBombs.push(e):t.push(e)}this.readyBombs=t;const n=[];for(;this.particles.length>0;){const e=this.particles.shift();if(!e)break;e.update(),e.alive&&n.push(e)}this.particles=n}render(){this.ctx.beginPath(),this.ctx.fillStyle="rgba(0, 0, 0, 0.1)",this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height);for(let e=0;e{const t=e.color+" "+e.d;if(!h[t]){const n=e.d?a:s;if(e.color){const o=e.d?r:c;h[t]=await createImageBitmap(on.colorizedCanvas(n,o,e.color))}else h[t]=n}return h[t]},y=function(e,t){return t.width=window.innerWidth,t.height=window.innerHeight,e.appendChild(t),window.addEventListener("resize",(()=>{t.width=window.innerWidth,t.height=window.innerHeight,so=!0})),t}(i,on.createCanvas()),f={final:!1,requesting:!0,log:[],logPointer:0,speeds:[.5,1,2,5,10,20,50,100,250,500],speedIdx:1,paused:!1,lastRealTs:0,lastGameTs:0,gameStartTs:0,dataOffset:0,dataSize:1e4};Et.onConnectionStateChange((e=>{l.setConnectionState(e)}));const w=async e=>{f.requesting=!0;const t=await Et.requestReplayData(e,f.dataOffset,f.dataSize);return f.dataOffset+=f.dataSize,f.requesting=!1,t};let v=()=>0;const b=async()=>{if("play"===o){const o=await Et.connect(n,e,t),i=Be.decodeGame(o);Kn.setGame(i.id,i),v=()=>D()}else{if("replay"!==o)throw"[ 2020-12-22 MODE invalid, must be play|replay ]";{const t=await w(e);if(!t.game)throw"[ 2021-05-29 no game received ]";const n=Be.decodeGame(t.game);Kn.setGame(n.id,n),f.requesting=!1,f.log=t.log,f.lastRealTs=D(),f.gameStartTs=parseInt(f.log[0][4],10),f.lastGameTs=f.gameStartTs,v=()=>f.lastGameTs}}so=!0};await b();const x=Kn.getPieceDrawOffset(e),C=Kn.getPieceDrawSize(e),k=Kn.getPuzzleWidth(e),A=Kn.getPuzzleHeight(e),z=Kn.getTableWidth(e),S=Kn.getTableHeight(e),P={x:(z-k)/2,y:(S-A)/2},I={w:k,h:A},T={w:C,h:C},_=await mn.loadPuzzleBitmaps(Kn.getPuzzle(e)),B=new oo(y,Kn.getRng(e));B.init();const E=y.getContext("2d");y.classList.add("loaded");const O=tn();O.move(-(z-y.width)/2,-(S-y.height)/2);const N=function(e,t,n){let o=[],i=!0,l=!1,a=!1,s=!1,r=!1,c=!1,d=!1,u=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{i&&("Shift"===t.key?u=e:"ArrowUp"===t.key||"w"===t.key||"W"===t.key?s=e:"ArrowDown"===t.key||"s"===t.key||"S"===t.key?r=e:"ArrowLeft"===t.key||"a"===t.key||"A"===t.key?l=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?a=e:"q"===t.key?d=e:"e"===t.key&&(c=e))};e.addEventListener("mousedown",(e=>{0===e.button&&y([dt,...p(e)])})),e.addEventListener("mouseup",(e=>{0===e.button&&y([ut,...p(e)])})),e.addEventListener("mousemove",(e=>{y([gt,...p(e)])})),e.addEventListener("wheel",(e=>{if(n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?pt:ht;y([t,...p(e)])}})),t.addEventListener("keydown",(e=>m(!0,e))),t.addEventListener("keyup",(e=>m(!1,e))),t.addEventListener("keypress",(e=>{i&&(" "===e.key&&y([wt]),"F"!==e.key&&"f"!==e.key||(lo=!lo,so=!0),"G"!==e.key&&"g"!==e.key||(ao=!ao,so=!0))}));const y=e=>{o.push(e)};return{addEvent:y,consumeAll:()=>{if(0===o.length)return[];const e=o.slice();return o=[],e},createKeyEvents:()=>{const e=u?20:10,t=(l?e:0)-(a?e:0),o=(s?e:0)-(r?e:0);0===t&&0===o||y([ct,t,o]),c&&d||(c?n.canZoom("in")&&y([pt,...h()]):d&&n.canZoom("out")&&y([ht,...h()]))},setHotkeys:e=>{i=e}}}(y,window,O),U=Kn.getImageUrl(e),M=()=>{const t=Kn.getStartTs(e),n=Kn.getFinishTs(e),o=v();l.setFinished(!!n),l.setDuration((n||o)-t)};M(),l.setPiecesDone(Kn.getFinishedPiecesCount(e)),l.setPiecesTotal(Kn.getPieceCount(e));const G=v();l.setActivePlayers(Kn.getActivePlayers(e,G)),l.setIdlePlayers(Kn.getIdlePlayers(e,G));const $=!!Kn.getFinishTs(e);let R=$;const V=()=>R&&!$,j=()=>Kn.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",F=()=>Kn.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let L="",W="",q=!1;const H=e=>{q=e;const[t,n]=e?[L,"grab"]:[W,"default"];y.style.cursor=`url('${t}') ${u} ${p}, ${n}`},Q=e=>{L=on.colorizedCanvas(a,r,e).toDataURL(),W=on.colorizedCanvas(s,c,e).toDataURL(),H(q)};Q(F());const Y=()=>{l.setReplaySpeed&&l.setReplaySpeed(f.speeds[f.speedIdx]),l.setReplayPaused&&l.setReplayPaused(f.paused)};if("play"===o?setInterval(M,1e3):"replay"===o&&Y(),"play"===o)Et.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[i,l]of o)switch(i){case xt:{const n=Be.decodePlayer(l);n.id!==t&&(Kn.setPlayer(e,n.id,n),so=!0)}break;case bt:{const t=Be.decodePiece(l);Kn.setPiece(e,t.idx,t),so=!0}break;case vt:Kn.setPuzzleData(e,l),so=!0}R=!!Kn.getFinishTs(e)}));else if("replay"===o){const t=setInterval((()=>{const n=D();if(f.requesting)return void(f.lastRealTs=n);if(f.logPointer+1>=f.log.length)return f.lastRealTs=n,void(async e=>{const t=await w(e);f.log=f.log.slice(f.logPointer),f.logPointer=0,f.log.push(...t.log),t.log.length=f.log.length){f.final&&clearInterval(t);break}const o=f.log[n],l=f.gameStartTs+o[o.length-1];if(l>i)break;const a=o.slice();if(a[0]===at){const t=a[1];Kn.addPlayer(e,t,l),so=!0}else if(a[0]===st){const t=Kn.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";Kn.addPlayer(e,t,l),so=!0}else if(a[0]===rt){const t=Kn.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=a[2];Kn.handleInput(e,t,n,l),so=!0}f.logPointer=n}f.lastRealTs=n,f.lastGameTs=i,M()}),50)}let K=null;return(e=>{const t=e.fps||60,n=e.slow||1,o=e.update,i=e.render,l=window.requestAnimationFrame,a=1/t,s=n*a;let r,c=0,d=window.performance.now();const u=()=>{for(r=window.performance.now(),c+=Math.min(1,(r-d)/1e3);c>s;)c-=s,o(a);i(c/n),d=r,l(u)};l(u)})({update:()=>{N.createKeyEvents();for(const n of N.consumeAll())if("play"===o){const o=n[0];if(o===ct){const e=n[1],t=n[2];so=!0,O.move(e,t)}else if(o===gt){if(K&&!Kn.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=O.worldToViewport(e),o=Math.round(t.x-K.x),i=Math.round(t.y-K.y);so=!0,O.move(o,i),K=t}}else if(o===yt)Q(n[1]);else if(o===dt){const e={x:n[1],y:n[2]};K=O.worldToViewport(e),H(!0)}else if(o===ut)K=null,H(!1);else if(o===pt){const e={x:n[1],y:n[2]};so=!0,O.zoom("in",O.worldToViewport(e))}else if(o===ht){const e={x:n[1],y:n[2]};so=!0,O.zoom("out",O.worldToViewport(e))}else o===wt&&l.togglePreview();const i=v();Kn.handleInput(e,t,n,i).length>0&&(so=!0),Et.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===ct){const e=n[1],t=n[2];so=!0,O.move(e,t)}else if(e===gt){if(K){const e={x:n[1],y:n[2]},t=O.worldToViewport(e),o=Math.round(t.x-K.x),i=Math.round(t.y-K.y);so=!0,O.move(o,i),K=t}}else if(e===dt){const e={x:n[1],y:n[2]};K=O.worldToViewport(e)}else if(e===ut)K=null;else if(e===pt){const e={x:n[1],y:n[2]};so=!0,O.zoom("in",O.worldToViewport(e))}else if(e===ht){const e={x:n[1],y:n[2]};so=!0,O.zoom("out",O.worldToViewport(e))}else e===wt&&l.togglePreview()}R=!!Kn.getFinishTs(e),V()&&(B.update(),so=!0)},render:async()=>{if(!so)return;const n=v();let i,a,s;window.DEBUG&&rn(0),E.fillStyle=j(),E.fillRect(0,0,y.width,y.height),window.DEBUG&&cn("clear done"),i=O.worldToViewportRaw(P),a=O.worldDimToViewportRaw(I),E.fillStyle="rgba(255, 255, 255, .3)",E.fillRect(i.x,i.y,a.w,a.h),window.DEBUG&&cn("board done");const r=Kn.getPiecesSortedByZIndex(e);window.DEBUG&&cn("get tiles done"),a=O.worldDimToViewportRaw(T);for(const e of r)(-1===e.owner?lo:ao)&&(s=_[e.idx],i=O.worldToViewportRaw({x:x+e.pos.x,y:x+e.pos.y}),E.drawImage(s,0,0,s.width,s.height,i.x,i.y,a.w,a.h));window.DEBUG&&cn("tiles done");const c=[];for(const l of Kn.getActivePlayers(e,n))d=l,("replay"===o||d.id!==t)&&(s=await m(l),i=O.worldToViewport(l),E.drawImage(s,i.x-u,i.y-p),c.push([`${l.name} (${l.points})`,i.x,i.y+g]));var d;E.fillStyle="white",E.textAlign="center";for(const[e,t,o]of c)E.fillText(e,t,o);window.DEBUG&&cn("players done"),l.setActivePlayers(Kn.getActivePlayers(e,n)),l.setIdlePlayers(Kn.getIdlePlayers(e,n)),l.setPiecesDone(Kn.getFinishedPiecesCount(e)),window.DEBUG&&cn("HUD done"),V()&&B.render(),so=!1}}),{setHotkeys:e=>{N.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),N.addEvent([mt,e])},onColorChange:e=>{localStorage.setItem("player_color",e),N.addEvent([yt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),N.addEvent([ft,e])},replayOnSpeedUp:()=>{f.speedIdx+1{f.speedIdx>=1&&(f.speedIdx--,Y())},replayOnPauseToggle:()=>{f.paused=!f.paused,Y()},previewImageUrl:U,player:{background:j(),color:F(),name:Kn.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon"},disconnect:Et.disconnect,connect:b}}var co=e({name:"game",components:{PuzzleStatus:Qe,Scores:Fe,SettingsOverlay:Ke,PreviewOverlay:et,ConnectionOverlay:Ot,HelpOverlay:Rt},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},disconnect:()=>{},connect:()=>{}}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await 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)}}))},unmounted(){this.g.disconnect()},methods:{reconnect(){this.g.connect()},toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}}});const uo={id:"game"},go={class:"menu"},po={class:"tabs"},ho=s("🧩 Puzzles");co.render=function(e,i,s,r,c,d){const u=l("settings-overlay"),p=l("preview-overlay"),h=l("help-overlay"),m=l("connection-overlay"),y=l("puzzle-status"),f=l("router-link"),w=l("scores");return a(),t("div",uo,[g(n(u,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(p,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(h,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(m,{connectionState:e.connectionState,onReconnect:e.reconnect},null,8,["connectionState","onReconnect"]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},null,8,["finished","duration","piecesDone","piecesTotal"]),n("div",go,[n("div",po,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[ho])),_:1}),n("div",{class:"opener",onClick:i[5]||(i[5]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[6]||(i[6]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[7]||(i[7]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var mo=e({name:"replay",components:{PuzzleStatus:Qe,Scores:Fe,SettingsOverlay:Ke,PreviewOverlay:et,HelpOverlay:Rt},data:()=>({activePlayers:[],idlePlayers:[],finished:!1,duration:0,piecesDone:0,piecesTotal:0,overlay:"",connectionState:0,g:{player:{background:"",color:"",name:""},previewImageUrl:"",setHotkeys:e=>{},onBgChange:e=>{},onColorChange:e=>{},onNameChange:e=>{},replayOnSpeedUp:()=>{},replayOnSpeedDown:()=>{},replayOnPauseToggle:()=>{},disconnect:()=>{}},replay:{speed:1,paused:!1}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.g=await 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}}))},unmounted(){this.g.disconnect()},methods:{toggle(e,t){""===this.overlay?(this.overlay=e,t&&this.g.setHotkeys(!1)):(this.overlay="",t&&this.g.setHotkeys(!0))}},computed:{replayText(){return"Replay-Speed: "+this.replay.speed+"x"+(this.replay.paused?" Paused":"")}}});const yo={id:"replay"},fo={class:"menu"},wo={class:"tabs"},vo=s("🧩 Puzzles");mo.render=function(e,i,s,c,d,u){const p=l("settings-overlay"),h=l("preview-overlay"),m=l("help-overlay"),y=l("puzzle-status"),f=l("router-link"),w=l("scores");return a(),t("div",yo,[g(n(p,{onBgclick:i[1]||(i[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":i[2]||(i[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[v,"settings"===e.overlay]]),g(n(h,{onBgclick:i[3]||(i[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[v,"preview"===e.overlay]]),g(n(m,{onBgclick:i[4]||(i[4]=t=>e.toggle("help",!0))},null,512),[[v,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:o((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:i[5]||(i[5]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:i[6]||(i[6]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:i[7]||(i[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",fo,[n("div",wo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[vo])),_:1}),n("div",{class:"opener",onClick:i[8]||(i[8]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:i[9]||(i[9]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:i[10]||(i[10]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=b({history:x(),routes:[{name:"index",path:"/",component:R},{name:"new-game",path:"/new-game",component:Ee},{name:"game",path:"/g/:id",component:co},{name:"replay",path:"/replay/:id",component:mo}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const o=C(k);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=Be.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); diff --git a/build/public/assets/index.d7fe3ee6.js b/build/public/assets/index.d7fe3ee6.js new file mode 100644 index 0000000..6f2483d --- /dev/null +++ b/build/public/assets/index.d7fe3ee6.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as o,b as l,r as i,o as a,e as s,t as r,F as d,f as c,g as u,h as g,v as p,i as h,j as m,k as y,l as f,m as w,n as v,p as b,q as x,s as k,u as C}from"./vendor.18cd2d7e.js";var A=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const S={id:"app"},z={key:0,class:"nav"},P=s("Index"),I=s("New game");A.render=function(e,s,r,d,c,u){const g=i("router-link"),p=i("router-view");return a(),t("div",S,[e.showNav?(a(),t("ul",z,[n("li",null,[n(g,{class:"btn",to:{name:"index"}},{default:o((()=>[P])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:o((()=>[I])),_:1})])])):l("",!0),n(p)])};const T=864e5,_=e=>{const t=Math.floor(e/T);e%=T;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 E=1e3,D=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>_(t-e),O=_,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||D();return`${n} ${B(o,l)}`}}});const N={class:"game-info-text"},M=n("br",null,null,-1),G=n("br",null,null,-1),$=n("br",null,null,-1),R=s(" ↪️ Watch replay ");U.render=function(e,d,c,u,g,p){const h=i("router-link");return a(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",N,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),M,s(" 👥 "+r(e.game.players),1),G,s(" "+r(e.time(e.game.started,e.game.finished)),1),$])])),_:1},8,["to"]),e.game.hasReplay?(a(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[R])),_:1},8,["to"])):l("",!0)],4)};var V=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),F=n("h1",null,"Finished games",-1);V.render=function(e,o,l,s,r,u){const g=i("game-teaser");return a(),t("div",null,[j,(a(!0),t(d,null,c(e.gamesRunning,((e,o)=>(a(),t("div",{class:"game-teaser-wrap",key:o},[n(g,{game:e},null,8,["game"])])))),128)),F,(a(!0),t(d,null,c(e.gamesFinished,((e,o)=>(a(),t("div",{class:"game-teaser-wrap",key:o},[n(g,{game:e},null,8,["game"])])))),128))])};var L=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")}}});L.render=function(e,o,l,i,s,r){return a(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var W=e({name:"image-library",components:{ImageTeaser:L},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});W.render=function(e,n,o,l,s,r){const u=i("image-teaser");return a(),t("div",null,[(a(!0),t(d,null,c(e.images,((n,o)=>(a(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])};const q={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}}}};q.render=function(e,n,o,l,i,s){return a(),t("div",{style:s.style,title:o.title},null,12,["title"])};var H=e({name:"tags-input",props:{modelValue:{type:Array,required:!0}},emits:{"update:modelValue":null},data:()=>({input:"",values:[]}),created(){this.values=this.modelValue},methods:{onKeyUp(e){if(","===e.key)return this.add(),e.stopPropagation(),!1},add(){const e=this.input.replace(/,/g,"").trim();e&&(this.values.includes(e)||this.values.push(e),this.input="",this.$emit("update:modelValue",this.values))},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const Q=m()(((e,o,l,i,s,u)=>(a(),t("div",null,[g(n("input",{class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onKeydown:o[2]||(o[2]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[3]||(o[3]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[p,e.input]]),(a(!0),t(d,null,c(e.values,((n,o)=>(a(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));H.render=Q,H.__scopeId="data-v-771460ae";var Y=e({name:"new-image-dialog",components:{ResponsiveImage:q,TagsInput:H},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const o=new FileReader;o.readAsDataURL(n),o.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const K={key:0,class:"has-image"},Z={key:1},J={class:"upload"},X=n("span",{class:"btn"},"Upload File",-1),ee={class:"area-settings"},te=n("td",null,[n("label",null,"Title")],-1),ne=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),oe=n("td",null,[n("label",null,"Tags")],-1),le={class:"area-buttons"},ie=s("🧩 Post to gallery "),ae=n("br",null,null,-1),se=s(" + set up game");Y.render=function(e,o,l,s,r,d){const c=i("responsive-image"),h=i("tags-input");return a(),t("div",{class:"overlay new-image-dialog",onClick:o[8]||(o[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[7]||(o[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(a(),t("div",K,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(a(),t("div",Z,[n("label",J,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),X])]))],2),n("div",ee,[n("table",null,[n("tr",null,[te,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[3]||(o[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ne,n("tr",null,[oe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[4]||(o[4]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",le,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[5]||(o[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[6]||(o[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[ie,ae,se],8,["disabled"])])])])};var re=e({name:"edit-image-dialog",components:{ResponsiveImage:q,TagsInput:H},props:{image:{type:Object,required:!0}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const de={class:"area-image"},ce={class:"has-image"},ue={class:"area-settings"},ge=n("td",null,[n("label",null,"Title")],-1),pe=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"};var ye,fe,we,ve;re.render=function(e,o,l,s,r,d){const c=i("responsive-image"),h=i("tags-input");return a(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",de,[n("div",ce,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ue,[n("table",null,[n("tr",null,[ge,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),pe,n("tr",null,[he,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",me,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])},(fe=ye||(ye={}))[fe.Flat=0]="Flat",fe[fe.Out=1]="Out",fe[fe.In=-1]="In",(ve=we||(we={}))[ve.FINAL=0]="FINAL",ve[ve.ANY=1]="ANY";var be=e({name:"new-game-dialog",components:{ResponsiveImage:q},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:we.ANY}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const xe={class:"area-image"},ke={class:"has-image"},Ce={class:"area-settings"},Ae=n("td",null,[n("label",null,"Pieces")],-1),Se=n("td",null,[n("label",null,"Scoring: ")],-1),ze=s(" Any (Score when pieces are connected to each other or on final location)"),Pe=n("br",null,null,-1),Ie=s(" Final (Score when pieces are put to their final location)"),Te={class:"area-buttons"};be.render=function(e,o,l,s,r,d){const c=i("responsive-image");return a(),t("div",{class:"overlay new-game-dialog",onClick:o[6]||(o[6]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[5]||(o[5]=u((()=>{}),["stop"]))},[n("div",xe,[n("div",ke,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ce,[n("table",null,[n("tr",null,[Ae,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Se,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[y,e.scoreMode]]),ze]),Pe,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[y,e.scoreMode]]),Ie])])])])]),n("div",Te,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[4]||(o[4]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};class _e{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 _e(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const Ee=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},De=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=Ee(o.getHours(),"00"),i=Ee(o.getMinutes(),"00"),a=Ee(o.getSeconds(),"00");console[t](`${l}:${i}:${a}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var Be={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",_e.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||we.FINAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:_e.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}},Oe=e({components:{ImageLibrary:W,NewImageDialog:Y,EditImageDialog:re,NewGameDialog:be},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},methods:{toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${Be.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/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 Ue={class:"upload-image-teaser"},Ne=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),Me={key:0},Ge=s(" Tags: "),$e=s(" Sort by: "),Re=n("option",{value:"date_desc"},"Newest first",-1),Ve=n("option",{value:"date_asc"},"Oldest first",-1),je=n("option",{value:"alpha_asc"},"A-Z",-1),Fe=n("option",{value:"alpha_desc"},"Z-A",-1);Oe.render=function(e,o,s,u,p,h){const m=i("image-library"),y=i("new-image-dialog"),w=i("edit-image-dialog"),v=i("new-game-dialog");return a(),t("div",null,[n("div",Ue,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),Ne]),n("div",null,[e.tags.length>0?(a(),t("label",Me,[Ge,(a(!0),t(d,null,c(e.tags,((n,o)=>(a(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title),11,["onClick"])))),128))])):l("",!0),n("label",null,[$e,g(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[Re,Ve,je,Fe],544),[[f,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(a(),t(y,{key:0,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(a(),t(w,{key:1,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(a(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var Le=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 We={class:"scores"},qe=n("div",null,"Scores",-1),He=n("td",null,"⚡",-1),Qe=n("td",null,"💤",-1);Le.render=function(e,o,l,i,s,u){return a(),t("div",We,[qe,n("table",null,[(a(!0),t(d,null,c(e.actives,((e,o)=>(a(),t("tr",{key:o,style:{color:e.color}},[He,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(a(!0),t(d,null,c(e.idles,((e,o)=>(a(),t("tr",{key:o,style:{color:e.color}},[Qe,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var Ye=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 O(this.duration)}}});const Ke={class:"timer"};Ye.render=function(e,o,l,i,s,d){return a(),t("div",Ke,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),w(e.$slots,"default")])};var Ze=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 Je=n("td",null,[n("label",null,"Background: ")],-1),Xe=n("td",null,[n("label",null,"Color: ")],-1),et=n("td",null,[n("label",null,"Name: ")],-1),tt=n("td",null,[n("label",null,"Sounds: ")],-1);Ze.render=function(e,o,l,i,s,r){return a(),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,[Je,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[Xe,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[et,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])]),n("tr",null,[tt,n("td",null,[g(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[v,e.modelValue.soundsEnabled]])])])])])};var nt=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const ot={class:"preview"};nt.render=function(e,o,l,i,s,r){return a(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",ot,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var lt=1,it=4,at=2,st=3,rt=2,dt=4,ct=3,ut=9,gt=1,pt=2,ht=3,mt=4,yt=5,ft=6,wt=7,vt=8,bt=10,xt=11,kt=1,Ct=2,At=3;const St=De("Communication.js");let zt,Pt=[],It=e=>{Pt.push(e)},Tt=[],_t=e=>{Tt.push(e)};let Et=0;const Dt=e=>{Et!==e&&(Et=e,_t(e))};function Bt(e){if(2===Et)try{zt.send(JSON.stringify(e))}catch(t){St.info("unable to send message.. maybe because ws is invalid?")}}let Ot,Ut;var Nt={connect:function(e,t,n){return Ot=0,Ut={},Dt(3),new Promise((o=>{zt=new WebSocket(e,n+"|"+t),zt.onopen=()=>{Dt(2),Bt([st])},zt.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===it){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&&Ut[o])return void delete Ut[o];It(t)}}},zt.onerror=()=>{throw Dt(1),"[ 2021-05-15 onerror ]"},zt.onclose=e=>{4e3===e.code||1001===e.code?Dt(4):Dt(1)}}))},requestReplayData:async function(e,t,n){const o={gameId:e,offset:t,size:n},l=await fetch(`/api/replay-data${Be.asQueryArgs(o)}`);return await l.json()},disconnect:function(){zt&&zt.close(4e3),Ot=0,Ut={}},sendClientEvent:function(e){Ot++,Ut[Ot]=e,Bt([at,Ot,Ut[Ot]])},onServerChange:function(e){It=e;for(const t of Pt)It(t);Pt=[]},onConnectionStateChange:function(e){_t=e;for(const t of Tt)_t(t);Tt=[]},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},Mt=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Nt.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Nt.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const Gt={key:0,class:"overlay connection-lost"},$t={key:0,class:"overlay-content"},Rt=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Vt={key:1,class:"overlay-content"},jt=n("div",null,"Connecting...",-1);Mt.render=function(e,o,i,s,r,d){return e.show?(a(),t("div",Gt,[e.lostConnection?(a(),t("div",$t,[Rt,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(a(),t("div",Vt,[jt])):l("",!0)])):l("",!0)};var Ft=e({name:"help-overlay",emits:{bgclick:null}});const Lt=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),Wt=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),qt=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),Ht=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),Qt=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Yt=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),Kt=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),Zt=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Jt=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Xt=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),en=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1);Ft.render=function(e,o,l,i,s,r){return a(),t("div",{class:"overlay transparent",onClick:o[2]||(o[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:o[1]||(o[1]=u((()=>{}),["stop"]))},[Lt,Wt,qt,Ht,Qt,Yt,Kt,Zt,Jt,Xt,en])])};var tn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.550555f3.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="}),ln=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),an=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function sn(){let e=0,t=0,n=1;const o=(o,l)=>{e+=o/n,t+=l/n},l=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},i=o=>({x:o.x/n-e,y:o.y/n-t}),a=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n});return{move:o,canZoom:e=>n!=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}=a(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:a,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:i}}function rn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var dn={createCanvas:rn,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=rn(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=rn(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 cn=De("Debug.js");let un=0,gn=0;var pn=e=>{un=performance.now(),gn=e},hn=e=>{const t=performance.now(),n=t-un;n>gn&&cn.log(e+": "+n),un=t};function mn(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 fn={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:mn,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 mn(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 wn=De("PuzzleGraphics.js");function vn(e,t){const n=Be.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var bn={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){wn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,i=n.tileDrawSize,a=o/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,i={x:l,y:l},r=fn.pointAdd(i,{x:o,y:0}),c=fn.pointAdd(r,{x:0,y:o}),u=fn.pointSub(c,{x:o,y:0});if(n.moveTo(i.x,i.y),0!==e.top)for(let o=0;oBe.decodePiece(xn[e].puzzle.tiles[t]),Un=(e,t)=>On(e,t).group,Nn=(e,t)=>{const n=xn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=xn[e].puzzle.info,o=Be.coordByPieceIdx(n,t),l=o.x*n.tileSize,i=o.y*n.tileSize;return{x:l,y:i}}(e,t);return fn.pointAdd(o,l)},Mn=(e,t)=>On(e,t).pos,Gn=e=>{const t=Xn(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}},$n=(e,t)=>{const n=Fn(e),o=On(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Rn=(e,t)=>On(e,t).z,Vn=(e,t)=>{for(const n of xn[e].puzzle.tiles){const e=Be.decodePiece(n);if(e.owner===t)return e.idx}return-1},jn=e=>xn[e].puzzle.info.tileDrawSize,Fn=e=>xn[e].puzzle.info.tileSize,Ln=e=>xn[e].puzzle.data.maxGroup,Wn=e=>xn[e].puzzle.data.maxZ;function qn(e,t){const n=xn[e].puzzle.info,o=Be.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Hn=(e,t,n)=>{for(const o of t)Bn(e,o,{z:n})},Qn=(e,t,n)=>{const o=Mn(e,t);Bn(e,t,{pos:fn.pointAdd(o,n)})},Yn=(e,t,n)=>{const o=jn(e),l=Gn(e),i=n;for(const a of t){const t=On(e,a);t.pos.x+n.xl.x+l.w&&(i.x=Math.min(l.x+l.w-t.pos.x+o,i.x)),t.pos.y+n.yl.y+l.h&&(i.y=Math.min(l.y+l.h-t.pos.y+o,i.y))}for(const a of t)Qn(e,a,i)},Kn=(e,t,n)=>{for(const o of t)Bn(e,o,{owner:n})};function Zn(e,t){const n=xn[e].puzzle.tiles,o=Be.decodePiece(n[t]),l=[];if(o.group)for(const i of n){const e=Be.decodePiece(i);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const Jn=(e,t)=>{const n=Cn(e,t);return n?n.points:0},Xn=e=>xn[e].puzzle.info.table.width,eo=e=>xn[e].puzzle.info.table.height;var to={setGame:function(e,t){xn[e]=t},exists:function(e){return!!xn[e]||!1},playerExists:Sn,getActivePlayers:function(e,t){const n=t-30*E;return zn(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*E;return zn(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Sn(e,t)?En(e,t,{ts:n}):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,n))},getFinishedPiecesCount:_n,getPieceCount:Pn,getImageUrl:function(e){return xn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){xn[e].puzzle.info.imageUrl=t},get:function(e){return xn[e]||null},getAllGames:function(){return Object.values(xn).sort(((e,t)=>Tn(e.id)===Tn(t.id)?t.puzzle.data.started-e.puzzle.data.started:Tn(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Cn(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Cn(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Cn(e,t);return n?n.name:null},getPlayerIndexById:kn,getPlayerIdByIndex:function(e,t){return xn[e].players.length>t?Be.decodePlayer(xn[e].players[t]).id:null},changePlayer:En,setPlayer:An,setPiece:function(e,t,n){xn[e].puzzle.tiles[t]=Be.encodePiece(n)},setPuzzleData:function(e,t){xn[e].puzzle.data=t},getTableWidth:Xn,getTableHeight:eo,getPuzzle:e=>xn[e].puzzle,getRng:e=>xn[e].rng.obj,getPuzzleWidth:e=>xn[e].puzzle.info.width,getPuzzleHeight:e=>xn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return xn[e].puzzle.tiles.map(Be.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Vn(e,t);return n<0?null:xn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>xn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:jn,getFinalPiecePos:Nn,getStartTs:e=>xn[e].puzzle.data.started,getFinishTs:e=>xn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const i=xn[e].puzzle,a=function(e,t){return t in xn[e].evtInfos?xn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],r=()=>{s.push([kt,i.data])},d=t=>{s.push([Ct,Be.encodePiece(On(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=Cn(e,t);n&&s.push([At,Be.encodePlayer(n)])},g=n[0];if(g===ft){const l=n[1];En(e,t,{bgcolor:l,ts:o}),u()}else if(g===wt){const l=n[1];En(e,t,{color:l,ts:o}),u()}else if(g===vt){const l=`${n[1]}`.substr(0,16);En(e,t,{name:l,ts:o}),u()}else if(g===gt){const l={x:n[1],y:n[2]};En(e,t,{d:1,ts:o}),u(),a._last_mouse_down=l;const i=((e,t)=>{const n=xn[e].puzzle.info,o=xn[e].puzzle.tiles;let l=-1,i=-1;for(let a=0;al)&&(l=e.z,i=a)}return i})(e,l);if(i>=0){const n=Wn(e)+1;Dn(e,{maxZ:n}),r();const o=Zn(e,i);Hn(e,o,Wn(e)),Kn(e,o,t),c(o)}a._last_mouse=l}else if(g===ht){const l=n[1],i=n[2],s={x:l,y:i};if(null===a._last_mouse_down)En(e,t,{x:l,y:i,ts:o}),u();else{const n=Vn(e,t);if(n>=0){En(e,t,{x:l,y:i,ts:o}),u();const r=Zn(e,n);let d=fn.pointInBounds(s,Gn(e))&&fn.pointInBounds(a._last_mouse_down,Gn(e));for(const t of r){const n=$n(e,t);if(fn.pointInBounds(s,n)){d=!0;break}}if(d){const t=l-a._last_mouse_down.x,n=i-a._last_mouse_down.y;Yn(e,r,{x:t,y:n}),c(r)}}else En(e,t,{ts:o}),u();a._last_mouse_down=s}a._last_mouse=s}else if(g===pt){const s={x:n[1],y:n[2]},g=0;a._last_mouse_down=null;const p=Vn(e,t);if(p>=0){const n=Zn(e,p);Kn(e,n,0),c(n);const a=Mn(e,p),s=Nn(e,p);if(fn.pointDistance(s,a){for(const n of t)Bn(e,n,{owner:-1,z:1})})(e,n),c(n);let d=Jn(e,t);In(e)===we.FINAL?d+=n.length:In(e)===we.ANY&&(d+=1),En(e,t,{d:g,ts:o,points:d}),u(),_n(e)===Pn(e)&&(Dn(e,{finished:o}),r()),l&&l(t)}else{const n=(e,t,n,o)=>{const l=xn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=Un(e,t),l=Un(e,n);return!(!o||o!==l)})(e,t,n))return!1;const i=Mn(e,t),a=fn.pointAdd(Mn(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(fn.pointDistance(i,a){const o=xn[e].puzzle.tiles,l=Un(e,t),i=Un(e,n);let a;const s=[];l&&s.push(l),i&&s.push(i),l?a=l:i?a=i:(Dn(e,{maxGroup:Ln(e)+1}),r(),a=Ln(e));if(Bn(e,t,{group:a}),d(t),Bn(e,n,{group:a}),d(n),s.length>0)for(const r of o){const t=Be.decodePiece(r);s.includes(t.group)&&(Bn(e,t.idx,{group:a}),d(t.idx))}})(e,t,n),l=Zn(e,t);const s=((e,t)=>{let n=0;for(const o of t){const t=Rn(e,o);t>n&&(n=t)}return n})(e,l);return Hn(e,l,s),c(l),!0}return!1};let i=!1;for(const t of Zn(e,p)){const o=qn(e,t);if(n(e,t,o[0],[0,1])||n(e,t,o[1],[-1,0])||n(e,t,o[2],[0,-1])||n(e,t,o[3],[1,0])){i=!0;break}}if(i&&In(e)===we.ANY){const n=Jn(e,t)+1;En(e,t,{d:g,ts:o,points:n}),u()}else En(e,t,{d:g,ts:o}),u();i&&l&&l(t)}}else En(e,t,{d:g,ts:o}),u();a._last_mouse=s}else if(g===mt){const l=n[1],i=n[2];En(e,t,{x:l,y:i,ts:o}),u(),a._last_mouse={x:l,y:i}}else if(g===yt){const l=n[1],i=n[2];En(e,t,{x:l,y:i,ts:o}),u(),a._last_mouse={x:l,y:i}}else En(e,t,{ts:o}),u();return function(e,t,n){xn[e].evtInfos[t]=n}(e,t,a),s}};let no=-10,oo=20,lo=2,io=15;class ao{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*(lo+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;lo=t/2,io=t-lo;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 ao(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new ao(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]},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,ho=!0})),t}(l,dn.createCanvas()),v={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};Nt.onConnectionStateChange((e=>{i.setConnectionState(e)}));const b=async e=>{v.requesting=!0;const t=await Nt.requestReplayData(e,v.dataOffset,v.dataSize);return v.dataOffset+=v.dataSize,v.requesting=!1,t};let x=()=>0;const k=async()=>{if("play"===o){const o=await Nt.connect(n,e,t),l=Be.decodeGame(o);to.setGame(l.id,l),x=()=>D()}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=Be.decodeGame(t.game);to.setGame(n.id,n),v.requesting=!1,v.log=t.log,v.lastRealTs=D(),v.gameStartTs=parseInt(v.log[0][4],10),v.lastGameTs=v.gameStartTs,x=()=>v.lastGameTs}}ho=!0};await k();const C=to.getPieceDrawOffset(e),A=to.getPieceDrawSize(e),S=to.getPuzzleWidth(e),z=to.getPuzzleHeight(e),P=to.getTableWidth(e),I=to.getTableHeight(e),T={x:(P-S)/2,y:(I-z)/2},_={w:S,h:z},E={w:A,h:A},B=await bn.loadPuzzleBitmaps(to.getPuzzle(e)),O=new ro(w,to.getRng(e));O.init();const U=w.getContext("2d");w.classList.add("loaded");const N=sn();N.move(-(P-w.width)/2,-(I-w.height)/2);const M=function(e,t,n){let o=[],l=!0,i=!1,a=!1,s=!1,r=!1,d=!1,c=!1,u=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{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?i=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?a=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};e.addEventListener("mousedown",(e=>{0===e.button&&y([gt,...p(e)])})),e.addEventListener("mouseup",(e=>{0===e.button&&y([pt,...p(e)])})),e.addEventListener("mousemove",(e=>{y([ht,...p(e)])})),e.addEventListener("wheel",(e=>{if(n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?mt:yt;y([t,...p(e)])}})),t.addEventListener("keydown",(e=>m(!0,e))),t.addEventListener("keyup",(e=>m(!1,e))),t.addEventListener("keypress",(e=>{l&&(" "===e.key&&y([bt]),"F"!==e.key&&"f"!==e.key||(go=!go,ho=!0),"G"!==e.key&&"g"!==e.key||(po=!po,ho=!0),"M"!==e.key&&"m"!==e.key||y([xt]))}));const y=e=>{o.push(e)};return{addEvent:y,consumeAll:()=>{if(0===o.length)return[];const e=o.slice();return o=[],e},createKeyEvents:()=>{const e=u?20:10,t=(i?e:0)-(a?e:0),o=(s?e:0)-(r?e:0);0===t&&0===o||y([ut,t,o]),d&&c||(d?n.canZoom("in")&&y([mt,...h()]):c&&n.canZoom("out")&&y([yt,...h()]))},setHotkeys:e=>{l=e}}}(w,window,N),G=to.getImageUrl(e),$=()=>{const t=to.getStartTs(e),n=to.getFinishTs(e),o=x();i.setFinished(!!n),i.setDuration((n||o)-t)};$(),i.setPiecesDone(to.getFinishedPiecesCount(e)),i.setPiecesTotal(to.getPieceCount(e));const R=x();i.setActivePlayers(to.getActivePlayers(e,R)),i.setIdlePlayers(to.getIdlePlayers(e,R));const V=!!to.getFinishTs(e);let j=V;const F=()=>j&&!V,L=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},W=()=>to.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",q=()=>to.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let H="",Q="",Y=!1;const K=e=>{Y=e;const[t,n]=e?[H,"grab"]:[Q,"default"];w.style.cursor=`url('${t}') ${p} ${m}, ${n}`},Z=e=>{H=dn.colorizedCanvas(r,c,e).toDataURL(),Q=dn.colorizedCanvas(d,u,e).toDataURL(),K(Y)};Z(q());const J=()=>{i.setReplaySpeed&&i.setReplaySpeed(v.speeds[v.speedIdx]),i.setReplayPaused&&i.setReplayPaused(v.paused)};if("play"===o?setInterval($,1e3):"replay"===o&&J(),"play"===o)Nt.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,i]of o)switch(l){case At:{const n=Be.decodePlayer(i);n.id!==t&&(to.setPlayer(e,n.id,n),ho=!0)}break;case Ct:{const t=Be.decodePiece(i);to.setPiece(e,t.idx,t),ho=!0}break;case kt:to.setPuzzleData(e,i),ho=!0}j=!!to.getFinishTs(e)}));else if("replay"===o){const t=setInterval((()=>{const n=D();if(v.requesting)return void(v.lastRealTs=n);if(v.logPointer+1>=v.log.length)return v.lastRealTs=n,void(async e=>{const t=await b(e);v.log=v.log.slice(v.logPointer),v.logPointer=0,v.log.push(...t.log),t.log.length=v.log.length){v.final&&clearInterval(t);break}const o=v.log[n],i=v.gameStartTs+o[o.length-1];if(i>l)break;const a=o.slice();if(a[0]===rt){const t=a[1];to.addPlayer(e,t,i),ho=!0}else if(a[0]===dt){const t=to.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";to.addPlayer(e,t,i),ho=!0}else if(a[0]===ct){const t=to.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=a[2];to.handleInput(e,t,n,i),ho=!0}v.logPointer=n}v.lastRealTs=n,v.lastGameTs=l,$()}),50)}let X=null;return(e=>{const t=e.fps||60,n=e.slow||1,o=e.update,l=e.render,i=window.requestAnimationFrame,a=1/t,s=n*a;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>s;)d-=s,o(a);l(d/n),c=r,i(u)};i(u)})({update:()=>{M.createKeyEvents();for(const n of M.consumeAll())if("play"===o){const o=n[0];if(o===ut){const e=n[1],t=n[2];ho=!0,N.move(e,t)}else if(o===ht){if(X&&!to.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=N.worldToViewport(e),o=Math.round(t.x-X.x),l=Math.round(t.y-X.y);ho=!0,N.move(o,l),X=t}}else if(o===wt)Z(n[1]);else if(o===gt){const e={x:n[1],y:n[2]};X=N.worldToViewport(e),K(!0)}else if(o===pt)X=null,K(!1);else if(o===mt){const e={x:n[1],y:n[2]};ho=!0,N.zoom("in",N.worldToViewport(e))}else if(o===yt){const e={x:n[1],y:n[2]};ho=!0,N.zoom("out",N.worldToViewport(e))}else o===bt?i.togglePreview():o===xt&&i.toggleSoundsEnabled();const l=x();to.handleInput(e,t,n,l,(e=>{L()&&s.play()})).length>0&&(ho=!0),Nt.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===ut){const e=n[1],t=n[2];ho=!0,N.move(e,t)}else if(e===ht){if(X){const e={x:n[1],y:n[2]},t=N.worldToViewport(e),o=Math.round(t.x-X.x),l=Math.round(t.y-X.y);ho=!0,N.move(o,l),X=t}}else if(e===gt){const e={x:n[1],y:n[2]};X=N.worldToViewport(e)}else if(e===pt)X=null;else if(e===mt){const e={x:n[1],y:n[2]};ho=!0,N.zoom("in",N.worldToViewport(e))}else if(e===yt){const e={x:n[1],y:n[2]};ho=!0,N.zoom("out",N.worldToViewport(e))}else e===bt&&i.togglePreview()}j=!!to.getFinishTs(e),F()&&(O.update(),ho=!0)},render:async()=>{if(!ho)return;const n=x();let l,a,s;window.DEBUG&&pn(0),U.fillStyle=W(),U.fillRect(0,0,w.width,w.height),window.DEBUG&&hn("clear done"),l=N.worldToViewportRaw(T),a=N.worldDimToViewportRaw(_),U.fillStyle="rgba(255, 255, 255, .3)",U.fillRect(l.x,l.y,a.w,a.h),window.DEBUG&&hn("board done");const r=to.getPiecesSortedByZIndex(e);window.DEBUG&&hn("get tiles done"),a=N.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?go:po)&&(s=B[e.idx],l=N.worldToViewportRaw({x:C+e.pos.x,y:C+e.pos.y}),U.drawImage(s,0,0,s.width,s.height,l.x,l.y,a.w,a.h));window.DEBUG&&hn("tiles done");const d=[];for(const i of to.getActivePlayers(e,n))c=i,("replay"===o||c.id!==t)&&(s=await f(i),l=N.worldToViewport(i),U.drawImage(s,l.x-p,l.y-m),d.push([`${i.name} (${i.points})`,l.x,l.y+h]));var c;U.fillStyle="white",U.textAlign="center";for(const[e,t,o]of d)U.fillText(e,t,o);window.DEBUG&&hn("players done"),i.setActivePlayers(to.getActivePlayers(e,n)),i.setIdlePlayers(to.getIdlePlayers(e,n)),i.setPiecesDone(to.getFinishedPiecesCount(e)),window.DEBUG&&hn("HUD done"),F()&&O.render(),ho=!1}}),{setHotkeys:e=>{M.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),M.addEvent([ft,e])},onColorChange:e=>{localStorage.setItem("player_color",e),M.addEvent([wt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),M.addEvent([vt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},replayOnSpeedUp:()=>{v.speedIdx+1{v.speedIdx>=1&&(v.speedIdx--,J())},replayOnPauseToggle:()=>{v.paused=!v.paused,J()},previewImageUrl:G,player:{background:W(),color:q(),name:to.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:L()},disconnect:Nt.disconnect,connect:k}}var yo=e({name:"game",components:{PuzzleStatus:Ye,Scores:Le,SettingsOverlay:Ze,PreviewOverlay:nt,ConnectionOverlay:Mt,HelpOverlay:Ft},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=>{},disconnect:()=>{},connect:()=>{}}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await mo(`${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.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 fo={id:"game"},wo={class:"menu"},vo={class:"tabs"},bo=s("🧩 Puzzles");yo.render=function(e,l,s,r,d,c){const u=i("settings-overlay"),p=i("preview-overlay"),h=i("help-overlay"),m=i("connection-overlay"),y=i("puzzle-status"),f=i("router-link"),w=i("scores");return a(),t("div",fo,[g(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"]),[[b,"settings"===e.overlay]]),g(n(p,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[b,"preview"===e.overlay]]),g(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[b,"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",wo,[n("div",vo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[bo])),_: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(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var xo=e({name:"replay",components:{PuzzleStatus:Ye,Scores:Le,SettingsOverlay:Ze,PreviewOverlay:nt,HelpOverlay:Ft},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:()=>{},disconnect:()=>{}},replay:{speed:1,paused:!1}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await mo(`${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.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 ko={id:"replay"},Co={class:"menu"},Ao={class:"tabs"},So=s("🧩 Puzzles");xo.render=function(e,l,s,d,c,u){const p=i("settings-overlay"),h=i("preview-overlay"),m=i("help-overlay"),y=i("puzzle-status"),f=i("router-link"),w=i("scores");return a(),t("div",ko,[g(n(p,{onBgclick:l[1]||(l[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":l[2]||(l[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[b,"settings"===e.overlay]]),g(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[b,"preview"===e.overlay]]),g(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[b,"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",Co,[n("div",Ao,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[So])),_: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(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=x({history:k(),routes:[{name:"index",path:"/",component:V},{name:"new-game",path:"/new-game",component:Oe},{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=C(A);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=Be.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); diff --git a/build/public/assets/vendor.b622ee49.js b/build/public/assets/vendor.18cd2d7e.js similarity index 74% rename from build/public/assets/vendor.b622ee49.js rename to build/public/assets/vendor.18cd2d7e.js index 3b5cc43..aafe16c 100644 --- a/build/public/assets/vendor.b622ee49.js +++ b/build/public/assets/vendor.18cd2d7e.js @@ -1,6 +1,6 @@ -function e(e,t){const n=Object.create(null),r=e.split(",");for(let o=0;o!!n[e.toLowerCase()]:e=>!!n[e]}const t=e("Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt"),n=e("itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly");function r(e){if(E(e)){const t={};for(let n=0;n{if(e){const n=e.split(s);n.length>1&&(t[n[0].trim()]=n[1].trim())}})),t}function i(e){let t="";if(R(e))t=e;else if(E(e))for(let n=0;nc(e,t)))}const u=e=>null==e?"":P(e)?JSON.stringify(e,f,2):String(e),f=(e,t)=>k(t)?{[`Map(${t.size})`]:[...t.entries()].reduce(((e,[t,n])=>(e[`${t} =>`]=n,e)),{})}:S(t)?{[`Set(${t.size})`]:[...t.values()]}:!P(t)||E(t)||M(t)?t:String(t),p={},d=[],h=()=>{},m=()=>!1,g=/^on[^a-z]/,v=e=>g.test(e),y=e=>e.startsWith("onUpdate:"),b=Object.assign,_=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},w=Object.prototype.hasOwnProperty,x=(e,t)=>w.call(e,t),E=Array.isArray,k=e=>"[object Map]"===$(e),S=e=>"[object Set]"===$(e),O=e=>e instanceof Date,C=e=>"function"==typeof e,R=e=>"string"==typeof e,A=e=>"symbol"==typeof e,P=e=>null!==e&&"object"==typeof e,F=e=>P(e)&&C(e.then)&&C(e.catch),j=Object.prototype.toString,$=e=>j.call(e),M=e=>"[object Object]"===$(e),I=e=>R(e)&&"NaN"!==e&&"-"!==e[0]&&""+parseInt(e,10)===e,T=e(",key,ref,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),U=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},V=/-(\w)/g,N=U((e=>e.replace(V,((e,t)=>t?t.toUpperCase():"")))),L=/\B([A-Z])/g,B=U((e=>e.replace(L,"-$1").toLowerCase())),D=U((e=>e.charAt(0).toUpperCase()+e.slice(1))),q=U((e=>e?`on${D(e)}`:"")),z=(e,t)=>e!==t&&(e==e||t==t),W=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},G=e=>{const t=parseFloat(e);return isNaN(t)?e:t},H=new WeakMap,X=[];let J;const Q=Symbol(""),Y=Symbol("");function Z(e,t=p){(function(e){return e&&!0===e._isEffect})(e)&&(e=e.raw);const n=function(e,t){const n=function(){if(!n.active)return t.scheduler?void 0:e();if(!X.includes(n)){ne(n);try{return oe.push(re),re=!0,X.push(n),J=n,e()}finally{X.pop(),le(),J=X[X.length-1]}}};return n.id=te++,n.allowRecurse=!!t.allowRecurse,n._isEffect=!0,n.active=!0,n.raw=e,n.deps=[],n.options=t,n}(e,t);return t.lazy||n(),n}function ee(e){e.active&&(ne(e),e.options.onStop&&e.options.onStop(),e.active=!1)}let te=0;function ne(e){const{deps:t}=e;if(t.length){for(let n=0;n{e&&e.forEach((e=>{(e!==J||e.allowRecurse)&&i.add(e)}))};if("clear"===t)l.forEach(c);else if("length"===n&&E(e))l.forEach(((e,t)=>{("length"===t||t>=r)&&c(e)}));else switch(void 0!==n&&c(l.get(n)),t){case"add":E(e)?I(n)&&c(l.get("length")):(c(l.get(Q)),k(e)&&c(l.get(Y)));break;case"delete":E(e)||(c(l.get(Q)),k(e)&&c(l.get(Y)));break;case"set":k(e)&&c(l.get(Q))}i.forEach((e=>{e.options.scheduler?e.options.scheduler(e):e()}))}const ae=e("__proto__,__v_isRef,__isVue"),ue=new Set(Object.getOwnPropertyNames(Symbol).map((e=>Symbol[e])).filter(A)),fe=ge(),pe=ge(!1,!0),de=ge(!0),he=ge(!0,!0),me={};function ge(e=!1,t=!1){return function(n,r,o){if("__v_isReactive"===r)return!e;if("__v_isReadonly"===r)return e;if("__v_raw"===r&&o===(e?t?Ke:We:t?ze:qe).get(n))return n;const s=E(n);if(!e&&s&&x(me,r))return Reflect.get(me,r,o);const l=Reflect.get(n,r,o);if(A(r)?ue.has(r):ae(r))return l;if(e||ie(n,0,r),t)return l;if(nt(l)){return!s||!I(r)?l.value:l}return P(l)?e?Xe(l):He(l):l}}["includes","indexOf","lastIndexOf"].forEach((e=>{const t=Array.prototype[e];me[e]=function(...e){const n=et(this);for(let t=0,o=this.length;t{const t=Array.prototype[e];me[e]=function(...e){se();const n=t.apply(this,e);return le(),n}}));function ve(e=!1){return function(t,n,r,o){let s=t[n];if(!e&&(r=et(r),s=et(s),!E(t)&&nt(s)&&!nt(r)))return s.value=r,!0;const l=E(t)&&I(n)?Number(n)!0,deleteProperty:(e,t)=>!0},_e=b({},ye,{get:pe,set:ve(!0)});b({},be,{get:he});const we=e=>P(e)?He(e):e,xe=e=>P(e)?Xe(e):e,Ee=e=>e,ke=e=>Reflect.getPrototypeOf(e);function Se(e,t,n=!1,r=!1){const o=et(e=e.__v_raw),s=et(t);t!==s&&!n&&ie(o,0,t),!n&&ie(o,0,s);const{has:l}=ke(o),i=r?Ee:n?xe:we;return l.call(o,t)?i(e.get(t)):l.call(o,s)?i(e.get(s)):void 0}function Oe(e,t=!1){const n=this.__v_raw,r=et(n),o=et(e);return e!==o&&!t&&ie(r,0,e),!t&&ie(r,0,o),e===o?n.has(e):n.has(e)||n.has(o)}function Ce(e,t=!1){return e=e.__v_raw,!t&&ie(et(e),0,Q),Reflect.get(e,"size",e)}function Re(e){e=et(e);const t=et(this);return ke(t).has.call(t,e)||(t.add(e),ce(t,"add",e,e)),this}function Ae(e,t){t=et(t);const n=et(this),{has:r,get:o}=ke(n);let s=r.call(n,e);s||(e=et(e),s=r.call(n,e));const l=o.call(n,e);return n.set(e,t),s?z(t,l)&&ce(n,"set",e,t):ce(n,"add",e,t),this}function Pe(e){const t=et(this),{has:n,get:r}=ke(t);let o=n.call(t,e);o||(e=et(e),o=n.call(t,e)),r&&r.call(t,e);const s=t.delete(e);return o&&ce(t,"delete",e,void 0),s}function Fe(){const e=et(this),t=0!==e.size,n=e.clear();return t&&ce(e,"clear",void 0,void 0),n}function je(e,t){return function(n,r){const o=this,s=o.__v_raw,l=et(s),i=t?Ee:e?xe:we;return!e&&ie(l,0,Q),s.forEach(((e,t)=>n.call(r,i(e),i(t),o)))}}function $e(e,t,n){return function(...r){const o=this.__v_raw,s=et(o),l=k(s),i="entries"===e||e===Symbol.iterator&&l,c="keys"===e&&l,a=o[e](...r),u=n?Ee:t?xe:we;return!t&&ie(s,0,c?Y:Q),{next(){const{value:e,done:t}=a.next();return t?{value:e,done:t}:{value:i?[u(e[0]),u(e[1])]:u(e),done:t}},[Symbol.iterator](){return this}}}}function Me(e){return function(...t){return"delete"!==e&&this}}const Ie={get(e){return Se(this,e)},get size(){return Ce(this)},has:Oe,add:Re,set:Ae,delete:Pe,clear:Fe,forEach:je(!1,!1)},Te={get(e){return Se(this,e,!1,!0)},get size(){return Ce(this)},has:Oe,add:Re,set:Ae,delete:Pe,clear:Fe,forEach:je(!1,!0)},Ue={get(e){return Se(this,e,!0)},get size(){return Ce(this,!0)},has(e){return Oe.call(this,e,!0)},add:Me("add"),set:Me("set"),delete:Me("delete"),clear:Me("clear"),forEach:je(!0,!1)},Ve={get(e){return Se(this,e,!0,!0)},get size(){return Ce(this,!0)},has(e){return Oe.call(this,e,!0)},add:Me("add"),set:Me("set"),delete:Me("delete"),clear:Me("clear"),forEach:je(!0,!0)};function Ne(e,t){const n=t?e?Ve:Te:e?Ue:Ie;return(t,r,o)=>"__v_isReactive"===r?!e:"__v_isReadonly"===r?e:"__v_raw"===r?t:Reflect.get(x(n,r)&&r in t?n:t,r,o)}["keys","values","entries",Symbol.iterator].forEach((e=>{Ie[e]=$e(e,!1,!1),Ue[e]=$e(e,!0,!1),Te[e]=$e(e,!1,!0),Ve[e]=$e(e,!0,!0)}));const Le={get:Ne(!1,!1)},Be={get:Ne(!1,!0)},De={get:Ne(!0,!1)},qe=new WeakMap,ze=new WeakMap,We=new WeakMap,Ke=new WeakMap;function Ge(e){return e.__v_skip||!Object.isExtensible(e)?0:function(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}((e=>$(e).slice(8,-1))(e))}function He(e){return e&&e.__v_isReadonly?e:Je(e,!1,ye,Le,qe)}function Xe(e){return Je(e,!0,be,De,We)}function Je(e,t,n,r,o){if(!P(e))return e;if(e.__v_raw&&(!t||!e.__v_isReactive))return e;const s=o.get(e);if(s)return s;const l=Ge(e);if(0===l)return e;const i=new Proxy(e,2===l?r:n);return o.set(e,i),i}function Qe(e){return Ye(e)?Qe(e.__v_raw):!(!e||!e.__v_isReactive)}function Ye(e){return!(!e||!e.__v_isReadonly)}function Ze(e){return Qe(e)||Ye(e)}function et(e){return e&&et(e.__v_raw)||e}const tt=e=>P(e)?He(e):e;function nt(e){return Boolean(e&&!0===e.__v_isRef)}class rt{constructor(e,t=!1){this._rawValue=e,this._shallow=t,this.__v_isRef=!0,this._value=t?e:tt(e)}get value(){return ie(et(this),0,"value"),this._value}set value(e){z(et(e),this._rawValue)&&(this._rawValue=e,this._value=this._shallow?e:tt(e),ce(et(this),"set","value",e))}}function ot(e,t=!1){return nt(e)?e:new rt(e,t)}function st(e){return nt(e)?e.value:e}const lt={get:(e,t,n)=>st(Reflect.get(e,t,n)),set:(e,t,n,r)=>{const o=e[t];return nt(o)&&!nt(n)?(o.value=n,!0):Reflect.set(e,t,n,r)}};function it(e){return Qe(e)?e:new Proxy(e,lt)}class ct{constructor(e,t){this._object=e,this._key=t,this.__v_isRef=!0}get value(){return this._object[this._key]}set value(e){this._object[this._key]=e}}class at{constructor(e,t,n){this._setter=t,this._dirty=!0,this.__v_isRef=!0,this.effect=Z(e,{lazy:!0,scheduler:()=>{this._dirty||(this._dirty=!0,ce(et(this),"set","value"))}}),this.__v_isReadonly=n}get value(){const e=et(this);return e._dirty&&(e._value=this.effect(),e._dirty=!1),ie(e,0,"value"),e._value}set value(e){this._setter(e)}}function ut(e,t,n,r){let o;try{o=r?e(...r):e()}catch(s){pt(s,t,n)}return o}function ft(e,t,n,r){if(C(e)){const o=ut(e,t,n,r);return o&&F(o)&&o.catch((e=>{pt(e,t,n)})),o}const o=[];for(let s=0;s>>1;jt(mt[e])-1?mt.splice(t,0,e):mt.push(e),Rt()}}function Rt(){dt||ht||(ht=!0,kt=Et.then($t))}function At(e,t,n,r){E(e)?n.push(...e):t&&t.includes(e,e.allowRecurse?r+1:r)||n.push(e),Rt()}function Pt(e,t=null){if(vt.length){for(St=t,yt=[...new Set(vt)],vt.length=0,bt=0;btjt(e)-jt(t))),xt=0;xtnull==e.id?1/0:e.id;function $t(e){ht=!1,dt=!0,Pt(e),mt.sort(((e,t)=>jt(e)-jt(t)));try{for(gt=0;gte.trim())):t&&(o=n.map(G))}let i,c=r[i=q(t)]||r[i=q(N(t))];!c&&s&&(c=r[i=q(B(t))]),c&&ft(c,e,6,o);const a=r[i+"Once"];if(a){if(e.emitted){if(e.emitted[i])return}else(e.emitted={})[i]=!0;ft(a,e,6,o)}}function It(e,t,n=!1){if(!t.deopt&&void 0!==e.__emits)return e.__emits;const r=e.emits;let o={},s=!1;if(!C(e)){const r=e=>{const n=It(e,t,!0);n&&(s=!0,b(o,n))};!n&&t.mixins.length&&t.mixins.forEach(r),e.extends&&r(e.extends),e.mixins&&e.mixins.forEach(r)}return r||s?(E(r)?r.forEach((e=>o[e]=null)):b(o,r),e.__emits=o):e.__emits=null}function Tt(e,t){return!(!e||!v(t))&&(t=t.slice(2).replace(/Once$/,""),x(e,t[0].toLowerCase()+t.slice(1))||x(e,B(t))||x(e,t))}let Ut=0;const Vt=e=>Ut+=e;function Nt(e,t,n={},r,o){let s=e[t];Ut++,Qn();const l=s&&Lt(s(n)),i=Zn(Wn,{key:n.key||`_${t}`},l||(r?r():[]),l&&1===e._?64:-2);return!o&&i.scopeId&&(i.slotScopeIds=[i.scopeId+"-s"]),Ut--,i}function Lt(e){return e.some((e=>!er(e)||e.type!==Gn&&!(e.type===Wn&&!Lt(e.children))))?e:null}let Bt=null,Dt=null;function qt(e){const t=Bt;return Bt=e,Dt=e&&e.type.__scopeId||null,t}const zt=e=>Wt;function Wt(e,t=Bt){if(!t)return e;const n=(...n)=>{Ut||Qn(!0);const r=qt(t),o=e(...n);return qt(r),Ut||Yn(),o};return n._c=!0,n}function Kt(e){const{type:t,vnode:n,proxy:r,withProxy:o,props:s,propsOptions:[l],slots:i,attrs:c,emit:a,render:u,renderCache:f,data:p,setupState:d,ctx:h}=e;let m;const g=qt(e);try{let e;if(4&n.shapeFlag){const t=o||r;m=ar(u.call(t,t,f,s,d,p,h)),e=c}else{const n=t;0,m=ar(n.length>1?n(s,{attrs:c,slots:i,emit:a}):n(s,null)),e=t.props?c:Ht(c)}let g=m;if(!1!==t.inheritAttrs&&e){const t=Object.keys(e),{shapeFlag:n}=g;t.length&&(1&n||6&n)&&(l&&t.some(y)&&(e=Xt(e,l)),g=lr(g,e))}n.dirs&&(g.dirs=g.dirs?g.dirs.concat(n.dirs):n.dirs),n.transition&&(g.transition=n.transition),m=g}catch(v){Xn.length=0,pt(v,e,1),m=sr(Gn)}return qt(g),m}function Gt(e){let t;for(let n=0;n{let t;for(const n in e)("class"===n||"style"===n||v(n))&&((t||(t={}))[n]=e[n]);return t},Xt=(e,t)=>{const n={};for(const r in e)y(r)&&r.slice(9)in t||(n[r]=e[r]);return n};function Jt(e,t,n){const r=Object.keys(t);if(r.length!==Object.keys(e).length)return!0;for(let o=0;o{l=!0;const[n,r]=tn(e,t,!0);b(o,n),r&&s.push(...r)};!n&&t.mixins.length&&t.mixins.forEach(r),e.extends&&r(e.extends),e.mixins&&e.mixins.forEach(r)}if(!r&&!l)return e.__props=d;if(E(r))for(let i=0;i-1,n[1]=r<0||t-1||x(n,"default"))&&s.push(e)}}}return e.__props=[o,s]}function nn(e){return"$"!==e[0]}function rn(e){const t=e&&e.toString().match(/^\s*function (\w+)/);return t?t[1]:""}function on(e,t){return rn(e)===rn(t)}function sn(e,t){return E(t)?t.findIndex((t=>on(t,e))):C(t)&&on(t,e)?0:-1}function ln(e,t,n=Rr,r=!1){if(n){const o=n[e]||(n[e]=[]),s=t.__weh||(t.__weh=(...r)=>{if(n.isUnmounted)return;se(),Ar(n);const o=ft(t,n,e,r);return Ar(null),le(),o});return r?o.unshift(s):o.push(s),s}}const cn=e=>(t,n=Rr)=>!Fr&&ln(e,t,n),an=cn("bm"),un=cn("m"),fn=cn("bu"),pn=cn("u"),dn=cn("bum"),hn=cn("um"),mn=cn("rtg"),gn=cn("rtc"),vn={};function yn(e,t,n){return bn(e,t,n)}function bn(e,t,{immediate:n,deep:r,flush:o,onTrack:s,onTrigger:l}=p,i=Rr){let c,a,u=!1;if(nt(e)?(c=()=>e.value,u=!!e._shallow):Qe(e)?(c=()=>e,r=!0):c=E(e)?()=>e.map((e=>nt(e)?e.value:Qe(e)?wn(e):C(e)?ut(e,i,2,[i&&i.proxy]):void 0)):C(e)?t?()=>ut(e,i,2,[i&&i.proxy]):()=>{if(!i||!i.isUnmounted)return a&&a(),ft(e,i,3,[f])}:h,t&&r){const e=c;c=()=>wn(e())}let f=e=>{a=v.options.onStop=()=>{ut(e,i,4)}},d=E(e)?[]:vn;const m=()=>{if(v.active)if(t){const e=v();(r||u||z(e,d))&&(a&&a(),ft(t,i,3,[e,d===vn?void 0:d,f]),d=e)}else v()};let g;m.allowRecurse=!!t,g="sync"===o?m:"post"===o?()=>Un(m,i&&i.suspense):()=>{!i||i.isMounted?function(e){At(e,yt,vt,bt)}(m):m()};const v=Z(c,{lazy:!0,onTrack:s,onTrigger:l,scheduler:g});return Mr(v,i),t?n?m():d=v():"post"===o?Un(v,i&&i.suspense):v(),()=>{ee(v),i&&_(i.effects,v)}}function _n(e,t,n){const r=this.proxy;return bn(R(e)?()=>r[e]:e.bind(r),t.bind(r),n,this)}function wn(e,t=new Set){if(!P(e)||t.has(e))return e;if(t.add(e),nt(e))wn(e.value,t);else if(E(e))for(let n=0;n{wn(e,t)}));else for(const n in e)wn(e[n],t);return e}const xn=e=>e.type.__isKeepAlive;function En(e,t,n=Rr){const r=e.__wdc||(e.__wdc=()=>{let t=n;for(;t;){if(t.isDeactivated)return;t=t.parent}e()});if(ln(t,r,n),n){let e=n.parent;for(;e&&e.parent;)xn(e.parent.vnode)&&kn(r,t,n,e),e=e.parent}}function kn(e,t,n,r){const o=ln(t,e,r,!0);hn((()=>{_(r[t],o)}),n)}const Sn=e=>"_"===e[0]||"$stable"===e,On=e=>E(e)?e.map(ar):[ar(e)],Cn=(e,t,n)=>Wt((e=>On(t(e))),n),Rn=(e,t)=>{const n=e._ctx;for(const r in e){if(Sn(r))continue;const o=e[r];if(C(o))t[r]=Cn(0,o,n);else if(null!=o){const e=On(o);t[r]=()=>e}}},An=(e,t)=>{const n=On(t);e.slots.default=()=>n};function Pn(e,t){if(null===Bt)return e;const n=Bt.proxy,r=e.dirs||(e.dirs=[]);for(let o=0;o(s.has(e)||(e&&C(e.install)?(s.add(e),e.install(i,...t)):C(e)&&(s.add(e),e(i,...t))),i),mixin:e=>(o.mixins.includes(e)||(o.mixins.push(e),(e.props||e.emits)&&(o.deopt=!0)),i),component:(e,t)=>t?(o.components[e]=t,i):o.components[e],directive:(e,t)=>t?(o.directives[e]=t,i):o.directives[e],mount(s,c,a){if(!l){const u=sr(n,r);return u.appContext=o,c&&t?t(u,s):e(u,s,a),l=!0,i._container=s,s.__vue_app__=i,u.component.proxy}},unmount(){l&&(e(null,i._container),delete i._container.__vue_app__)},provide:(e,t)=>(o.provides[e]=t,i)};return i}}function In(e){return C(e)?{setup:e,name:e.name}:e}const Tn={scheduler:Ct,allowRecurse:!0},Un=function(e,t){t&&t.pendingBranch?E(e)?t.effects.push(...e):t.effects.push(e):At(e,wt,_t,xt)},Vn=(e,t,n,r)=>{if(E(e))return void e.forEach(((e,o)=>Vn(e,t&&(E(t)?t[o]:t),n,r)));let o;if(r){if(r.type.__asyncLoader)return;o=4&r.shapeFlag?r.component.exposed||r.component.proxy:r.el}else o=null;const{i:s,r:l}=e,i=t&&t.r,c=s.refs===p?s.refs={}:s.refs,a=s.setupState;if(null!=i&&i!==l&&(R(i)?(c[i]=null,x(a,i)&&(a[i]=null)):nt(i)&&(i.value=null)),R(l)){const e=()=>{c[l]=o,x(a,l)&&(a[l]=o)};o?(e.id=-1,Un(e,n)):e()}else if(nt(l)){const e=()=>{l.value=o};o?(e.id=-1,Un(e,n)):e()}else C(l)&&ut(l,s,12,[o,c])};function Nn(e){return function(e,t){const{insert:n,remove:r,patchProp:o,forcePatchProp:s,createElement:l,createText:i,createComment:c,setText:a,setElementText:u,parentNode:f,nextSibling:m,setScopeId:g=h,cloneNode:v,insertStaticContent:y}=e,_=(e,t,n,r=null,o=null,s=null,l=!1,i=null,c=!1)=>{e&&!tr(e,t)&&(r=re(e),J(e,o,s,!0),e=null),-2===t.patchFlag&&(c=!1,t.dynamicChildren=null);const{type:a,ref:u,shapeFlag:f}=t;switch(a){case Kn:w(e,t,n,r);break;case Gn:E(e,t,n,r);break;case Hn:null==e&&k(t,n,r,l);break;case Wn:I(e,t,n,r,o,s,l,i,c);break;default:1&f?C(e,t,n,r,o,s,l,i,c):6&f?U(e,t,n,r,o,s,l,i,c):(64&f||128&f)&&a.process(e,t,n,r,o,s,l,i,c,ie)}null!=u&&o&&Vn(u,e&&e.ref,s,t)},w=(e,t,r,o)=>{if(null==e)n(t.el=i(t.children),r,o);else{const n=t.el=e.el;t.children!==e.children&&a(n,t.children)}},E=(e,t,r,o)=>{null==e?n(t.el=c(t.children||""),r,o):t.el=e.el},k=(e,t,n,r)=>{[e.el,e.anchor]=y(e.children,t,n,r)},S=({el:e,anchor:t},r,o)=>{let s;for(;e&&e!==t;)s=m(e),n(e,r,o),e=s;n(t,r,o)},O=({el:e,anchor:t})=>{let n;for(;e&&e!==t;)n=m(e),r(e),e=n;r(t)},C=(e,t,n,r,o,s,l,i,c)=>{l=l||"svg"===t.type,null==e?R(t,n,r,o,s,l,i,c):j(e,t,o,s,l,i,c)},R=(e,t,r,s,i,c,a,f)=>{let p,d;const{type:h,props:m,shapeFlag:g,transition:y,patchFlag:b,dirs:_}=e;if(e.el&&void 0!==v&&-1===b)p=e.el=v(e.el);else{if(p=e.el=l(e.type,c,m&&m.is,m),8&g?u(p,e.children):16&g&&P(e.children,p,null,s,i,c&&"foreignObject"!==h,a,f||!!e.dynamicChildren),_&&Fn(e,null,s,"created"),m){for(const t in m)T(t)||o(p,t,null,m[t],c,e.children,s,i,ne);(d=m.onVnodeBeforeMount)&&Ln(d,s,e)}A(p,e,e.scopeId,a,s)}_&&Fn(e,null,s,"beforeMount");const w=(!i||i&&!i.pendingBranch)&&y&&!y.persisted;w&&y.beforeEnter(p),n(p,t,r),((d=m&&m.onVnodeMounted)||w||_)&&Un((()=>{d&&Ln(d,s,e),w&&y.enter(p),_&&Fn(e,null,s,"mounted")}),i)},A=(e,t,n,r,o)=>{if(n&&g(e,n),r)for(let s=0;s{for(let a=c;a{const a=t.el=e.el;let{patchFlag:f,dynamicChildren:d,dirs:h}=t;f|=16&e.patchFlag;const m=e.props||p,g=t.props||p;let v;if((v=g.onVnodeBeforeUpdate)&&Ln(v,n,t,e),h&&Fn(t,e,n,"beforeUpdate"),f>0){if(16&f)M(a,t,m,g,n,r,l);else if(2&f&&m.class!==g.class&&o(a,"class",null,g.class,l),4&f&&o(a,"style",m.style,g.style,l),8&f){const i=t.dynamicProps;for(let t=0;t{v&&Ln(v,n,t,e),h&&Fn(t,e,n,"updated")}),r)},$=(e,t,n,r,o,s,l)=>{for(let i=0;i{if(n!==r){for(const a in r){if(T(a))continue;const u=r[a],f=n[a];(u!==f||s&&s(e,a))&&o(e,a,f,u,c,t.children,l,i,ne)}if(n!==p)for(const s in n)T(s)||s in r||o(e,s,n[s],null,c,t.children,l,i,ne)}},I=(e,t,r,o,s,l,c,a,u)=>{const f=t.el=e?e.el:i(""),p=t.anchor=e?e.anchor:i("");let{patchFlag:d,dynamicChildren:h,slotScopeIds:m}=t;d>0&&(u=!0),m&&(a=a?a.concat(m):m),null==e?(n(f,r,o),n(p,r,o),P(t.children,r,p,s,l,c,a,u)):d>0&&64&d&&h&&e.dynamicChildren?($(e.dynamicChildren,h,r,s,l,c,a),(null!=t.key||s&&t===s.subTree)&&Bn(e,t,!0)):z(e,t,r,p,s,l,c,a,u)},U=(e,t,n,r,o,s,l,i,c)=>{t.slotScopeIds=i,null==e?512&t.shapeFlag?o.ctx.activate(t,n,r,l,c):V(t,n,r,o,s,l,c):L(e,t,c)},V=(e,t,n,r,o,s,l)=>{const i=e.component=function(e,t,n){const r=e.type,o=(t?t.appContext:e.appContext)||Or,s={uid:Cr++,vnode:e,type:r,parent:t,appContext:o,root:null,next:null,subTree:null,update:null,render:null,proxy:null,exposed:null,withProxy:null,effects:null,provides:t?t.provides:Object.create(o.provides),accessCache:null,renderCache:[],components:null,directives:null,propsOptions:tn(r,o),emitsOptions:It(r,o),emit:null,emitted:null,propsDefaults:p,ctx:p,data:p,props:p,attrs:p,slots:p,refs:p,setupState:p,setupContext:null,suspense:n,suspenseId:n?n.pendingId:0,asyncDep:null,asyncResolved:!1,isMounted:!1,isUnmounted:!1,isDeactivated:!1,bc:null,c:null,bm:null,m:null,bu:null,u:null,um:null,bum:null,da:null,a:null,rtg:null,rtc:null,ec:null};return s.ctx={_:s},s.root=t?t.root:s,s.emit=Mt.bind(null,s),s}(e,r,o);if(xn(e)&&(i.ctx.renderer=ie),function(e,t=!1){Fr=t;const{props:n,children:r}=e.vnode,o=Pr(e);Yt(e,n,o,t),((e,t)=>{if(32&e.vnode.shapeFlag){const n=t._;n?(e.slots=t,K(t,"_",n)):Rn(t,e.slots={})}else e.slots={},t&&An(e,t);K(e.slots,nr,1)})(e,r);const s=o?function(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,kr);const{setup:r}=n;if(r){const n=e.setupContext=r.length>1?function(e){const t=t=>{e.exposed=it(t)};return{attrs:e.attrs,slots:e.slots,emit:e.emit,expose:t}}(e):null;Rr=e,se();const o=ut(r,e,0,[e.props,n]);if(le(),Rr=null,F(o)){if(t)return o.then((t=>{jr(e,t)})).catch((t=>{pt(t,e,0)}));e.asyncDep=o}else jr(e,o)}else $r(e)}(e,t):void 0;Fr=!1}(i),i.asyncDep){if(o&&o.registerDep(i,D),!e.el){const e=i.subTree=sr(Gn);E(null,e,t,n)}}else D(i,e,t,n,o,s,l)},L=(e,t,n)=>{const r=t.component=e.component;if(function(e,t,n){const{props:r,children:o,component:s}=e,{props:l,children:i,patchFlag:c}=t,a=s.emitsOptions;if(t.dirs||t.transition)return!0;if(!(n&&c>=0))return!(!o&&!i||i&&i.$stable)||r!==l&&(r?!l||Jt(r,l,a):!!l);if(1024&c)return!0;if(16&c)return r?Jt(r,l,a):!!l;if(8&c){const e=t.dynamicProps;for(let t=0;tgt&&mt.splice(t,1)}(r.update),r.update()}else t.component=e.component,t.el=e.el,r.vnode=t},D=(e,t,n,r,o,s,l)=>{e.update=Z((function(){if(e.isMounted){let t,{next:n,bu:r,u:i,parent:c,vnode:a}=e,u=n;n?(n.el=a.el,q(e,n,l)):n=a,r&&W(r),(t=n.props&&n.props.onVnodeBeforeUpdate)&&Ln(t,c,n,a);const p=Kt(e),d=e.subTree;e.subTree=p,_(d,p,f(d.el),re(d),e,o,s),n.el=p.el,null===u&&function({vnode:e,parent:t},n){for(;t&&t.subTree===e;)(e=t.vnode).el=n,t=t.parent}(e,p.el),i&&Un(i,o),(t=n.props&&n.props.onVnodeUpdated)&&Un((()=>{Ln(t,c,n,a)}),o)}else{let l;const{el:i,props:c}=t,{bm:a,m:u,parent:f}=e;a&&W(a),(l=c&&c.onVnodeBeforeMount)&&Ln(l,f,t);const p=e.subTree=Kt(e);if(i&&ue?ue(t.el,p,e,o,null):(_(null,p,n,r,e,o,s),t.el=p.el),u&&Un(u,o),l=c&&c.onVnodeMounted){const e=t;Un((()=>{Ln(l,f,e)}),o)}const{a:d}=e;d&&256&t.shapeFlag&&Un(d,o),e.isMounted=!0,t=n=r=null}}),Tn)},q=(e,t,n)=>{t.component=e;const r=e.vnode.props;e.vnode=t,e.next=null,function(e,t,n,r){const{props:o,attrs:s,vnode:{patchFlag:l}}=e,i=et(o),[c]=e.propsOptions;if(!(r||l>0)||16&l){let r;Zt(e,t,o,s);for(const s in i)t&&(x(t,s)||(r=B(s))!==s&&x(t,r))||(c?!n||void 0===n[s]&&void 0===n[r]||(o[s]=en(c,t||p,s,void 0,e)):delete o[s]);if(s!==i)for(const e in s)t&&x(t,e)||delete s[e]}else if(8&l){const n=e.vnode.dynamicProps;for(let r=0;r{const{vnode:r,slots:o}=e;let s=!0,l=p;if(32&r.shapeFlag){const e=t._;e?n&&1===e?s=!1:(b(o,t),n||1!==e||delete o._):(s=!t.$stable,Rn(t,o)),l=t}else t&&(An(e,t),l={default:1});if(s)for(const i in o)Sn(i)||i in l||delete o[i]})(e,t.children,n),se(),Pt(void 0,e.update),le()},z=(e,t,n,r,o,s,l,i,c=!1)=>{const a=e&&e.children,f=e?e.shapeFlag:0,p=t.children,{patchFlag:d,shapeFlag:h}=t;if(d>0){if(128&d)return void H(a,p,n,r,o,s,l,i,c);if(256&d)return void G(a,p,n,r,o,s,l,i,c)}8&h?(16&f&&ne(a,o,s),p!==a&&u(n,p)):16&f?16&h?H(a,p,n,r,o,s,l,i,c):ne(a,o,s,!0):(8&f&&u(n,""),16&h&&P(p,n,r,o,s,l,i,c))},G=(e,t,n,r,o,s,l,i,c)=>{t=t||d;const a=(e=e||d).length,u=t.length,f=Math.min(a,u);let p;for(p=0;pu?ne(e,o,s,!0,!1,f):P(t,n,r,o,s,l,i,c,f)},H=(e,t,n,r,o,s,l,i,c)=>{let a=0;const u=t.length;let f=e.length-1,p=u-1;for(;a<=f&&a<=p;){const r=e[a],u=t[a]=c?ur(t[a]):ar(t[a]);if(!tr(r,u))break;_(r,u,n,null,o,s,l,i,c),a++}for(;a<=f&&a<=p;){const r=e[f],a=t[p]=c?ur(t[p]):ar(t[p]);if(!tr(r,a))break;_(r,a,n,null,o,s,l,i,c),f--,p--}if(a>f){if(a<=p){const e=p+1,f=ep)for(;a<=f;)J(e[a],o,s,!0),a++;else{const h=a,m=a,g=new Map;for(a=m;a<=p;a++){const e=t[a]=c?ur(t[a]):ar(t[a]);null!=e.key&&g.set(e.key,a)}let v,y=0;const b=p-m+1;let w=!1,x=0;const E=new Array(b);for(a=0;a=b){J(r,o,s,!0);continue}let u;if(null!=r.key)u=g.get(r.key);else for(v=m;v<=p;v++)if(0===E[v-m]&&tr(r,t[v])){u=v;break}void 0===u?J(r,o,s,!0):(E[u-m]=a+1,u>=x?x=u:w=!0,_(r,t[u],n,null,o,s,l,i,c),y++)}const k=w?function(e){const t=e.slice(),n=[0];let r,o,s,l,i;const c=e.length;for(r=0;r0&&(t[r]=n[s-1]),n[s]=r)}}s=n.length,l=n[s-1];for(;s-- >0;)n[s]=l,l=t[l];return n}(E):d;for(v=k.length-1,a=b-1;a>=0;a--){const e=m+a,f=t[e],p=e+1{const{el:l,type:i,transition:c,children:a,shapeFlag:u}=e;if(6&u)return void X(e.component.subTree,t,r,o);if(128&u)return void e.suspense.move(t,r,o);if(64&u)return void i.move(e,t,r,ie);if(i===Wn){n(l,t,r);for(let e=0;ec.enter(l)),s);else{const{leave:e,delayLeave:o,afterLeave:s}=c,i=()=>n(l,t,r),a=()=>{e(l,(()=>{i(),s&&s()}))};o?o(l,i,a):a()}else n(l,t,r)},J=(e,t,n,r=!1,o=!1)=>{const{type:s,props:l,ref:i,children:c,dynamicChildren:a,shapeFlag:u,patchFlag:f,dirs:p}=e;if(null!=i&&Vn(i,null,n,null),256&u)return void t.ctx.deactivate(e);const d=1&u&&p;let h;if((h=l&&l.onVnodeBeforeUnmount)&&Ln(h,t,e),6&u)te(e.component,n,r);else{if(128&u)return void e.suspense.unmount(n,r);d&&Fn(e,null,t,"beforeUnmount"),64&u?e.type.remove(e,t,n,o,ie,r):a&&(s!==Wn||f>0&&64&f)?ne(a,t,n,!1,!0):(s===Wn&&(128&f||256&f)||!o&&16&u)&&ne(c,t,n),r&&Q(e)}((h=l&&l.onVnodeUnmounted)||d)&&Un((()=>{h&&Ln(h,t,e),d&&Fn(e,null,t,"unmounted")}),n)},Q=e=>{const{type:t,el:n,anchor:o,transition:s}=e;if(t===Wn)return void Y(n,o);if(t===Hn)return void O(e);const l=()=>{r(n),s&&!s.persisted&&s.afterLeave&&s.afterLeave()};if(1&e.shapeFlag&&s&&!s.persisted){const{leave:t,delayLeave:r}=s,o=()=>t(n,l);r?r(e.el,l,o):o()}else l()},Y=(e,t)=>{let n;for(;e!==t;)n=m(e),r(e),e=n;r(t)},te=(e,t,n)=>{const{bum:r,effects:o,update:s,subTree:l,um:i}=e;if(r&&W(r),o)for(let c=0;c{e.isUnmounted=!0}),t),t&&t.pendingBranch&&!t.isUnmounted&&e.asyncDep&&!e.asyncResolved&&e.suspenseId===t.pendingId&&(t.deps--,0===t.deps&&t.resolve())},ne=(e,t,n,r=!1,o=!1,s=0)=>{for(let l=s;l6&e.shapeFlag?re(e.component.subTree):128&e.shapeFlag?e.suspense.next():m(e.anchor||e.el),oe=(e,t,n)=>{null==e?t._vnode&&J(t._vnode,null,null,!0):_(t._vnode||null,e,t,null,null,null,n),Ft(),t._vnode=e},ie={p:_,um:J,m:X,r:Q,mt:V,mc:P,pc:z,pbc:$,n:re,o:e};let ae,ue;t&&([ae,ue]=t(ie));return{render:oe,hydrate:ae,createApp:Mn(oe,ae)}}(e)}function Ln(e,t,n,r=null){ft(e,t,7,[n,r])}function Bn(e,t,n=!1){const r=e.children,o=t.children;if(E(r)&&E(o))for(let s=0;snull!=e?e:null,or=({ref:e})=>null!=e?R(e)||nt(e)||C(e)?{i:Bt,r:e}:e:null,sr=function(e,t=null,n=null,o=0,s=null,l=!1){e&&e!==qn||(e=Gn);if(er(e)){const r=lr(e,t,!0);return n&&fr(r,n),r}c=e,C(c)&&"__vccOpts"in c&&(e=e.__vccOpts);var c;if(t){(Ze(t)||nr in t)&&(t=b({},t));let{class:e,style:n}=t;e&&!R(e)&&(t.class=i(e)),P(n)&&(Ze(n)&&!E(n)&&(n=b({},n)),t.style=r(n))}const a=R(e)?1:(e=>e.__isSuspense)(e)?128:(e=>e.__isTeleport)(e)?64:P(e)?4:C(e)?2:0,u={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&rr(t),ref:t&&or(t),scopeId:Dt,slotScopeIds:null,children:null,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:a,patchFlag:o,dynamicProps:s,dynamicChildren:null,appContext:null};if(fr(u,n),128&a){const{content:e,fallback:t}=function(e){const{shapeFlag:t,children:n}=e;let r,o;return 32&t?(r=Qt(n.default),o=Qt(n.fallback)):(r=Qt(n),o=ar(null)),{content:r,fallback:o}}(u);u.ssContent=e,u.ssFallback=t}!l&&Jn&&(o>0||6&a)&&32!==o&&Jn.push(u);return u};function lr(e,t,n=!1){const{props:o,ref:s,patchFlag:l,children:c}=e,a=t?function(...e){const t=b({},e[0]);for(let n=1;n1)return n&&C(t)?t():t}}let hr=!0;function mr(e,t,n=[],r=[],o=[],s=!1){const{mixins:l,extends:i,data:c,computed:a,methods:u,watch:f,provide:d,inject:m,components:g,directives:v,beforeMount:y,mounted:_,beforeUpdate:w,updated:x,activated:k,deactivated:S,beforeDestroy:O,beforeUnmount:R,destroyed:A,unmounted:F,render:j,renderTracked:$,renderTriggered:M,errorCaptured:I,expose:T}=t,U=e.proxy,V=e.ctx,N=e.appContext.mixins;if(s&&j&&e.render===h&&(e.render=j),s||(hr=!1,gr("beforeCreate","bc",t,e,N),hr=!0,yr(e,N,n,r,o)),i&&mr(e,i,n,r,o,!0),l&&yr(e,l,n,r,o),m)if(E(m))for(let p=0;pbr(e,t,U))),c&&br(e,c,U)),a)for(const p in a){const e=a[p],t=Tr({get:C(e)?e.bind(U,U):C(e.get)?e.get.bind(U,U):h,set:!C(e)&&C(e.set)?e.set.bind(U):h});Object.defineProperty(V,p,{enumerable:!0,configurable:!0,get:()=>t.value,set:e=>t.value=e})}var L;if(f&&r.push(f),!s&&r.length&&r.forEach((e=>{for(const t in e)_r(e[t],V,U,t)})),d&&o.push(d),!s&&o.length&&o.forEach((e=>{const t=C(e)?e.call(U):e;Reflect.ownKeys(t).forEach((e=>{pr(e,t[e])}))})),s&&(g&&b(e.components||(e.components=b({},e.type.components)),g),v&&b(e.directives||(e.directives=b({},e.type.directives)),v)),s||gr("created","c",t,e,N),y&&an(y.bind(U)),_&&un(_.bind(U)),w&&fn(w.bind(U)),x&&pn(x.bind(U)),k&&En(k.bind(U),"a",L),S&&function(e,t){En(e,"da",t)}(S.bind(U)),I&&((e,t=Rr)=>{ln("ec",e,t)})(I.bind(U)),$&&gn($.bind(U)),M&&mn(M.bind(U)),R&&dn(R.bind(U)),F&&hn(F.bind(U)),E(T)&&!s)if(T.length){const t=e.exposed||(e.exposed=it({}));T.forEach((e=>{t[e]=function(e,t){return nt(e[t])?e[t]:new ct(e,t)}(U,e)}))}else e.exposed||(e.exposed=p)}function gr(e,t,n,r,o){for(let s=0;s{let t=e;for(let e=0;en[r];if(R(e)){const n=t[e];C(n)&&yn(o,n)}else if(C(e))yn(o,e.bind(n));else if(P(e))if(E(e))e.forEach((e=>_r(e,t,n,r)));else{const r=C(e.handler)?e.handler.bind(n):t[e.handler];C(r)&&yn(o,r,e)}}function wr(e,t,n){const r=n.appContext.config.optionMergeStrategies,{mixins:o,extends:s}=t;s&&wr(e,s,n),o&&o.forEach((t=>wr(e,t,n)));for(const l in t)r&&x(r,l)?e[l]=r[l](e[l],t[l],n.proxy,l):e[l]=t[l]}const xr=e=>e?Pr(e)?e.exposed?e.exposed:e.proxy:xr(e.parent):null,Er=b(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>xr(e.parent),$root:e=>xr(e.root),$emit:e=>e.emit,$options:e=>function(e){const t=e.type,{__merged:n,mixins:r,extends:o}=t;if(n)return n;const s=e.appContext.mixins;if(!s.length&&!r&&!o)return t;const l={};return s.forEach((t=>wr(l,t,e))),wr(l,t,e),t.__merged=l}(e),$forceUpdate:e=>()=>Ct(e.update),$nextTick:e=>Ot.bind(e.proxy),$watch:e=>_n.bind(e)}),kr={get({_:e},t){const{ctx:n,setupState:r,data:o,props:s,accessCache:l,type:i,appContext:c}=e;if("__v_skip"===t)return!0;let a;if("$"!==t[0]){const i=l[t];if(void 0!==i)switch(i){case 0:return r[t];case 1:return o[t];case 3:return n[t];case 2:return s[t]}else{if(r!==p&&x(r,t))return l[t]=0,r[t];if(o!==p&&x(o,t))return l[t]=1,o[t];if((a=e.propsOptions[0])&&x(a,t))return l[t]=2,s[t];if(n!==p&&x(n,t))return l[t]=3,n[t];hr&&(l[t]=4)}}const u=Er[t];let f,d;return u?("$attrs"===t&&ie(e,0,t),u(e)):(f=i.__cssModules)&&(f=f[t])?f:n!==p&&x(n,t)?(l[t]=3,n[t]):(d=c.config.globalProperties,x(d,t)?d[t]:void 0)},set({_:e},t,n){const{data:r,setupState:o,ctx:s}=e;if(o!==p&&x(o,t))o[t]=n;else if(r!==p&&x(r,t))r[t]=n;else if(x(e.props,t))return!1;return("$"!==t[0]||!(t.slice(1)in e))&&(s[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:r,appContext:o,propsOptions:s}},l){let i;return void 0!==n[l]||e!==p&&x(e,l)||t!==p&&x(t,l)||(i=s[0])&&x(i,l)||x(r,l)||x(Er,l)||x(o.config.globalProperties,l)}},Sr=b({},kr,{get(e,t){if(t!==Symbol.unscopables)return kr.get(e,t,e)},has:(e,n)=>"_"!==n[0]&&!t(n)}),Or=jn();let Cr=0;let Rr=null;const Ar=e=>{Rr=e};function Pr(e){return 4&e.vnode.shapeFlag}let Fr=!1;function jr(e,t,n){C(t)?e.render=t:P(t)&&(e.setupState=it(t)),$r(e)}function $r(e,t){const n=e.type;e.render||(e.render=n.render||h,e.render._rc&&(e.withProxy=new Proxy(e.ctx,Sr))),Rr=e,se(),mr(e,n),le(),Rr=null}function Mr(e,t=Rr){t&&(t.effects||(t.effects=[])).push(e)}function Ir(e){return C(e)&&e.displayName||e.name}function Tr(e){const t=function(e){let t,n;return C(e)?(t=e,n=h):(t=e.get,n=e.set),new at(t,n,C(e)||!e.set)}(e);return Mr(t.effect),t}function Ur(e,t,n){const r=arguments.length;return 2===r?P(t)&&!E(t)?er(t)?sr(e,null,[t]):sr(e,t):sr(e,null,t):(r>3?n=Array.prototype.slice.call(arguments,2):3===r&&er(n)&&(n=[n]),sr(e,t,n))}function Vr(e,t){let n;if(E(e)||R(e)){n=new Array(e.length);for(let r=0,o=e.length;r{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{const o=t?Br.createElementNS(Lr,e):Br.createElement(e,n?{is:n}:void 0);return"select"===e&&r&&null!=r.multiple&&o.setAttribute("multiple",r.multiple),o},createText:e=>Br.createTextNode(e),createComment:e=>Br.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Br.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},cloneNode(e){const t=e.cloneNode(!0);return"_value"in e&&(t._value=e._value),t},insertStaticContent(e,t,n,r){const o=r?qr||(qr=Br.createElementNS(Lr,"svg")):Dr||(Dr=Br.createElement("div"));o.innerHTML=e;const s=o.firstChild;let l=s,i=l;for(;l;)i=l,zr.insert(l,t,n),l=o.firstChild;return[s,i]}};const Wr=/\s*!important$/;function Kr(e,t,n){if(E(n))n.forEach((n=>Kr(e,t,n)));else if(t.startsWith("--"))e.setProperty(t,n);else{const r=function(e,t){const n=Hr[t];if(n)return n;let r=N(t);if("filter"!==r&&r in e)return Hr[t]=r;r=D(r);for(let o=0;odocument.createEvent("Event").timeStamp&&(Jr=()=>performance.now());const e=navigator.userAgent.match(/firefox\/(\d+)/i);Qr=!!(e&&Number(e[1])<=53)}let Yr=0;const Zr=Promise.resolve(),eo=()=>{Yr=0};function to(e,t,n,r){e.addEventListener(t,n,r)}function no(e,t,n,r,o=null){const s=e._vei||(e._vei={}),l=s[t];if(r&&l)l.value=r;else{const[n,i]=function(e){let t;if(ro.test(e)){let n;for(t={};n=e.match(ro);)e=e.slice(0,e.length-n[0].length),t[n[0].toLowerCase()]=!0}return[B(e.slice(2)),t]}(t);if(r){to(e,n,s[t]=function(e,t){const n=e=>{const r=e.timeStamp||Jr();(Qr||r>=n.attached-1)&&ft(function(e,t){if(E(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map((e=>t=>!t._stopped&&e(t)))}return t}(e,n.value),t,5,[e])};return n.value=e,n.attached=(()=>Yr||(Zr.then(eo),Yr=Jr()))(),n}(r,o),i)}else l&&(!function(e,t,n,r){e.removeEventListener(t,n,r)}(e,n,l,i),s[t]=void 0)}}const ro=/(?:Once|Passive|Capture)$/;const oo=/^on[a-z]/;const so=e=>{const t=e.props["onUpdate:modelValue"];return E(t)?e=>W(t,e):t};function lo(e){e.target.composing=!0}function io(e){const t=e.target;t.composing&&(t.composing=!1,function(e,t){const n=document.createEvent("HTMLEvents");n.initEvent(t,!0,!0),e.dispatchEvent(n)}(t,"input"))}const co={created(e,{modifiers:{lazy:t,trim:n,number:r}},o){e._assign=so(o);const s=r||"number"===e.type;to(e,t?"change":"input",(t=>{if(t.target.composing)return;let r=e.value;n?r=r.trim():s&&(r=G(r)),e._assign(r)})),n&&to(e,"change",(()=>{e.value=e.value.trim()})),t||(to(e,"compositionstart",lo),to(e,"compositionend",io),to(e,"change",io))},mounted(e,{value:t}){e.value=null==t?"":t},beforeUpdate(e,{value:t,modifiers:{trim:n,number:r}},o){if(e._assign=so(o),e.composing)return;if(document.activeElement===e){if(n&&e.value.trim()===t)return;if((r||"number"===e.type)&&G(e.value)===t)return}const s=null==t?"":t;e.value!==s&&(e.value=s)}},ao={created(e,{value:t},n){e.checked=c(t,n.props.value),e._assign=so(n),to(e,"change",(()=>{e._assign(po(e))}))},beforeUpdate(e,{value:t,oldValue:n},r){e._assign=so(r),t!==n&&(e.checked=c(t,r.props.value))}},uo={created(e,{value:t,modifiers:{number:n}},r){const o=S(t);to(e,"change",(()=>{const t=Array.prototype.filter.call(e.options,(e=>e.selected)).map((e=>n?G(po(e)):po(e)));e._assign(e.multiple?o?new Set(t):t:t[0])})),e._assign=so(r)},mounted(e,{value:t}){fo(e,t)},beforeUpdate(e,t,n){e._assign=so(n)},updated(e,{value:t}){fo(e,t)}};function fo(e,t){const n=e.multiple;if(!n||E(t)||S(t)){for(let r=0,o=e.options.length;r-1:o.selected=t.has(s);else if(c(po(o),t))return void(e.selectedIndex=r)}n||(e.selectedIndex=-1)}}function po(e){return"_value"in e?e._value:e.value}const ho=["ctrl","shift","alt","meta"],mo={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&0!==e.button,middle:e=>"button"in e&&1!==e.button,right:e=>"button"in e&&2!==e.button,exact:(e,t)=>ho.some((n=>e[`${n}Key`]&&!t.includes(n)))},go=(e,t)=>(n,...r)=>{for(let e=0;en=>{if(!("key"in n))return;const r=B(n.key);return t.some((e=>e===r||vo[e]===r))?e(n):void 0},bo={beforeMount(e,{value:t},{transition:n}){e._vod="none"===e.style.display?"":e.style.display,n&&t?n.beforeEnter(e):_o(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:r}){!t!=!n&&(r?t?(r.beforeEnter(e),_o(e,!0),r.enter(e)):r.leave(e,(()=>{_o(e,!1)})):_o(e,t))},beforeUnmount(e,{value:t}){_o(e,t)}};function _o(e,t){e.style.display=t?e._vod:"none"}const wo=b({patchProp:(e,t,r,o,s=!1,l,i,c,a)=>{switch(t){case"class":!function(e,t,n){if(null==t&&(t=""),n)e.setAttribute("class",t);else{const n=e._vtc;n&&(t=(t?[t,...n]:[...n]).join(" ")),e.className=t}}(e,o,s);break;case"style":!function(e,t,n){const r=e.style;if(n)if(R(n)){if(t!==n){const t=r.display;r.cssText=n,"_vod"in e&&(r.display=t)}}else{for(const e in n)Kr(r,e,n[e]);if(t&&!R(t))for(const e in t)null==n[e]&&Kr(r,e,"")}else e.removeAttribute("style")}(e,r,o);break;default:v(t)?y(t)||no(e,t,0,o,i):function(e,t,n,r){if(r)return"innerHTML"===t||!!(t in e&&oo.test(t)&&C(n));if("spellcheck"===t||"draggable"===t)return!1;if("form"===t)return!1;if("list"===t&&"INPUT"===e.tagName)return!1;if("type"===t&&"TEXTAREA"===e.tagName)return!1;if(oo.test(t)&&R(n))return!1;return t in e}(e,t,o,s)?function(e,t,n,r,o,s,l){if("innerHTML"===t||"textContent"===t)return r&&l(r,o,s),void(e[t]=null==n?"":n);if("value"!==t||"PROGRESS"===e.tagName){if(""===n||null==n){const r=typeof e[t];if(""===n&&"boolean"===r)return void(e[t]=!0);if(null==n&&"string"===r)return e[t]="",void e.removeAttribute(t);if("number"===r)return e[t]=0,void e.removeAttribute(t)}try{e[t]=n}catch(i){}}else{e._value=n;const t=null==n?"":n;e.value!==t&&(e.value=t)}}(e,t,o,l,i,c,a):("true-value"===t?e._trueValue=o:"false-value"===t&&(e._falseValue=o),function(e,t,r,o){if(o&&t.startsWith("xlink:"))null==r?e.removeAttributeNS(Xr,t.slice(6,t.length)):e.setAttributeNS(Xr,t,r);else{const o=n(t);null==r||o&&!1===r?e.removeAttribute(t):e.setAttribute(t,o?"":r)}}(e,t,o,s))}},forcePatchProp:(e,t)=>"value"===t},zr);let xo;const Eo=(...e)=>{const t=(xo||(xo=Nn(wo))).createApp(...e),{mount:n}=t;return t.mount=e=>{const r=function(e){if(R(e)){return document.querySelector(e)}return e} +function e(e,t){const n=Object.create(null),r=e.split(",");for(let o=0;o!!n[e.toLowerCase()]:e=>!!n[e]}const t=e("Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt"),n=e("itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly");function r(e){if(E(e)){const t={};for(let n=0;n{if(e){const n=e.split(s);n.length>1&&(t[n[0].trim()]=n[1].trim())}})),t}function i(e){let t="";if(R(e))t=e;else if(E(e))for(let n=0;nc(e,t)))}const u=e=>null==e?"":P(e)?JSON.stringify(e,f,2):String(e),f=(e,t)=>k(t)?{[`Map(${t.size})`]:[...t.entries()].reduce(((e,[t,n])=>(e[`${t} =>`]=n,e)),{})}:S(t)?{[`Set(${t.size})`]:[...t.values()]}:!P(t)||E(t)||M(t)?t:String(t),p={},d=[],h=()=>{},m=()=>!1,g=/^on[^a-z]/,v=e=>g.test(e),y=e=>e.startsWith("onUpdate:"),b=Object.assign,_=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},w=Object.prototype.hasOwnProperty,x=(e,t)=>w.call(e,t),E=Array.isArray,k=e=>"[object Map]"===$(e),S=e=>"[object Set]"===$(e),O=e=>e instanceof Date,C=e=>"function"==typeof e,R=e=>"string"==typeof e,A=e=>"symbol"==typeof e,P=e=>null!==e&&"object"==typeof e,F=e=>P(e)&&C(e.then)&&C(e.catch),j=Object.prototype.toString,$=e=>j.call(e),M=e=>"[object Object]"===$(e),I=e=>R(e)&&"NaN"!==e&&"-"!==e[0]&&""+parseInt(e,10)===e,T=e(",key,ref,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),V=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},U=/-(\w)/g,N=V((e=>e.replace(U,((e,t)=>t?t.toUpperCase():"")))),L=/\B([A-Z])/g,B=V((e=>e.replace(L,"-$1").toLowerCase())),D=V((e=>e.charAt(0).toUpperCase()+e.slice(1))),q=V((e=>e?`on${D(e)}`:"")),z=(e,t)=>e!==t&&(e==e||t==t),W=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},G=e=>{const t=parseFloat(e);return isNaN(t)?e:t},H=new WeakMap,X=[];let J;const Q=Symbol(""),Y=Symbol("");function Z(e,t=p){(function(e){return e&&!0===e._isEffect})(e)&&(e=e.raw);const n=function(e,t){const n=function(){if(!n.active)return t.scheduler?void 0:e();if(!X.includes(n)){ne(n);try{return oe.push(re),re=!0,X.push(n),J=n,e()}finally{X.pop(),le(),J=X[X.length-1]}}};return n.id=te++,n.allowRecurse=!!t.allowRecurse,n._isEffect=!0,n.active=!0,n.raw=e,n.deps=[],n.options=t,n}(e,t);return t.lazy||n(),n}function ee(e){e.active&&(ne(e),e.options.onStop&&e.options.onStop(),e.active=!1)}let te=0;function ne(e){const{deps:t}=e;if(t.length){for(let n=0;n{e&&e.forEach((e=>{(e!==J||e.allowRecurse)&&i.add(e)}))};if("clear"===t)l.forEach(c);else if("length"===n&&E(e))l.forEach(((e,t)=>{("length"===t||t>=r)&&c(e)}));else switch(void 0!==n&&c(l.get(n)),t){case"add":E(e)?I(n)&&c(l.get("length")):(c(l.get(Q)),k(e)&&c(l.get(Y)));break;case"delete":E(e)||(c(l.get(Q)),k(e)&&c(l.get(Y)));break;case"set":k(e)&&c(l.get(Q))}i.forEach((e=>{e.options.scheduler?e.options.scheduler(e):e()}))}const ae=e("__proto__,__v_isRef,__isVue"),ue=new Set(Object.getOwnPropertyNames(Symbol).map((e=>Symbol[e])).filter(A)),fe=ge(),pe=ge(!1,!0),de=ge(!0),he=ge(!0,!0),me={};function ge(e=!1,t=!1){return function(n,r,o){if("__v_isReactive"===r)return!e;if("__v_isReadonly"===r)return e;if("__v_raw"===r&&o===(e?t?Ke:We:t?ze:qe).get(n))return n;const s=E(n);if(!e&&s&&x(me,r))return Reflect.get(me,r,o);const l=Reflect.get(n,r,o);if(A(r)?ue.has(r):ae(r))return l;if(e||ie(n,0,r),t)return l;if(nt(l)){return!s||!I(r)?l.value:l}return P(l)?e?Xe(l):He(l):l}}["includes","indexOf","lastIndexOf"].forEach((e=>{const t=Array.prototype[e];me[e]=function(...e){const n=et(this);for(let t=0,o=this.length;t{const t=Array.prototype[e];me[e]=function(...e){se();const n=t.apply(this,e);return le(),n}}));function ve(e=!1){return function(t,n,r,o){let s=t[n];if(!e&&(r=et(r),s=et(s),!E(t)&&nt(s)&&!nt(r)))return s.value=r,!0;const l=E(t)&&I(n)?Number(n)!0,deleteProperty:(e,t)=>!0},_e=b({},ye,{get:pe,set:ve(!0)});b({},be,{get:he});const we=e=>P(e)?He(e):e,xe=e=>P(e)?Xe(e):e,Ee=e=>e,ke=e=>Reflect.getPrototypeOf(e);function Se(e,t,n=!1,r=!1){const o=et(e=e.__v_raw),s=et(t);t!==s&&!n&&ie(o,0,t),!n&&ie(o,0,s);const{has:l}=ke(o),i=r?Ee:n?xe:we;return l.call(o,t)?i(e.get(t)):l.call(o,s)?i(e.get(s)):void 0}function Oe(e,t=!1){const n=this.__v_raw,r=et(n),o=et(e);return e!==o&&!t&&ie(r,0,e),!t&&ie(r,0,o),e===o?n.has(e):n.has(e)||n.has(o)}function Ce(e,t=!1){return e=e.__v_raw,!t&&ie(et(e),0,Q),Reflect.get(e,"size",e)}function Re(e){e=et(e);const t=et(this);return ke(t).has.call(t,e)||(t.add(e),ce(t,"add",e,e)),this}function Ae(e,t){t=et(t);const n=et(this),{has:r,get:o}=ke(n);let s=r.call(n,e);s||(e=et(e),s=r.call(n,e));const l=o.call(n,e);return n.set(e,t),s?z(t,l)&&ce(n,"set",e,t):ce(n,"add",e,t),this}function Pe(e){const t=et(this),{has:n,get:r}=ke(t);let o=n.call(t,e);o||(e=et(e),o=n.call(t,e)),r&&r.call(t,e);const s=t.delete(e);return o&&ce(t,"delete",e,void 0),s}function Fe(){const e=et(this),t=0!==e.size,n=e.clear();return t&&ce(e,"clear",void 0,void 0),n}function je(e,t){return function(n,r){const o=this,s=o.__v_raw,l=et(s),i=t?Ee:e?xe:we;return!e&&ie(l,0,Q),s.forEach(((e,t)=>n.call(r,i(e),i(t),o)))}}function $e(e,t,n){return function(...r){const o=this.__v_raw,s=et(o),l=k(s),i="entries"===e||e===Symbol.iterator&&l,c="keys"===e&&l,a=o[e](...r),u=n?Ee:t?xe:we;return!t&&ie(s,0,c?Y:Q),{next(){const{value:e,done:t}=a.next();return t?{value:e,done:t}:{value:i?[u(e[0]),u(e[1])]:u(e),done:t}},[Symbol.iterator](){return this}}}}function Me(e){return function(...t){return"delete"!==e&&this}}const Ie={get(e){return Se(this,e)},get size(){return Ce(this)},has:Oe,add:Re,set:Ae,delete:Pe,clear:Fe,forEach:je(!1,!1)},Te={get(e){return Se(this,e,!1,!0)},get size(){return Ce(this)},has:Oe,add:Re,set:Ae,delete:Pe,clear:Fe,forEach:je(!1,!0)},Ve={get(e){return Se(this,e,!0)},get size(){return Ce(this,!0)},has(e){return Oe.call(this,e,!0)},add:Me("add"),set:Me("set"),delete:Me("delete"),clear:Me("clear"),forEach:je(!0,!1)},Ue={get(e){return Se(this,e,!0,!0)},get size(){return Ce(this,!0)},has(e){return Oe.call(this,e,!0)},add:Me("add"),set:Me("set"),delete:Me("delete"),clear:Me("clear"),forEach:je(!0,!0)};function Ne(e,t){const n=t?e?Ue:Te:e?Ve:Ie;return(t,r,o)=>"__v_isReactive"===r?!e:"__v_isReadonly"===r?e:"__v_raw"===r?t:Reflect.get(x(n,r)&&r in t?n:t,r,o)}["keys","values","entries",Symbol.iterator].forEach((e=>{Ie[e]=$e(e,!1,!1),Ve[e]=$e(e,!0,!1),Te[e]=$e(e,!1,!0),Ue[e]=$e(e,!0,!0)}));const Le={get:Ne(!1,!1)},Be={get:Ne(!1,!0)},De={get:Ne(!0,!1)},qe=new WeakMap,ze=new WeakMap,We=new WeakMap,Ke=new WeakMap;function Ge(e){return e.__v_skip||!Object.isExtensible(e)?0:function(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}((e=>$(e).slice(8,-1))(e))}function He(e){return e&&e.__v_isReadonly?e:Je(e,!1,ye,Le,qe)}function Xe(e){return Je(e,!0,be,De,We)}function Je(e,t,n,r,o){if(!P(e))return e;if(e.__v_raw&&(!t||!e.__v_isReactive))return e;const s=o.get(e);if(s)return s;const l=Ge(e);if(0===l)return e;const i=new Proxy(e,2===l?r:n);return o.set(e,i),i}function Qe(e){return Ye(e)?Qe(e.__v_raw):!(!e||!e.__v_isReactive)}function Ye(e){return!(!e||!e.__v_isReadonly)}function Ze(e){return Qe(e)||Ye(e)}function et(e){return e&&et(e.__v_raw)||e}const tt=e=>P(e)?He(e):e;function nt(e){return Boolean(e&&!0===e.__v_isRef)}class rt{constructor(e,t=!1){this._rawValue=e,this._shallow=t,this.__v_isRef=!0,this._value=t?e:tt(e)}get value(){return ie(et(this),0,"value"),this._value}set value(e){z(et(e),this._rawValue)&&(this._rawValue=e,this._value=this._shallow?e:tt(e),ce(et(this),"set","value",e))}}function ot(e,t=!1){return nt(e)?e:new rt(e,t)}function st(e){return nt(e)?e.value:e}const lt={get:(e,t,n)=>st(Reflect.get(e,t,n)),set:(e,t,n,r)=>{const o=e[t];return nt(o)&&!nt(n)?(o.value=n,!0):Reflect.set(e,t,n,r)}};function it(e){return Qe(e)?e:new Proxy(e,lt)}class ct{constructor(e,t){this._object=e,this._key=t,this.__v_isRef=!0}get value(){return this._object[this._key]}set value(e){this._object[this._key]=e}}class at{constructor(e,t,n){this._setter=t,this._dirty=!0,this.__v_isRef=!0,this.effect=Z(e,{lazy:!0,scheduler:()=>{this._dirty||(this._dirty=!0,ce(et(this),"set","value"))}}),this.__v_isReadonly=n}get value(){const e=et(this);return e._dirty&&(e._value=this.effect(),e._dirty=!1),ie(e,0,"value"),e._value}set value(e){this._setter(e)}}function ut(e,t,n,r){let o;try{o=r?e(...r):e()}catch(s){pt(s,t,n)}return o}function ft(e,t,n,r){if(C(e)){const o=ut(e,t,n,r);return o&&F(o)&&o.catch((e=>{pt(e,t,n)})),o}const o=[];for(let s=0;s>>1;jt(mt[e])-1?mt.splice(t,0,e):mt.push(e),Rt()}}function Rt(){dt||ht||(ht=!0,kt=Et.then($t))}function At(e,t,n,r){E(e)?n.push(...e):t&&t.includes(e,e.allowRecurse?r+1:r)||n.push(e),Rt()}function Pt(e,t=null){if(vt.length){for(St=t,yt=[...new Set(vt)],vt.length=0,bt=0;btjt(e)-jt(t))),xt=0;xtnull==e.id?1/0:e.id;function $t(e){ht=!1,dt=!0,Pt(e),mt.sort(((e,t)=>jt(e)-jt(t)));try{for(gt=0;gte.trim())):t&&(o=n.map(G))}let i,c=r[i=q(t)]||r[i=q(N(t))];!c&&s&&(c=r[i=q(B(t))]),c&&ft(c,e,6,o);const a=r[i+"Once"];if(a){if(e.emitted){if(e.emitted[i])return}else(e.emitted={})[i]=!0;ft(a,e,6,o)}}function It(e,t,n=!1){if(!t.deopt&&void 0!==e.__emits)return e.__emits;const r=e.emits;let o={},s=!1;if(!C(e)){const r=e=>{const n=It(e,t,!0);n&&(s=!0,b(o,n))};!n&&t.mixins.length&&t.mixins.forEach(r),e.extends&&r(e.extends),e.mixins&&e.mixins.forEach(r)}return r||s?(E(r)?r.forEach((e=>o[e]=null)):b(o,r),e.__emits=o):e.__emits=null}function Tt(e,t){return!(!e||!v(t))&&(t=t.slice(2).replace(/Once$/,""),x(e,t[0].toLowerCase()+t.slice(1))||x(e,B(t))||x(e,t))}let Vt=0;const Ut=e=>Vt+=e;function Nt(e,t,n={},r,o){let s=e[t];Vt++,Qn();const l=s&&Lt(s(n)),i=Zn(Wn,{key:n.key||`_${t}`},l||(r?r():[]),l&&1===e._?64:-2);return!o&&i.scopeId&&(i.slotScopeIds=[i.scopeId+"-s"]),Vt--,i}function Lt(e){return e.some((e=>!er(e)||e.type!==Gn&&!(e.type===Wn&&!Lt(e.children))))?e:null}let Bt=null,Dt=null;function qt(e){const t=Bt;return Bt=e,Dt=e&&e.type.__scopeId||null,t}const zt=e=>Wt;function Wt(e,t=Bt){if(!t)return e;const n=(...n)=>{Vt||Qn(!0);const r=qt(t),o=e(...n);return qt(r),Vt||Yn(),o};return n._c=!0,n}function Kt(e){const{type:t,vnode:n,proxy:r,withProxy:o,props:s,propsOptions:[l],slots:i,attrs:c,emit:a,render:u,renderCache:f,data:p,setupState:d,ctx:h}=e;let m;const g=qt(e);try{let e;if(4&n.shapeFlag){const t=o||r;m=ar(u.call(t,t,f,s,d,p,h)),e=c}else{const n=t;0,m=ar(n.length>1?n(s,{attrs:c,slots:i,emit:a}):n(s,null)),e=t.props?c:Ht(c)}let g=m;if(!1!==t.inheritAttrs&&e){const t=Object.keys(e),{shapeFlag:n}=g;t.length&&(1&n||6&n)&&(l&&t.some(y)&&(e=Xt(e,l)),g=lr(g,e))}n.dirs&&(g.dirs=g.dirs?g.dirs.concat(n.dirs):n.dirs),n.transition&&(g.transition=n.transition),m=g}catch(v){Xn.length=0,pt(v,e,1),m=sr(Gn)}return qt(g),m}function Gt(e){let t;for(let n=0;n{let t;for(const n in e)("class"===n||"style"===n||v(n))&&((t||(t={}))[n]=e[n]);return t},Xt=(e,t)=>{const n={};for(const r in e)y(r)&&r.slice(9)in t||(n[r]=e[r]);return n};function Jt(e,t,n){const r=Object.keys(t);if(r.length!==Object.keys(e).length)return!0;for(let o=0;o{l=!0;const[n,r]=tn(e,t,!0);b(o,n),r&&s.push(...r)};!n&&t.mixins.length&&t.mixins.forEach(r),e.extends&&r(e.extends),e.mixins&&e.mixins.forEach(r)}if(!r&&!l)return e.__props=d;if(E(r))for(let i=0;i-1,n[1]=r<0||t-1||x(n,"default"))&&s.push(e)}}}return e.__props=[o,s]}function nn(e){return"$"!==e[0]}function rn(e){const t=e&&e.toString().match(/^\s*function (\w+)/);return t?t[1]:""}function on(e,t){return rn(e)===rn(t)}function sn(e,t){return E(t)?t.findIndex((t=>on(t,e))):C(t)&&on(t,e)?0:-1}function ln(e,t,n=Rr,r=!1){if(n){const o=n[e]||(n[e]=[]),s=t.__weh||(t.__weh=(...r)=>{if(n.isUnmounted)return;se(),Ar(n);const o=ft(t,n,e,r);return Ar(null),le(),o});return r?o.unshift(s):o.push(s),s}}const cn=e=>(t,n=Rr)=>!Fr&&ln(e,t,n),an=cn("bm"),un=cn("m"),fn=cn("bu"),pn=cn("u"),dn=cn("bum"),hn=cn("um"),mn=cn("rtg"),gn=cn("rtc"),vn={};function yn(e,t,n){return bn(e,t,n)}function bn(e,t,{immediate:n,deep:r,flush:o,onTrack:s,onTrigger:l}=p,i=Rr){let c,a,u=!1;if(nt(e)?(c=()=>e.value,u=!!e._shallow):Qe(e)?(c=()=>e,r=!0):c=E(e)?()=>e.map((e=>nt(e)?e.value:Qe(e)?wn(e):C(e)?ut(e,i,2,[i&&i.proxy]):void 0)):C(e)?t?()=>ut(e,i,2,[i&&i.proxy]):()=>{if(!i||!i.isUnmounted)return a&&a(),ft(e,i,3,[f])}:h,t&&r){const e=c;c=()=>wn(e())}let f=e=>{a=v.options.onStop=()=>{ut(e,i,4)}},d=E(e)?[]:vn;const m=()=>{if(v.active)if(t){const e=v();(r||u||z(e,d))&&(a&&a(),ft(t,i,3,[e,d===vn?void 0:d,f]),d=e)}else v()};let g;m.allowRecurse=!!t,g="sync"===o?m:"post"===o?()=>Vn(m,i&&i.suspense):()=>{!i||i.isMounted?function(e){At(e,yt,vt,bt)}(m):m()};const v=Z(c,{lazy:!0,onTrack:s,onTrigger:l,scheduler:g});return Mr(v,i),t?n?m():d=v():"post"===o?Vn(v,i&&i.suspense):v(),()=>{ee(v),i&&_(i.effects,v)}}function _n(e,t,n){const r=this.proxy;return bn(R(e)?()=>r[e]:e.bind(r),t.bind(r),n,this)}function wn(e,t=new Set){if(!P(e)||t.has(e))return e;if(t.add(e),nt(e))wn(e.value,t);else if(E(e))for(let n=0;n{wn(e,t)}));else for(const n in e)wn(e[n],t);return e}const xn=e=>e.type.__isKeepAlive;function En(e,t,n=Rr){const r=e.__wdc||(e.__wdc=()=>{let t=n;for(;t;){if(t.isDeactivated)return;t=t.parent}e()});if(ln(t,r,n),n){let e=n.parent;for(;e&&e.parent;)xn(e.parent.vnode)&&kn(r,t,n,e),e=e.parent}}function kn(e,t,n,r){const o=ln(t,e,r,!0);hn((()=>{_(r[t],o)}),n)}const Sn=e=>"_"===e[0]||"$stable"===e,On=e=>E(e)?e.map(ar):[ar(e)],Cn=(e,t,n)=>Wt((e=>On(t(e))),n),Rn=(e,t)=>{const n=e._ctx;for(const r in e){if(Sn(r))continue;const o=e[r];if(C(o))t[r]=Cn(0,o,n);else if(null!=o){const e=On(o);t[r]=()=>e}}},An=(e,t)=>{const n=On(t);e.slots.default=()=>n};function Pn(e,t){if(null===Bt)return e;const n=Bt.proxy,r=e.dirs||(e.dirs=[]);for(let o=0;o(s.has(e)||(e&&C(e.install)?(s.add(e),e.install(i,...t)):C(e)&&(s.add(e),e(i,...t))),i),mixin:e=>(o.mixins.includes(e)||(o.mixins.push(e),(e.props||e.emits)&&(o.deopt=!0)),i),component:(e,t)=>t?(o.components[e]=t,i):o.components[e],directive:(e,t)=>t?(o.directives[e]=t,i):o.directives[e],mount(s,c,a){if(!l){const u=sr(n,r);return u.appContext=o,c&&t?t(u,s):e(u,s,a),l=!0,i._container=s,s.__vue_app__=i,u.component.proxy}},unmount(){l&&(e(null,i._container),delete i._container.__vue_app__)},provide:(e,t)=>(o.provides[e]=t,i)};return i}}function In(e){return C(e)?{setup:e,name:e.name}:e}const Tn={scheduler:Ct,allowRecurse:!0},Vn=function(e,t){t&&t.pendingBranch?E(e)?t.effects.push(...e):t.effects.push(e):At(e,wt,_t,xt)},Un=(e,t,n,r)=>{if(E(e))return void e.forEach(((e,o)=>Un(e,t&&(E(t)?t[o]:t),n,r)));let o;if(r){if(r.type.__asyncLoader)return;o=4&r.shapeFlag?r.component.exposed||r.component.proxy:r.el}else o=null;const{i:s,r:l}=e,i=t&&t.r,c=s.refs===p?s.refs={}:s.refs,a=s.setupState;if(null!=i&&i!==l&&(R(i)?(c[i]=null,x(a,i)&&(a[i]=null)):nt(i)&&(i.value=null)),R(l)){const e=()=>{c[l]=o,x(a,l)&&(a[l]=o)};o?(e.id=-1,Vn(e,n)):e()}else if(nt(l)){const e=()=>{l.value=o};o?(e.id=-1,Vn(e,n)):e()}else C(l)&&ut(l,s,12,[o,c])};function Nn(e){return function(e,t){const{insert:n,remove:r,patchProp:o,forcePatchProp:s,createElement:l,createText:i,createComment:c,setText:a,setElementText:u,parentNode:f,nextSibling:m,setScopeId:g=h,cloneNode:v,insertStaticContent:y}=e,_=(e,t,n,r=null,o=null,s=null,l=!1,i=null,c=!1)=>{e&&!tr(e,t)&&(r=re(e),J(e,o,s,!0),e=null),-2===t.patchFlag&&(c=!1,t.dynamicChildren=null);const{type:a,ref:u,shapeFlag:f}=t;switch(a){case Kn:w(e,t,n,r);break;case Gn:E(e,t,n,r);break;case Hn:null==e&&k(t,n,r,l);break;case Wn:I(e,t,n,r,o,s,l,i,c);break;default:1&f?C(e,t,n,r,o,s,l,i,c):6&f?V(e,t,n,r,o,s,l,i,c):(64&f||128&f)&&a.process(e,t,n,r,o,s,l,i,c,ie)}null!=u&&o&&Un(u,e&&e.ref,s,t)},w=(e,t,r,o)=>{if(null==e)n(t.el=i(t.children),r,o);else{const n=t.el=e.el;t.children!==e.children&&a(n,t.children)}},E=(e,t,r,o)=>{null==e?n(t.el=c(t.children||""),r,o):t.el=e.el},k=(e,t,n,r)=>{[e.el,e.anchor]=y(e.children,t,n,r)},S=({el:e,anchor:t},r,o)=>{let s;for(;e&&e!==t;)s=m(e),n(e,r,o),e=s;n(t,r,o)},O=({el:e,anchor:t})=>{let n;for(;e&&e!==t;)n=m(e),r(e),e=n;r(t)},C=(e,t,n,r,o,s,l,i,c)=>{l=l||"svg"===t.type,null==e?R(t,n,r,o,s,l,i,c):j(e,t,o,s,l,i,c)},R=(e,t,r,s,i,c,a,f)=>{let p,d;const{type:h,props:m,shapeFlag:g,transition:y,patchFlag:b,dirs:_}=e;if(e.el&&void 0!==v&&-1===b)p=e.el=v(e.el);else{if(p=e.el=l(e.type,c,m&&m.is,m),8&g?u(p,e.children):16&g&&P(e.children,p,null,s,i,c&&"foreignObject"!==h,a,f||!!e.dynamicChildren),_&&Fn(e,null,s,"created"),m){for(const t in m)T(t)||o(p,t,null,m[t],c,e.children,s,i,ne);(d=m.onVnodeBeforeMount)&&Ln(d,s,e)}A(p,e,e.scopeId,a,s)}_&&Fn(e,null,s,"beforeMount");const w=(!i||i&&!i.pendingBranch)&&y&&!y.persisted;w&&y.beforeEnter(p),n(p,t,r),((d=m&&m.onVnodeMounted)||w||_)&&Vn((()=>{d&&Ln(d,s,e),w&&y.enter(p),_&&Fn(e,null,s,"mounted")}),i)},A=(e,t,n,r,o)=>{if(n&&g(e,n),r)for(let s=0;s{for(let a=c;a{const a=t.el=e.el;let{patchFlag:f,dynamicChildren:d,dirs:h}=t;f|=16&e.patchFlag;const m=e.props||p,g=t.props||p;let v;if((v=g.onVnodeBeforeUpdate)&&Ln(v,n,t,e),h&&Fn(t,e,n,"beforeUpdate"),f>0){if(16&f)M(a,t,m,g,n,r,l);else if(2&f&&m.class!==g.class&&o(a,"class",null,g.class,l),4&f&&o(a,"style",m.style,g.style,l),8&f){const i=t.dynamicProps;for(let t=0;t{v&&Ln(v,n,t,e),h&&Fn(t,e,n,"updated")}),r)},$=(e,t,n,r,o,s,l)=>{for(let i=0;i{if(n!==r){for(const a in r){if(T(a))continue;const u=r[a],f=n[a];(u!==f||s&&s(e,a))&&o(e,a,f,u,c,t.children,l,i,ne)}if(n!==p)for(const s in n)T(s)||s in r||o(e,s,n[s],null,c,t.children,l,i,ne)}},I=(e,t,r,o,s,l,c,a,u)=>{const f=t.el=e?e.el:i(""),p=t.anchor=e?e.anchor:i("");let{patchFlag:d,dynamicChildren:h,slotScopeIds:m}=t;d>0&&(u=!0),m&&(a=a?a.concat(m):m),null==e?(n(f,r,o),n(p,r,o),P(t.children,r,p,s,l,c,a,u)):d>0&&64&d&&h&&e.dynamicChildren?($(e.dynamicChildren,h,r,s,l,c,a),(null!=t.key||s&&t===s.subTree)&&Bn(e,t,!0)):z(e,t,r,p,s,l,c,a,u)},V=(e,t,n,r,o,s,l,i,c)=>{t.slotScopeIds=i,null==e?512&t.shapeFlag?o.ctx.activate(t,n,r,l,c):U(t,n,r,o,s,l,c):L(e,t,c)},U=(e,t,n,r,o,s,l)=>{const i=e.component=function(e,t,n){const r=e.type,o=(t?t.appContext:e.appContext)||Or,s={uid:Cr++,vnode:e,type:r,parent:t,appContext:o,root:null,next:null,subTree:null,update:null,render:null,proxy:null,exposed:null,withProxy:null,effects:null,provides:t?t.provides:Object.create(o.provides),accessCache:null,renderCache:[],components:null,directives:null,propsOptions:tn(r,o),emitsOptions:It(r,o),emit:null,emitted:null,propsDefaults:p,ctx:p,data:p,props:p,attrs:p,slots:p,refs:p,setupState:p,setupContext:null,suspense:n,suspenseId:n?n.pendingId:0,asyncDep:null,asyncResolved:!1,isMounted:!1,isUnmounted:!1,isDeactivated:!1,bc:null,c:null,bm:null,m:null,bu:null,u:null,um:null,bum:null,da:null,a:null,rtg:null,rtc:null,ec:null};return s.ctx={_:s},s.root=t?t.root:s,s.emit=Mt.bind(null,s),s}(e,r,o);if(xn(e)&&(i.ctx.renderer=ie),function(e,t=!1){Fr=t;const{props:n,children:r}=e.vnode,o=Pr(e);Yt(e,n,o,t),((e,t)=>{if(32&e.vnode.shapeFlag){const n=t._;n?(e.slots=t,K(t,"_",n)):Rn(t,e.slots={})}else e.slots={},t&&An(e,t);K(e.slots,nr,1)})(e,r);const s=o?function(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,kr);const{setup:r}=n;if(r){const n=e.setupContext=r.length>1?function(e){const t=t=>{e.exposed=it(t)};return{attrs:e.attrs,slots:e.slots,emit:e.emit,expose:t}}(e):null;Rr=e,se();const o=ut(r,e,0,[e.props,n]);if(le(),Rr=null,F(o)){if(t)return o.then((t=>{jr(e,t)})).catch((t=>{pt(t,e,0)}));e.asyncDep=o}else jr(e,o)}else $r(e)}(e,t):void 0;Fr=!1}(i),i.asyncDep){if(o&&o.registerDep(i,D),!e.el){const e=i.subTree=sr(Gn);E(null,e,t,n)}}else D(i,e,t,n,o,s,l)},L=(e,t,n)=>{const r=t.component=e.component;if(function(e,t,n){const{props:r,children:o,component:s}=e,{props:l,children:i,patchFlag:c}=t,a=s.emitsOptions;if(t.dirs||t.transition)return!0;if(!(n&&c>=0))return!(!o&&!i||i&&i.$stable)||r!==l&&(r?!l||Jt(r,l,a):!!l);if(1024&c)return!0;if(16&c)return r?Jt(r,l,a):!!l;if(8&c){const e=t.dynamicProps;for(let t=0;tgt&&mt.splice(t,1)}(r.update),r.update()}else t.component=e.component,t.el=e.el,r.vnode=t},D=(e,t,n,r,o,s,l)=>{e.update=Z((function(){if(e.isMounted){let t,{next:n,bu:r,u:i,parent:c,vnode:a}=e,u=n;n?(n.el=a.el,q(e,n,l)):n=a,r&&W(r),(t=n.props&&n.props.onVnodeBeforeUpdate)&&Ln(t,c,n,a);const p=Kt(e),d=e.subTree;e.subTree=p,_(d,p,f(d.el),re(d),e,o,s),n.el=p.el,null===u&&function({vnode:e,parent:t},n){for(;t&&t.subTree===e;)(e=t.vnode).el=n,t=t.parent}(e,p.el),i&&Vn(i,o),(t=n.props&&n.props.onVnodeUpdated)&&Vn((()=>{Ln(t,c,n,a)}),o)}else{let l;const{el:i,props:c}=t,{bm:a,m:u,parent:f}=e;a&&W(a),(l=c&&c.onVnodeBeforeMount)&&Ln(l,f,t);const p=e.subTree=Kt(e);if(i&&ue?ue(t.el,p,e,o,null):(_(null,p,n,r,e,o,s),t.el=p.el),u&&Vn(u,o),l=c&&c.onVnodeMounted){const e=t;Vn((()=>{Ln(l,f,e)}),o)}const{a:d}=e;d&&256&t.shapeFlag&&Vn(d,o),e.isMounted=!0,t=n=r=null}}),Tn)},q=(e,t,n)=>{t.component=e;const r=e.vnode.props;e.vnode=t,e.next=null,function(e,t,n,r){const{props:o,attrs:s,vnode:{patchFlag:l}}=e,i=et(o),[c]=e.propsOptions;if(!(r||l>0)||16&l){let r;Zt(e,t,o,s);for(const s in i)t&&(x(t,s)||(r=B(s))!==s&&x(t,r))||(c?!n||void 0===n[s]&&void 0===n[r]||(o[s]=en(c,t||p,s,void 0,e)):delete o[s]);if(s!==i)for(const e in s)t&&x(t,e)||delete s[e]}else if(8&l){const n=e.vnode.dynamicProps;for(let r=0;r{const{vnode:r,slots:o}=e;let s=!0,l=p;if(32&r.shapeFlag){const e=t._;e?n&&1===e?s=!1:(b(o,t),n||1!==e||delete o._):(s=!t.$stable,Rn(t,o)),l=t}else t&&(An(e,t),l={default:1});if(s)for(const i in o)Sn(i)||i in l||delete o[i]})(e,t.children,n),se(),Pt(void 0,e.update),le()},z=(e,t,n,r,o,s,l,i,c=!1)=>{const a=e&&e.children,f=e?e.shapeFlag:0,p=t.children,{patchFlag:d,shapeFlag:h}=t;if(d>0){if(128&d)return void H(a,p,n,r,o,s,l,i,c);if(256&d)return void G(a,p,n,r,o,s,l,i,c)}8&h?(16&f&&ne(a,o,s),p!==a&&u(n,p)):16&f?16&h?H(a,p,n,r,o,s,l,i,c):ne(a,o,s,!0):(8&f&&u(n,""),16&h&&P(p,n,r,o,s,l,i,c))},G=(e,t,n,r,o,s,l,i,c)=>{t=t||d;const a=(e=e||d).length,u=t.length,f=Math.min(a,u);let p;for(p=0;pu?ne(e,o,s,!0,!1,f):P(t,n,r,o,s,l,i,c,f)},H=(e,t,n,r,o,s,l,i,c)=>{let a=0;const u=t.length;let f=e.length-1,p=u-1;for(;a<=f&&a<=p;){const r=e[a],u=t[a]=c?ur(t[a]):ar(t[a]);if(!tr(r,u))break;_(r,u,n,null,o,s,l,i,c),a++}for(;a<=f&&a<=p;){const r=e[f],a=t[p]=c?ur(t[p]):ar(t[p]);if(!tr(r,a))break;_(r,a,n,null,o,s,l,i,c),f--,p--}if(a>f){if(a<=p){const e=p+1,f=ep)for(;a<=f;)J(e[a],o,s,!0),a++;else{const h=a,m=a,g=new Map;for(a=m;a<=p;a++){const e=t[a]=c?ur(t[a]):ar(t[a]);null!=e.key&&g.set(e.key,a)}let v,y=0;const b=p-m+1;let w=!1,x=0;const E=new Array(b);for(a=0;a=b){J(r,o,s,!0);continue}let u;if(null!=r.key)u=g.get(r.key);else for(v=m;v<=p;v++)if(0===E[v-m]&&tr(r,t[v])){u=v;break}void 0===u?J(r,o,s,!0):(E[u-m]=a+1,u>=x?x=u:w=!0,_(r,t[u],n,null,o,s,l,i,c),y++)}const k=w?function(e){const t=e.slice(),n=[0];let r,o,s,l,i;const c=e.length;for(r=0;r0&&(t[r]=n[s-1]),n[s]=r)}}s=n.length,l=n[s-1];for(;s-- >0;)n[s]=l,l=t[l];return n}(E):d;for(v=k.length-1,a=b-1;a>=0;a--){const e=m+a,f=t[e],p=e+1{const{el:l,type:i,transition:c,children:a,shapeFlag:u}=e;if(6&u)return void X(e.component.subTree,t,r,o);if(128&u)return void e.suspense.move(t,r,o);if(64&u)return void i.move(e,t,r,ie);if(i===Wn){n(l,t,r);for(let e=0;ec.enter(l)),s);else{const{leave:e,delayLeave:o,afterLeave:s}=c,i=()=>n(l,t,r),a=()=>{e(l,(()=>{i(),s&&s()}))};o?o(l,i,a):a()}else n(l,t,r)},J=(e,t,n,r=!1,o=!1)=>{const{type:s,props:l,ref:i,children:c,dynamicChildren:a,shapeFlag:u,patchFlag:f,dirs:p}=e;if(null!=i&&Un(i,null,n,null),256&u)return void t.ctx.deactivate(e);const d=1&u&&p;let h;if((h=l&&l.onVnodeBeforeUnmount)&&Ln(h,t,e),6&u)te(e.component,n,r);else{if(128&u)return void e.suspense.unmount(n,r);d&&Fn(e,null,t,"beforeUnmount"),64&u?e.type.remove(e,t,n,o,ie,r):a&&(s!==Wn||f>0&&64&f)?ne(a,t,n,!1,!0):(s===Wn&&(128&f||256&f)||!o&&16&u)&&ne(c,t,n),r&&Q(e)}((h=l&&l.onVnodeUnmounted)||d)&&Vn((()=>{h&&Ln(h,t,e),d&&Fn(e,null,t,"unmounted")}),n)},Q=e=>{const{type:t,el:n,anchor:o,transition:s}=e;if(t===Wn)return void Y(n,o);if(t===Hn)return void O(e);const l=()=>{r(n),s&&!s.persisted&&s.afterLeave&&s.afterLeave()};if(1&e.shapeFlag&&s&&!s.persisted){const{leave:t,delayLeave:r}=s,o=()=>t(n,l);r?r(e.el,l,o):o()}else l()},Y=(e,t)=>{let n;for(;e!==t;)n=m(e),r(e),e=n;r(t)},te=(e,t,n)=>{const{bum:r,effects:o,update:s,subTree:l,um:i}=e;if(r&&W(r),o)for(let c=0;c{e.isUnmounted=!0}),t),t&&t.pendingBranch&&!t.isUnmounted&&e.asyncDep&&!e.asyncResolved&&e.suspenseId===t.pendingId&&(t.deps--,0===t.deps&&t.resolve())},ne=(e,t,n,r=!1,o=!1,s=0)=>{for(let l=s;l6&e.shapeFlag?re(e.component.subTree):128&e.shapeFlag?e.suspense.next():m(e.anchor||e.el),oe=(e,t,n)=>{null==e?t._vnode&&J(t._vnode,null,null,!0):_(t._vnode||null,e,t,null,null,null,n),Ft(),t._vnode=e},ie={p:_,um:J,m:X,r:Q,mt:U,mc:P,pc:z,pbc:$,n:re,o:e};let ae,ue;t&&([ae,ue]=t(ie));return{render:oe,hydrate:ae,createApp:Mn(oe,ae)}}(e)}function Ln(e,t,n,r=null){ft(e,t,7,[n,r])}function Bn(e,t,n=!1){const r=e.children,o=t.children;if(E(r)&&E(o))for(let s=0;snull!=e?e:null,or=({ref:e})=>null!=e?R(e)||nt(e)||C(e)?{i:Bt,r:e}:e:null,sr=function(e,t=null,n=null,o=0,s=null,l=!1){e&&e!==qn||(e=Gn);if(er(e)){const r=lr(e,t,!0);return n&&fr(r,n),r}c=e,C(c)&&"__vccOpts"in c&&(e=e.__vccOpts);var c;if(t){(Ze(t)||nr in t)&&(t=b({},t));let{class:e,style:n}=t;e&&!R(e)&&(t.class=i(e)),P(n)&&(Ze(n)&&!E(n)&&(n=b({},n)),t.style=r(n))}const a=R(e)?1:(e=>e.__isSuspense)(e)?128:(e=>e.__isTeleport)(e)?64:P(e)?4:C(e)?2:0,u={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&rr(t),ref:t&&or(t),scopeId:Dt,slotScopeIds:null,children:null,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:a,patchFlag:o,dynamicProps:s,dynamicChildren:null,appContext:null};if(fr(u,n),128&a){const{content:e,fallback:t}=function(e){const{shapeFlag:t,children:n}=e;let r,o;return 32&t?(r=Qt(n.default),o=Qt(n.fallback)):(r=Qt(n),o=ar(null)),{content:r,fallback:o}}(u);u.ssContent=e,u.ssFallback=t}!l&&Jn&&(o>0||6&a)&&32!==o&&Jn.push(u);return u};function lr(e,t,n=!1){const{props:o,ref:s,patchFlag:l,children:c}=e,a=t?function(...e){const t=b({},e[0]);for(let n=1;n1)return n&&C(t)?t():t}}let hr=!0;function mr(e,t,n=[],r=[],o=[],s=!1){const{mixins:l,extends:i,data:c,computed:a,methods:u,watch:f,provide:d,inject:m,components:g,directives:v,beforeMount:y,mounted:_,beforeUpdate:w,updated:x,activated:k,deactivated:S,beforeDestroy:O,beforeUnmount:R,destroyed:A,unmounted:F,render:j,renderTracked:$,renderTriggered:M,errorCaptured:I,expose:T}=t,V=e.proxy,U=e.ctx,N=e.appContext.mixins;if(s&&j&&e.render===h&&(e.render=j),s||(hr=!1,gr("beforeCreate","bc",t,e,N),hr=!0,yr(e,N,n,r,o)),i&&mr(e,i,n,r,o,!0),l&&yr(e,l,n,r,o),m)if(E(m))for(let p=0;pbr(e,t,V))),c&&br(e,c,V)),a)for(const p in a){const e=a[p],t=Tr({get:C(e)?e.bind(V,V):C(e.get)?e.get.bind(V,V):h,set:!C(e)&&C(e.set)?e.set.bind(V):h});Object.defineProperty(U,p,{enumerable:!0,configurable:!0,get:()=>t.value,set:e=>t.value=e})}var L;if(f&&r.push(f),!s&&r.length&&r.forEach((e=>{for(const t in e)_r(e[t],U,V,t)})),d&&o.push(d),!s&&o.length&&o.forEach((e=>{const t=C(e)?e.call(V):e;Reflect.ownKeys(t).forEach((e=>{pr(e,t[e])}))})),s&&(g&&b(e.components||(e.components=b({},e.type.components)),g),v&&b(e.directives||(e.directives=b({},e.type.directives)),v)),s||gr("created","c",t,e,N),y&&an(y.bind(V)),_&&un(_.bind(V)),w&&fn(w.bind(V)),x&&pn(x.bind(V)),k&&En(k.bind(V),"a",L),S&&function(e,t){En(e,"da",t)}(S.bind(V)),I&&((e,t=Rr)=>{ln("ec",e,t)})(I.bind(V)),$&&gn($.bind(V)),M&&mn(M.bind(V)),R&&dn(R.bind(V)),F&&hn(F.bind(V)),E(T)&&!s)if(T.length){const t=e.exposed||(e.exposed=it({}));T.forEach((e=>{t[e]=function(e,t){return nt(e[t])?e[t]:new ct(e,t)}(V,e)}))}else e.exposed||(e.exposed=p)}function gr(e,t,n,r,o){for(let s=0;s{let t=e;for(let e=0;en[r];if(R(e)){const n=t[e];C(n)&&yn(o,n)}else if(C(e))yn(o,e.bind(n));else if(P(e))if(E(e))e.forEach((e=>_r(e,t,n,r)));else{const r=C(e.handler)?e.handler.bind(n):t[e.handler];C(r)&&yn(o,r,e)}}function wr(e,t,n){const r=n.appContext.config.optionMergeStrategies,{mixins:o,extends:s}=t;s&&wr(e,s,n),o&&o.forEach((t=>wr(e,t,n)));for(const l in t)r&&x(r,l)?e[l]=r[l](e[l],t[l],n.proxy,l):e[l]=t[l]}const xr=e=>e?Pr(e)?e.exposed?e.exposed:e.proxy:xr(e.parent):null,Er=b(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>xr(e.parent),$root:e=>xr(e.root),$emit:e=>e.emit,$options:e=>function(e){const t=e.type,{__merged:n,mixins:r,extends:o}=t;if(n)return n;const s=e.appContext.mixins;if(!s.length&&!r&&!o)return t;const l={};return s.forEach((t=>wr(l,t,e))),wr(l,t,e),t.__merged=l}(e),$forceUpdate:e=>()=>Ct(e.update),$nextTick:e=>Ot.bind(e.proxy),$watch:e=>_n.bind(e)}),kr={get({_:e},t){const{ctx:n,setupState:r,data:o,props:s,accessCache:l,type:i,appContext:c}=e;if("__v_skip"===t)return!0;let a;if("$"!==t[0]){const i=l[t];if(void 0!==i)switch(i){case 0:return r[t];case 1:return o[t];case 3:return n[t];case 2:return s[t]}else{if(r!==p&&x(r,t))return l[t]=0,r[t];if(o!==p&&x(o,t))return l[t]=1,o[t];if((a=e.propsOptions[0])&&x(a,t))return l[t]=2,s[t];if(n!==p&&x(n,t))return l[t]=3,n[t];hr&&(l[t]=4)}}const u=Er[t];let f,d;return u?("$attrs"===t&&ie(e,0,t),u(e)):(f=i.__cssModules)&&(f=f[t])?f:n!==p&&x(n,t)?(l[t]=3,n[t]):(d=c.config.globalProperties,x(d,t)?d[t]:void 0)},set({_:e},t,n){const{data:r,setupState:o,ctx:s}=e;if(o!==p&&x(o,t))o[t]=n;else if(r!==p&&x(r,t))r[t]=n;else if(x(e.props,t))return!1;return("$"!==t[0]||!(t.slice(1)in e))&&(s[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:r,appContext:o,propsOptions:s}},l){let i;return void 0!==n[l]||e!==p&&x(e,l)||t!==p&&x(t,l)||(i=s[0])&&x(i,l)||x(r,l)||x(Er,l)||x(o.config.globalProperties,l)}},Sr=b({},kr,{get(e,t){if(t!==Symbol.unscopables)return kr.get(e,t,e)},has:(e,n)=>"_"!==n[0]&&!t(n)}),Or=jn();let Cr=0;let Rr=null;const Ar=e=>{Rr=e};function Pr(e){return 4&e.vnode.shapeFlag}let Fr=!1;function jr(e,t,n){C(t)?e.render=t:P(t)&&(e.setupState=it(t)),$r(e)}function $r(e,t){const n=e.type;e.render||(e.render=n.render||h,e.render._rc&&(e.withProxy=new Proxy(e.ctx,Sr))),Rr=e,se(),mr(e,n),le(),Rr=null}function Mr(e,t=Rr){t&&(t.effects||(t.effects=[])).push(e)}function Ir(e){return C(e)&&e.displayName||e.name}function Tr(e){const t=function(e){let t,n;return C(e)?(t=e,n=h):(t=e.get,n=e.set),new at(t,n,C(e)||!e.set)}(e);return Mr(t.effect),t}function Vr(e,t,n){const r=arguments.length;return 2===r?P(t)&&!E(t)?er(t)?sr(e,null,[t]):sr(e,t):sr(e,null,t):(r>3?n=Array.prototype.slice.call(arguments,2):3===r&&er(n)&&(n=[n]),sr(e,t,n))}function Ur(e,t){let n;if(E(e)||R(e)){n=new Array(e.length);for(let r=0,o=e.length;r{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{const o=t?Br.createElementNS(Lr,e):Br.createElement(e,n?{is:n}:void 0);return"select"===e&&r&&null!=r.multiple&&o.setAttribute("multiple",r.multiple),o},createText:e=>Br.createTextNode(e),createComment:e=>Br.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Br.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},cloneNode(e){const t=e.cloneNode(!0);return"_value"in e&&(t._value=e._value),t},insertStaticContent(e,t,n,r){const o=r?qr||(qr=Br.createElementNS(Lr,"svg")):Dr||(Dr=Br.createElement("div"));o.innerHTML=e;const s=o.firstChild;let l=s,i=l;for(;l;)i=l,zr.insert(l,t,n),l=o.firstChild;return[s,i]}};const Wr=/\s*!important$/;function Kr(e,t,n){if(E(n))n.forEach((n=>Kr(e,t,n)));else if(t.startsWith("--"))e.setProperty(t,n);else{const r=function(e,t){const n=Hr[t];if(n)return n;let r=N(t);if("filter"!==r&&r in e)return Hr[t]=r;r=D(r);for(let o=0;odocument.createEvent("Event").timeStamp&&(Jr=()=>performance.now());const e=navigator.userAgent.match(/firefox\/(\d+)/i);Qr=!!(e&&Number(e[1])<=53)}let Yr=0;const Zr=Promise.resolve(),eo=()=>{Yr=0};function to(e,t,n,r){e.addEventListener(t,n,r)}function no(e,t,n,r,o=null){const s=e._vei||(e._vei={}),l=s[t];if(r&&l)l.value=r;else{const[n,i]=function(e){let t;if(ro.test(e)){let n;for(t={};n=e.match(ro);)e=e.slice(0,e.length-n[0].length),t[n[0].toLowerCase()]=!0}return[B(e.slice(2)),t]}(t);if(r){to(e,n,s[t]=function(e,t){const n=e=>{const r=e.timeStamp||Jr();(Qr||r>=n.attached-1)&&ft(function(e,t){if(E(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map((e=>t=>!t._stopped&&e(t)))}return t}(e,n.value),t,5,[e])};return n.value=e,n.attached=(()=>Yr||(Zr.then(eo),Yr=Jr()))(),n}(r,o),i)}else l&&(!function(e,t,n,r){e.removeEventListener(t,n,r)}(e,n,l,i),s[t]=void 0)}}const ro=/(?:Once|Passive|Capture)$/;const oo=/^on[a-z]/;const so=e=>{const t=e.props["onUpdate:modelValue"];return E(t)?e=>W(t,e):t};function lo(e){e.target.composing=!0}function io(e){const t=e.target;t.composing&&(t.composing=!1,function(e,t){const n=document.createEvent("HTMLEvents");n.initEvent(t,!0,!0),e.dispatchEvent(n)}(t,"input"))}const co={created(e,{modifiers:{lazy:t,trim:n,number:r}},o){e._assign=so(o);const s=r||"number"===e.type;to(e,t?"change":"input",(t=>{if(t.target.composing)return;let r=e.value;n?r=r.trim():s&&(r=G(r)),e._assign(r)})),n&&to(e,"change",(()=>{e.value=e.value.trim()})),t||(to(e,"compositionstart",lo),to(e,"compositionend",io),to(e,"change",io))},mounted(e,{value:t}){e.value=null==t?"":t},beforeUpdate(e,{value:t,modifiers:{trim:n,number:r}},o){if(e._assign=so(o),e.composing)return;if(document.activeElement===e){if(n&&e.value.trim()===t)return;if((r||"number"===e.type)&&G(e.value)===t)return}const s=null==t?"":t;e.value!==s&&(e.value=s)}},ao={created(e,t,n){e._assign=so(n),to(e,"change",(()=>{const t=e._modelValue,n=mo(e),r=e.checked,o=e._assign;if(E(t)){const e=a(t,n),s=-1!==e;if(r&&!s)o(t.concat(n));else if(!r&&s){const n=[...t];n.splice(e,1),o(n)}}else if(S(t)){const e=new Set(t);r?e.add(n):e.delete(n),o(e)}else o(go(e,r))}))},mounted:uo,beforeUpdate(e,t,n){e._assign=so(n),uo(e,t,n)}};function uo(e,{value:t,oldValue:n},r){e._modelValue=t,E(t)?e.checked=a(t,r.props.value)>-1:S(t)?e.checked=t.has(r.props.value):t!==n&&(e.checked=c(t,go(e,!0)))}const fo={created(e,{value:t},n){e.checked=c(t,n.props.value),e._assign=so(n),to(e,"change",(()=>{e._assign(mo(e))}))},beforeUpdate(e,{value:t,oldValue:n},r){e._assign=so(r),t!==n&&(e.checked=c(t,r.props.value))}},po={created(e,{value:t,modifiers:{number:n}},r){const o=S(t);to(e,"change",(()=>{const t=Array.prototype.filter.call(e.options,(e=>e.selected)).map((e=>n?G(mo(e)):mo(e)));e._assign(e.multiple?o?new Set(t):t:t[0])})),e._assign=so(r)},mounted(e,{value:t}){ho(e,t)},beforeUpdate(e,t,n){e._assign=so(n)},updated(e,{value:t}){ho(e,t)}};function ho(e,t){const n=e.multiple;if(!n||E(t)||S(t)){for(let r=0,o=e.options.length;r-1:o.selected=t.has(s);else if(c(mo(o),t))return void(e.selectedIndex=r)}n||(e.selectedIndex=-1)}}function mo(e){return"_value"in e?e._value:e.value}function go(e,t){const n=t?"_trueValue":"_falseValue";return n in e?e[n]:t}const vo=["ctrl","shift","alt","meta"],yo={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&0!==e.button,middle:e=>"button"in e&&1!==e.button,right:e=>"button"in e&&2!==e.button,exact:(e,t)=>vo.some((n=>e[`${n}Key`]&&!t.includes(n)))},bo=(e,t)=>(n,...r)=>{for(let e=0;en=>{if(!("key"in n))return;const r=B(n.key);return t.some((e=>e===r||_o[e]===r))?e(n):void 0},xo={beforeMount(e,{value:t},{transition:n}){e._vod="none"===e.style.display?"":e.style.display,n&&t?n.beforeEnter(e):Eo(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:r}){!t!=!n&&(r?t?(r.beforeEnter(e),Eo(e,!0),r.enter(e)):r.leave(e,(()=>{Eo(e,!1)})):Eo(e,t))},beforeUnmount(e,{value:t}){Eo(e,t)}};function Eo(e,t){e.style.display=t?e._vod:"none"}const ko=b({patchProp:(e,t,r,o,s=!1,l,i,c,a)=>{switch(t){case"class":!function(e,t,n){if(null==t&&(t=""),n)e.setAttribute("class",t);else{const n=e._vtc;n&&(t=(t?[t,...n]:[...n]).join(" ")),e.className=t}}(e,o,s);break;case"style":!function(e,t,n){const r=e.style;if(n)if(R(n)){if(t!==n){const t=r.display;r.cssText=n,"_vod"in e&&(r.display=t)}}else{for(const e in n)Kr(r,e,n[e]);if(t&&!R(t))for(const e in t)null==n[e]&&Kr(r,e,"")}else e.removeAttribute("style")}(e,r,o);break;default:v(t)?y(t)||no(e,t,0,o,i):function(e,t,n,r){if(r)return"innerHTML"===t||!!(t in e&&oo.test(t)&&C(n));if("spellcheck"===t||"draggable"===t)return!1;if("form"===t)return!1;if("list"===t&&"INPUT"===e.tagName)return!1;if("type"===t&&"TEXTAREA"===e.tagName)return!1;if(oo.test(t)&&R(n))return!1;return t in e}(e,t,o,s)?function(e,t,n,r,o,s,l){if("innerHTML"===t||"textContent"===t)return r&&l(r,o,s),void(e[t]=null==n?"":n);if("value"!==t||"PROGRESS"===e.tagName){if(""===n||null==n){const r=typeof e[t];if(""===n&&"boolean"===r)return void(e[t]=!0);if(null==n&&"string"===r)return e[t]="",void e.removeAttribute(t);if("number"===r)return e[t]=0,void e.removeAttribute(t)}try{e[t]=n}catch(i){}}else{e._value=n;const t=null==n?"":n;e.value!==t&&(e.value=t)}}(e,t,o,l,i,c,a):("true-value"===t?e._trueValue=o:"false-value"===t&&(e._falseValue=o),function(e,t,r,o){if(o&&t.startsWith("xlink:"))null==r?e.removeAttributeNS(Xr,t.slice(6,t.length)):e.setAttributeNS(Xr,t,r);else{const o=n(t);null==r||o&&!1===r?e.removeAttribute(t):e.setAttribute(t,o?"":r)}}(e,t,o,s))}},forcePatchProp:(e,t)=>"value"===t},zr);let So;const Oo=(...e)=>{const t=(So||(So=Nn(ko))).createApp(...e),{mount:n}=t;return t.mount=e=>{const r=function(e){if(R(e)){return document.querySelector(e)}return e} /*! * vue-router v4.0.8 * (c) 2021 Eduardo San Martin Morote * @license MIT - */(e);if(!r)return;const o=t._component;C(o)||o.render||o.template||(o.template=r.innerHTML),r.innerHTML="";const s=n(r,!1,r instanceof SVGElement);return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")),s},t};const ko="function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag,So=e=>ko?Symbol(e):"_vr_"+e,Oo=So("rvlm"),Co=So("rvd"),Ro=So("r"),Ao=So("rl"),Po=So("rvl"),Fo="undefined"!=typeof window;const jo=Object.assign;function $o(e,t){const n={};for(const r in t){const o=t[r];n[r]=Array.isArray(o)?o.map(e):e(o)}return n}let Mo=()=>{};const Io=/\/$/;function To(e,t,n="/"){let r,o={},s="",l="";const i=t.indexOf("?"),c=t.indexOf("#",i>-1?i:0);return i>-1&&(r=t.slice(0,i),s=t.slice(i+1,c>-1?c:t.length),o=e(s)),c>-1&&(r=r||t.slice(0,c),l=t.slice(c,t.length)),r=function(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t.split("/"),r=e.split("/");let o,s,l=n.length-1;for(o=0;oe===t[n])):1===e.length&&e[0]===t}var Do,qo,zo,Wo;function Ko(e){if(!e)if(Fo){const t=document.querySelector("base");e=(e=t&&t.getAttribute("href")||"/").replace(/^\w+:\/\/[^\/]+/,"")}else e="/";return"/"!==e[0]&&"#"!==e[0]&&(e="/"+e),e.replace(Io,"")}(qo=Do||(Do={})).pop="pop",qo.push="push",(Wo=zo||(zo={})).back="back",Wo.forward="forward",Wo.unknown="";const Go=/^[^#]+#/;function Ho(e,t){return e.replace(Go,"#")+t}const Xo=()=>({left:window.pageXOffset,top:window.pageYOffset});function Jo(e){let t;if("el"in e){let n=e.el;const r="string"==typeof n&&n.startsWith("#"),o="string"==typeof n?r?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!o)return;t=function(e,t){const n=document.documentElement.getBoundingClientRect(),r=e.getBoundingClientRect();return{behavior:t.behavior,left:r.left-n.left-(t.left||0),top:r.top-n.top-(t.top||0)}}(o,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(null!=t.left?t.left:window.pageXOffset,null!=t.top?t.top:window.pageYOffset)}function Qo(e,t){return(history.state?history.state.position-t:-1)+e}const Yo=new Map;function Zo(e,t){const{pathname:n,search:r,hash:o}=t,s=e.indexOf("#");if(s>-1){let t=o.includes(e.slice(s))?e.slice(s).length:1,n=o.slice(t);return"/"!==n[0]&&(n="/"+n),Uo(n,"")}return Uo(n,e)+r+o}function es(e,t,n,r=!1,o=!1){return{back:e,current:t,forward:n,replaced:r,position:window.history.length,scroll:o?Xo():null}}function ts(e){const{history:t,location:n}=window;let r={value:Zo(e,n)},o={value:t.state};function s(r,s,l){const i=e.indexOf("#"),c=i>-1?(n.host&&document.querySelector("base")?e:e.slice(i))+r:location.protocol+"//"+location.host+e+r;try{t[l?"replaceState":"pushState"](s,"",c),o.value=s}catch(a){console.error(a),n[l?"replace":"assign"](c)}}return o.value||s(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0),{location:r,state:o,push:function(e,n){const l=jo({},o.value,t.state,{forward:e,scroll:Xo()});s(l.current,l,!0),s(e,jo({},es(r.value,e,null),{position:l.position+1},n),!1),r.value=e},replace:function(e,n){s(e,jo({},t.state,es(o.value.back,e,o.value.forward,!0),n,{position:o.value.position}),!0),r.value=e}}}function ns(e){const t=ts(e=Ko(e)),n=function(e,t,n,r){let o=[],s=[],l=null;const i=({state:s})=>{const i=Zo(e,location),c=n.value,a=t.value;let u=0;if(s){if(n.value=i,t.value=s,l&&l===c)return void(l=null);u=a?s.position-a.position:0}else r(i);o.forEach((e=>{e(n.value,c,{delta:u,type:Do.pop,direction:u?u>0?zo.forward:zo.back:zo.unknown})}))};function c(){const{history:e}=window;e.state&&e.replaceState(jo({},e.state,{scroll:Xo()}),"")}return window.addEventListener("popstate",i),window.addEventListener("beforeunload",c),{pauseListeners:function(){l=n.value},listen:function(e){o.push(e);const t=()=>{const t=o.indexOf(e);t>-1&&o.splice(t,1)};return s.push(t),t},destroy:function(){for(const e of s)e();s=[],window.removeEventListener("popstate",i),window.removeEventListener("beforeunload",c)}}}(e,t.state,t.location,t.replace);const r=jo({location:"",base:e,go:function(e,t=!0){t||n.pauseListeners(),history.go(e)},createHref:Ho.bind(null,e)},t,n);return Object.defineProperty(r,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(r,"state",{enumerable:!0,get:()=>t.state.value}),r}function rs(e){return(e=location.host?e||location.pathname+location.search:"").indexOf("#")<0&&(e+="#"),ns(e)}function os(e){return"string"==typeof e||"symbol"==typeof e}const ss={path:"/",name:void 0,params:{},query:{},hash:"",fullPath:"/",matched:[],meta:{},redirectedFrom:void 0},ls=So("nf");var is,cs;function as(e,t){return jo(new Error,{type:e,[ls]:!0},t)}function us(e,t){return e instanceof Error&&ls in e&&(null==t||!!(e.type&t))}(cs=is||(is={}))[cs.aborted=4]="aborted",cs[cs.cancelled=8]="cancelled",cs[cs.duplicated=16]="duplicated";const fs={sensitive:!1,strict:!1,start:!0,end:!0},ps=/[.+*?^${}()[\]/\\]/g;function ds(e,t){let n=0;for(;nt.length?1===t.length&&80===t[0]?1:-1:0}function hs(e,t){let n=0;const r=e.score,o=t.score;for(;n1&&("*"===i||"+"===i)&&t(`A repeatable param (${a}) must be alone in its segment. eg: '/:ids+.`),s.push({type:1,value:a,regexp:u,repeatable:"*"===i||"+"===i,optional:"*"===i||"?"===i})):t("Invalid state to consume buffer"),a="")}function p(){a+=i}for(;c{s(p)}:Mo}function s(e){if(os(e)){const t=r.get(e);t&&(r.delete(e),n.splice(n.indexOf(t),1),t.children.forEach(s),t.alias.forEach(s))}else{let t=n.indexOf(e);t>-1&&(n.splice(t,1),e.record.name&&r.delete(e.record.name),e.children.forEach(s),e.alias.forEach(s))}}function l(e){let t=0;for(;t=0;)t++;n.splice(t,0,e),e.record.name&&!_s(e)&&r.set(e.record.name,e)}return t=xs({strict:!1,end:!0,sensitive:!1},t),e.forEach((e=>o(e))),{addRoute:o,resolve:function(e,t){let o,s,l,i={};if("name"in e&&e.name){if(o=r.get(e.name),!o)throw as(1,{location:e});l=o.record.name,i=jo(function(e,t){let n={};for(let r of t)r in e&&(n[r]=e[r]);return n}(t.params,o.keys.filter((e=>!e.optional)).map((e=>e.name))),e.params),s=o.stringify(i)}else if("path"in e)s=e.path,o=n.find((e=>e.re.test(s))),o&&(i=o.parse(s),l=o.record.name);else{if(o=t.name?r.get(t.name):n.find((e=>e.re.test(t.path))),!o)throw as(1,{location:e,currentLocation:t});l=o.record.name,i=jo({},t.params,e.params),s=o.stringify(i)}const c=[];let a=o;for(;a;)c.unshift(a.record),a=a.parent;return{name:l,path:s,params:i,matched:c,meta:ws(c)}},removeRoute:s,getRoutes:function(){return n},getRecordMatcher:function(e){return r.get(e)}}}function bs(e){const t={},n=e.props||!1;if("component"in e)t.default=n;else for(let r in e.components)t[r]="boolean"==typeof n?n:n[r];return t}function _s(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function ws(e){return e.reduce(((e,t)=>jo(e,t.meta)),{})}function xs(e,t){let n={};for(let r in e)n[r]=r in t?t[r]:e[r];return n}const Es=/#/g,ks=/&/g,Ss=/\//g,Os=/=/g,Cs=/\?/g,Rs=/\+/g,As=/%5B/g,Ps=/%5D/g,Fs=/%5E/g,js=/%60/g,$s=/%7B/g,Ms=/%7C/g,Is=/%7D/g,Ts=/%20/g;function Us(e){return encodeURI(""+e).replace(Ms,"|").replace(As,"[").replace(Ps,"]")}function Vs(e){return Us(e).replace(Rs,"%2B").replace(Ts,"+").replace(Es,"%23").replace(ks,"%26").replace(js,"`").replace($s,"{").replace(Is,"}").replace(Fs,"^")}function Ns(e){return function(e){return Us(e).replace(Es,"%23").replace(Cs,"%3F")}(e).replace(Ss,"%2F")}function Ls(e){try{return decodeURIComponent(""+e)}catch(t){}return""+e}function Bs(e){const t={};if(""===e||"?"===e)return t;const n=("?"===e[0]?e.slice(1):e).split("&");for(let r=0;re&&Vs(e))):[r&&Vs(r)]).forEach((e=>{void 0!==e&&(t+=(t.length?"&":"")+n,null!=e&&(t+="="+e))}))}return t}function qs(e){const t={};for(let n in e){let r=e[n];void 0!==r&&(t[n]=Array.isArray(r)?r.map((e=>null==e?null:""+e)):null==r?r:""+r)}return t}function zs(){let e=[];return{add:function(t){return e.push(t),()=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)}},list:()=>e,reset:function(){e=[]}}}function Ws(e,t,n,r,o){const s=r&&(r.enterCallbacks[o]=r.enterCallbacks[o]||[]);return()=>new Promise(((l,i)=>{const c=e=>{var c;!1===e?i(as(4,{from:n,to:t})):e instanceof Error?i(e):"string"==typeof(c=e)||c&&"object"==typeof c?i(as(2,{from:t,to:e})):(s&&r.enterCallbacks[o]===s&&"function"==typeof e&&s.push(e),l())},a=e.call(r&&r.instances[o],t,n,c);let u=Promise.resolve(a);e.length<3&&(u=u.then(c)),u.catch((e=>i(e)))}))}function Ks(e,t,n,r){const o=[];for(const l of e)for(const e in l.components){let i=l.components[e];if("beforeRouteEnter"===t||l.instances[e])if("object"==typeof(s=i)||"displayName"in s||"props"in s||"__vccOpts"in s){const s=(i.__vccOpts||i)[t];s&&o.push(Ws(s,n,r,l,e))}else{let s=i();s=s.catch(console.error),o.push((()=>s.then((o=>{if(!o)return Promise.reject(new Error(`Couldn't resolve component "${e}" at "${l.path}"`));const s=(i=o).__esModule||ko&&"Module"===i[Symbol.toStringTag]?o.default:o;var i;l.components[e]=s;const c=(s.__vccOpts||s)[t];return c&&Ws(c,n,r,l,e)()}))))}}var s;return o}function Gs(e){const t=dr(Ro),n=dr(Ao),r=Tr((()=>t.resolve(st(e.to)))),o=Tr((()=>{let{matched:e}=r.value,{length:t}=e;const o=e[t-1];let s=n.matched;if(!o||!s.length)return-1;let l=s.findIndex(Vo.bind(null,o));if(l>-1)return l;let i=Xs(e[t-2]);return t>1&&Xs(o)===i&&s[s.length-1].path!==i?s.findIndex(Vo.bind(null,e[t-2])):l})),s=Tr((()=>o.value>-1&&function(e,t){for(let n in t){let r=t[n],o=e[n];if("string"==typeof r){if(r!==o)return!1}else if(!Array.isArray(o)||o.length!==r.length||r.some(((e,t)=>e!==o[t])))return!1}return!0}(n.params,r.value.params))),l=Tr((()=>o.value>-1&&o.value===n.matched.length-1&&No(n.params,r.value.params)));return{route:r,href:Tr((()=>r.value.href)),isActive:s,isExactActive:l,navigate:function(n={}){return function(e){if(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)return;if(e.defaultPrevented)return;if(void 0!==e.button&&0!==e.button)return;if(e.currentTarget&&e.currentTarget.getAttribute){const t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}e.preventDefault&&e.preventDefault();return!0}(n)?t[st(e.replace)?"replace":"push"](st(e.to)):Promise.resolve()}}}const Hs=In({name:"RouterLink",props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:"page"}},setup(e,{slots:t}){const n=He(Gs(e)),{options:r}=dr(Ro),o=Tr((()=>({[Js(e.activeClass,r.linkActiveClass,"router-link-active")]:n.isActive,[Js(e.exactActiveClass,r.linkExactActiveClass,"router-link-exact-active")]:n.isExactActive})));return()=>{const r=t.default&&t.default(n);return e.custom?r:Ur("a",{"aria-current":n.isExactActive?e.ariaCurrentValue:null,href:n.href,onClick:n.navigate,class:o.value},r)}}});function Xs(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}const Js=(e,t,n)=>null!=e?e:null!=t?t:n;function Qs(e,t){if(!e)return null;const n=e(t);return 1===n.length?n[0]:n}const Ys=In({name:"RouterView",inheritAttrs:!1,props:{name:{type:String,default:"default"},route:Object},setup(e,{attrs:t,slots:n}){const r=dr(Po),o=Tr((()=>e.route||r.value)),s=dr(Co,0),l=Tr((()=>o.value.matched[s]));pr(Co,s+1),pr(Oo,l),pr(Po,o);const i=ot(c);var c;return yn((()=>[i.value,l.value,e.name]),(([e,t,n],[r,o,s])=>{t&&(t.instances[n]=e,o&&o!==t&&e&&e===r&&(t.leaveGuards.size||(t.leaveGuards=o.leaveGuards),t.updateGuards.size||(t.updateGuards=o.updateGuards))),!e||!t||o&&Vo(t,o)&&r||(t.enterCallbacks[n]||[]).forEach((t=>t(e)))}),{flush:"post"}),()=>{const r=o.value,s=l.value,c=s&&s.components[e.name],a=e.name;if(!c)return Qs(n.default,{Component:c,route:r});const u=s.props[e.name],f=u?!0===u?r.params:"function"==typeof u?u(r):u:null,p=Ur(c,jo({},f,t,{onVnodeUnmounted:e=>{e.component.isUnmounted&&(s.instances[a]=null)},ref:i}));return Qs(n.default,{Component:p,route:r})||p}}});function Zs(e){const t=ys(e.routes,e);let n=e.parseQuery||Bs,r=e.stringifyQuery||Ds,o=e.history;const s=zs(),l=zs(),i=zs(),c=ot(ss,!0);let a=ss;Fo&&e.scrollBehavior&&"scrollRestoration"in history&&(history.scrollRestoration="manual");const u=$o.bind(null,(e=>""+e)),f=$o.bind(null,Ns),p=$o.bind(null,Ls);function d(e,s){if(s=jo({},s||c.value),"string"==typeof e){let r=To(n,e,s.path),l=t.resolve({path:r.path},s),i=o.createHref(r.fullPath);return jo(r,l,{params:p(l.params),hash:Ls(r.hash),redirectedFrom:void 0,href:i})}let l;"path"in e?l=jo({},e,{path:To(n,e.path,s.path).path}):(l=jo({},e,{params:f(e.params)}),s.params=f(s.params));let i=t.resolve(l,s);const a=e.hash||"";i.params=u(p(i.params));const d=function(e,t){let n=t.query?e(t.query):"";return t.path+(n&&"?")+n+(t.hash||"")}(r,jo({},e,{hash:(h=a,Us(h).replace($s,"{").replace(Is,"}").replace(Fs,"^")),path:i.path}));var h;let m=o.createHref(d);return jo({fullPath:d,hash:a,query:r===Ds?qs(e.query):e.query},i,{redirectedFrom:void 0,href:m})}function h(e){return"string"==typeof e?To(n,e,c.value.path):jo({},e)}function m(e,t){if(a!==e)return as(8,{from:t,to:e})}function g(e){return y(e)}function v(e){const t=e.matched[e.matched.length-1];if(t&&t.redirect){const{redirect:n}=t;let r="function"==typeof n?n(e):n;return"string"==typeof r&&(r=r.indexOf("?")>-1||r.indexOf("#")>-1?r=h(r):{path:r}),jo({query:e.query,hash:e.hash,params:e.params},r)}}function y(e,t){const n=a=d(e),o=c.value,s=e.state,l=e.force,i=!0===e.replace,u=v(n);if(u)return y(jo(h(u),{state:s,force:l,replace:i}),t||n);const f=n;let p;return f.redirectedFrom=t,!l&&function(e,t,n){let r=t.matched.length-1,o=n.matched.length-1;return r>-1&&r===o&&Vo(t.matched[r],n.matched[o])&&No(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}(r,o,n)&&(p=as(16,{to:f,from:o}),P(o,o,!0,!1)),(p?Promise.resolve(p):_(f,o)).catch((e=>us(e)?e:R(e))).then((e=>{if(e){if(us(e,2))return y(jo(h(e.to),{state:s,force:l,replace:i}),t||f)}else e=x(f,o,!0,i,s);return w(f,o,e),e}))}function b(e,t){const n=m(e,t);return n?Promise.reject(n):Promise.resolve()}function _(e,t){let n;const[r,o,i]=function(e,t){const n=[],r=[],o=[],s=Math.max(t.matched.length,e.matched.length);for(let l=0;lVo(e,s)))?r.push(s):n.push(s));const i=e.matched[l];i&&(t.matched.find((e=>Vo(e,i)))||o.push(i))}return[n,r,o]}(e,t);n=Ks(r.reverse(),"beforeRouteLeave",e,t);for(const s of r)s.leaveGuards.forEach((r=>{n.push(Ws(r,e,t))}));const c=b.bind(null,e,t);return n.push(c),el(n).then((()=>{n=[];for(const r of s.list())n.push(Ws(r,e,t));return n.push(c),el(n)})).then((()=>{n=Ks(o,"beforeRouteUpdate",e,t);for(const r of o)r.updateGuards.forEach((r=>{n.push(Ws(r,e,t))}));return n.push(c),el(n)})).then((()=>{n=[];for(const r of e.matched)if(r.beforeEnter&&t.matched.indexOf(r)<0)if(Array.isArray(r.beforeEnter))for(const o of r.beforeEnter)n.push(Ws(o,e,t));else n.push(Ws(r.beforeEnter,e,t));return n.push(c),el(n)})).then((()=>(e.matched.forEach((e=>e.enterCallbacks={})),n=Ks(i,"beforeRouteEnter",e,t),n.push(c),el(n)))).then((()=>{n=[];for(const r of l.list())n.push(Ws(r,e,t));return n.push(c),el(n)})).catch((e=>us(e,8)?e:Promise.reject(e)))}function w(e,t,n){for(const r of i.list())r(e,t,n)}function x(e,t,n,r,s){const l=m(e,t);if(l)return l;const i=t===ss,a=Fo?history.state:{};n&&(r||i?o.replace(e.fullPath,jo({scroll:i&&a&&a.scroll},s)):o.push(e.fullPath,s)),c.value=e,P(e,t,n,i),A()}let E;function k(){E=o.listen(((e,t,n)=>{let r=d(e);const s=v(r);if(s)return void y(jo(s,{replace:!0}),r).catch(Mo);a=r;const l=c.value;var i,u;Fo&&(i=Qo(l.fullPath,n.delta),u=Xo(),Yo.set(i,u)),_(r,l).catch((e=>us(e,12)?e:us(e,2)?(y(e.to,r).catch(Mo),Promise.reject()):(n.delta&&o.go(-n.delta,!1),R(e)))).then((e=>{(e=e||x(r,l,!1))&&n.delta&&o.go(-n.delta,!1),w(r,l,e)})).catch(Mo)}))}let S,O=zs(),C=zs();function R(e){return A(e),C.list().forEach((t=>t(e))),Promise.reject(e)}function A(e){S||(S=!0,k(),O.list().forEach((([t,n])=>e?n(e):t())),O.reset())}function P(t,n,r,o){const{scrollBehavior:s}=e;if(!Fo||!s)return Promise.resolve();let l=!r&&function(e){const t=Yo.get(e);return Yo.delete(e),t}(Qo(t.fullPath,0))||(o||!r)&&history.state&&history.state.scroll||null;return Ot().then((()=>s(t,n,l))).then((e=>e&&Jo(e))).catch(R)}const F=e=>o.go(e);let j;const $=new Set;return{currentRoute:c,addRoute:function(e,n){let r,o;return os(e)?(r=t.getRecordMatcher(e),o=n):o=e,t.addRoute(o,r)},removeRoute:function(e){let n=t.getRecordMatcher(e);n&&t.removeRoute(n)},hasRoute:function(e){return!!t.getRecordMatcher(e)},getRoutes:function(){return t.getRoutes().map((e=>e.record))},resolve:d,options:e,push:g,replace:function(e){return g(jo(h(e),{replace:!0}))},go:F,back:()=>F(-1),forward:()=>F(1),beforeEach:s.add,beforeResolve:l.add,afterEach:i.add,onError:C.add,isReady:function(){return S&&c.value!==ss?Promise.resolve():new Promise(((e,t)=>{O.add([e,t])}))},install(e){e.component("RouterLink",Hs),e.component("RouterView",Ys),e.config.globalProperties.$router=this,Object.defineProperty(e.config.globalProperties,"$route",{enumerable:!0,get:()=>st(c)}),Fo&&!j&&c.value===ss&&(j=!0,g(o.location).catch((e=>{})));const t={};for(let r in ss)t[r]=Tr((()=>c.value[r]));e.provide(Ro,this),e.provide(Ao,He(t)),e.provide(Po,c);let n=e.unmount;$.add(e),e.unmount=function(){$.delete(e),$.size<1&&(E(),c.value=ss,j=!1,S=!1),n()}}}}function el(e){return e.reduce(((e,t)=>e.then((()=>t()))),Promise.resolve())}export{Wn as F,sr as a,cr as b,Zn as c,In as d,ir as e,Vr as f,go as g,Pn as h,yo as i,zt as j,ao as k,uo as l,Nt as m,bo as n,Qn as o,Zs as p,rs as q,Dn as r,Eo as s,u as t,co as v,Wt as w}; + */(e);if(!r)return;const o=t._component;C(o)||o.render||o.template||(o.template=r.innerHTML),r.innerHTML="";const s=n(r,!1,r instanceof SVGElement);return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")),s},t};const Co="function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag,Ro=e=>Co?Symbol(e):"_vr_"+e,Ao=Ro("rvlm"),Po=Ro("rvd"),Fo=Ro("r"),jo=Ro("rl"),$o=Ro("rvl"),Mo="undefined"!=typeof window;const Io=Object.assign;function To(e,t){const n={};for(const r in t){const o=t[r];n[r]=Array.isArray(o)?o.map(e):e(o)}return n}let Vo=()=>{};const Uo=/\/$/;function No(e,t,n="/"){let r,o={},s="",l="";const i=t.indexOf("?"),c=t.indexOf("#",i>-1?i:0);return i>-1&&(r=t.slice(0,i),s=t.slice(i+1,c>-1?c:t.length),o=e(s)),c>-1&&(r=r||t.slice(0,c),l=t.slice(c,t.length)),r=function(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t.split("/"),r=e.split("/");let o,s,l=n.length-1;for(o=0;oe===t[n])):1===e.length&&e[0]===t}var Wo,Ko,Go,Ho;function Xo(e){if(!e)if(Mo){const t=document.querySelector("base");e=(e=t&&t.getAttribute("href")||"/").replace(/^\w+:\/\/[^\/]+/,"")}else e="/";return"/"!==e[0]&&"#"!==e[0]&&(e="/"+e),e.replace(Uo,"")}(Ko=Wo||(Wo={})).pop="pop",Ko.push="push",(Ho=Go||(Go={})).back="back",Ho.forward="forward",Ho.unknown="";const Jo=/^[^#]+#/;function Qo(e,t){return e.replace(Jo,"#")+t}const Yo=()=>({left:window.pageXOffset,top:window.pageYOffset});function Zo(e){let t;if("el"in e){let n=e.el;const r="string"==typeof n&&n.startsWith("#"),o="string"==typeof n?r?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!o)return;t=function(e,t){const n=document.documentElement.getBoundingClientRect(),r=e.getBoundingClientRect();return{behavior:t.behavior,left:r.left-n.left-(t.left||0),top:r.top-n.top-(t.top||0)}}(o,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(null!=t.left?t.left:window.pageXOffset,null!=t.top?t.top:window.pageYOffset)}function es(e,t){return(history.state?history.state.position-t:-1)+e}const ts=new Map;function ns(e,t){const{pathname:n,search:r,hash:o}=t,s=e.indexOf("#");if(s>-1){let t=o.includes(e.slice(s))?e.slice(s).length:1,n=o.slice(t);return"/"!==n[0]&&(n="/"+n),Lo(n,"")}return Lo(n,e)+r+o}function rs(e,t,n,r=!1,o=!1){return{back:e,current:t,forward:n,replaced:r,position:window.history.length,scroll:o?Yo():null}}function os(e){const{history:t,location:n}=window;let r={value:ns(e,n)},o={value:t.state};function s(r,s,l){const i=e.indexOf("#"),c=i>-1?(n.host&&document.querySelector("base")?e:e.slice(i))+r:location.protocol+"//"+location.host+e+r;try{t[l?"replaceState":"pushState"](s,"",c),o.value=s}catch(a){console.error(a),n[l?"replace":"assign"](c)}}return o.value||s(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0),{location:r,state:o,push:function(e,n){const l=Io({},o.value,t.state,{forward:e,scroll:Yo()});s(l.current,l,!0),s(e,Io({},rs(r.value,e,null),{position:l.position+1},n),!1),r.value=e},replace:function(e,n){s(e,Io({},t.state,rs(o.value.back,e,o.value.forward,!0),n,{position:o.value.position}),!0),r.value=e}}}function ss(e){const t=os(e=Xo(e)),n=function(e,t,n,r){let o=[],s=[],l=null;const i=({state:s})=>{const i=ns(e,location),c=n.value,a=t.value;let u=0;if(s){if(n.value=i,t.value=s,l&&l===c)return void(l=null);u=a?s.position-a.position:0}else r(i);o.forEach((e=>{e(n.value,c,{delta:u,type:Wo.pop,direction:u?u>0?Go.forward:Go.back:Go.unknown})}))};function c(){const{history:e}=window;e.state&&e.replaceState(Io({},e.state,{scroll:Yo()}),"")}return window.addEventListener("popstate",i),window.addEventListener("beforeunload",c),{pauseListeners:function(){l=n.value},listen:function(e){o.push(e);const t=()=>{const t=o.indexOf(e);t>-1&&o.splice(t,1)};return s.push(t),t},destroy:function(){for(const e of s)e();s=[],window.removeEventListener("popstate",i),window.removeEventListener("beforeunload",c)}}}(e,t.state,t.location,t.replace);const r=Io({location:"",base:e,go:function(e,t=!0){t||n.pauseListeners(),history.go(e)},createHref:Qo.bind(null,e)},t,n);return Object.defineProperty(r,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(r,"state",{enumerable:!0,get:()=>t.state.value}),r}function ls(e){return(e=location.host?e||location.pathname+location.search:"").indexOf("#")<0&&(e+="#"),ss(e)}function is(e){return"string"==typeof e||"symbol"==typeof e}const cs={path:"/",name:void 0,params:{},query:{},hash:"",fullPath:"/",matched:[],meta:{},redirectedFrom:void 0},as=Ro("nf");var us,fs;function ps(e,t){return Io(new Error,{type:e,[as]:!0},t)}function ds(e,t){return e instanceof Error&&as in e&&(null==t||!!(e.type&t))}(fs=us||(us={}))[fs.aborted=4]="aborted",fs[fs.cancelled=8]="cancelled",fs[fs.duplicated=16]="duplicated";const hs={sensitive:!1,strict:!1,start:!0,end:!0},ms=/[.+*?^${}()[\]/\\]/g;function gs(e,t){let n=0;for(;nt.length?1===t.length&&80===t[0]?1:-1:0}function vs(e,t){let n=0;const r=e.score,o=t.score;for(;n1&&("*"===i||"+"===i)&&t(`A repeatable param (${a}) must be alone in its segment. eg: '/:ids+.`),s.push({type:1,value:a,regexp:u,repeatable:"*"===i||"+"===i,optional:"*"===i||"?"===i})):t("Invalid state to consume buffer"),a="")}function p(){a+=i}for(;c{s(p)}:Vo}function s(e){if(is(e)){const t=r.get(e);t&&(r.delete(e),n.splice(n.indexOf(t),1),t.children.forEach(s),t.alias.forEach(s))}else{let t=n.indexOf(e);t>-1&&(n.splice(t,1),e.record.name&&r.delete(e.record.name),e.children.forEach(s),e.alias.forEach(s))}}function l(e){let t=0;for(;t=0;)t++;n.splice(t,0,e),e.record.name&&!Es(e)&&r.set(e.record.name,e)}return t=Ss({strict:!1,end:!0,sensitive:!1},t),e.forEach((e=>o(e))),{addRoute:o,resolve:function(e,t){let o,s,l,i={};if("name"in e&&e.name){if(o=r.get(e.name),!o)throw ps(1,{location:e});l=o.record.name,i=Io(function(e,t){let n={};for(let r of t)r in e&&(n[r]=e[r]);return n}(t.params,o.keys.filter((e=>!e.optional)).map((e=>e.name))),e.params),s=o.stringify(i)}else if("path"in e)s=e.path,o=n.find((e=>e.re.test(s))),o&&(i=o.parse(s),l=o.record.name);else{if(o=t.name?r.get(t.name):n.find((e=>e.re.test(t.path))),!o)throw ps(1,{location:e,currentLocation:t});l=o.record.name,i=Io({},t.params,e.params),s=o.stringify(i)}const c=[];let a=o;for(;a;)c.unshift(a.record),a=a.parent;return{name:l,path:s,params:i,matched:c,meta:ks(c)}},removeRoute:s,getRoutes:function(){return n},getRecordMatcher:function(e){return r.get(e)}}}function xs(e){const t={},n=e.props||!1;if("component"in e)t.default=n;else for(let r in e.components)t[r]="boolean"==typeof n?n:n[r];return t}function Es(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function ks(e){return e.reduce(((e,t)=>Io(e,t.meta)),{})}function Ss(e,t){let n={};for(let r in e)n[r]=r in t?t[r]:e[r];return n}const Os=/#/g,Cs=/&/g,Rs=/\//g,As=/=/g,Ps=/\?/g,Fs=/\+/g,js=/%5B/g,$s=/%5D/g,Ms=/%5E/g,Is=/%60/g,Ts=/%7B/g,Vs=/%7C/g,Us=/%7D/g,Ns=/%20/g;function Ls(e){return encodeURI(""+e).replace(Vs,"|").replace(js,"[").replace($s,"]")}function Bs(e){return Ls(e).replace(Fs,"%2B").replace(Ns,"+").replace(Os,"%23").replace(Cs,"%26").replace(Is,"`").replace(Ts,"{").replace(Us,"}").replace(Ms,"^")}function Ds(e){return function(e){return Ls(e).replace(Os,"%23").replace(Ps,"%3F")}(e).replace(Rs,"%2F")}function qs(e){try{return decodeURIComponent(""+e)}catch(t){}return""+e}function zs(e){const t={};if(""===e||"?"===e)return t;const n=("?"===e[0]?e.slice(1):e).split("&");for(let r=0;re&&Bs(e))):[r&&Bs(r)]).forEach((e=>{void 0!==e&&(t+=(t.length?"&":"")+n,null!=e&&(t+="="+e))}))}return t}function Ks(e){const t={};for(let n in e){let r=e[n];void 0!==r&&(t[n]=Array.isArray(r)?r.map((e=>null==e?null:""+e)):null==r?r:""+r)}return t}function Gs(){let e=[];return{add:function(t){return e.push(t),()=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)}},list:()=>e,reset:function(){e=[]}}}function Hs(e,t,n,r,o){const s=r&&(r.enterCallbacks[o]=r.enterCallbacks[o]||[]);return()=>new Promise(((l,i)=>{const c=e=>{var c;!1===e?i(ps(4,{from:n,to:t})):e instanceof Error?i(e):"string"==typeof(c=e)||c&&"object"==typeof c?i(ps(2,{from:t,to:e})):(s&&r.enterCallbacks[o]===s&&"function"==typeof e&&s.push(e),l())},a=e.call(r&&r.instances[o],t,n,c);let u=Promise.resolve(a);e.length<3&&(u=u.then(c)),u.catch((e=>i(e)))}))}function Xs(e,t,n,r){const o=[];for(const l of e)for(const e in l.components){let i=l.components[e];if("beforeRouteEnter"===t||l.instances[e])if("object"==typeof(s=i)||"displayName"in s||"props"in s||"__vccOpts"in s){const s=(i.__vccOpts||i)[t];s&&o.push(Hs(s,n,r,l,e))}else{let s=i();s=s.catch(console.error),o.push((()=>s.then((o=>{if(!o)return Promise.reject(new Error(`Couldn't resolve component "${e}" at "${l.path}"`));const s=(i=o).__esModule||Co&&"Module"===i[Symbol.toStringTag]?o.default:o;var i;l.components[e]=s;const c=(s.__vccOpts||s)[t];return c&&Hs(c,n,r,l,e)()}))))}}var s;return o}function Js(e){const t=dr(Fo),n=dr(jo),r=Tr((()=>t.resolve(st(e.to)))),o=Tr((()=>{let{matched:e}=r.value,{length:t}=e;const o=e[t-1];let s=n.matched;if(!o||!s.length)return-1;let l=s.findIndex(Bo.bind(null,o));if(l>-1)return l;let i=Ys(e[t-2]);return t>1&&Ys(o)===i&&s[s.length-1].path!==i?s.findIndex(Bo.bind(null,e[t-2])):l})),s=Tr((()=>o.value>-1&&function(e,t){for(let n in t){let r=t[n],o=e[n];if("string"==typeof r){if(r!==o)return!1}else if(!Array.isArray(o)||o.length!==r.length||r.some(((e,t)=>e!==o[t])))return!1}return!0}(n.params,r.value.params))),l=Tr((()=>o.value>-1&&o.value===n.matched.length-1&&Do(n.params,r.value.params)));return{route:r,href:Tr((()=>r.value.href)),isActive:s,isExactActive:l,navigate:function(n={}){return function(e){if(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)return;if(e.defaultPrevented)return;if(void 0!==e.button&&0!==e.button)return;if(e.currentTarget&&e.currentTarget.getAttribute){const t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}e.preventDefault&&e.preventDefault();return!0}(n)?t[st(e.replace)?"replace":"push"](st(e.to)):Promise.resolve()}}}const Qs=In({name:"RouterLink",props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:"page"}},setup(e,{slots:t}){const n=He(Js(e)),{options:r}=dr(Fo),o=Tr((()=>({[Zs(e.activeClass,r.linkActiveClass,"router-link-active")]:n.isActive,[Zs(e.exactActiveClass,r.linkExactActiveClass,"router-link-exact-active")]:n.isExactActive})));return()=>{const r=t.default&&t.default(n);return e.custom?r:Vr("a",{"aria-current":n.isExactActive?e.ariaCurrentValue:null,href:n.href,onClick:n.navigate,class:o.value},r)}}});function Ys(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}const Zs=(e,t,n)=>null!=e?e:null!=t?t:n;function el(e,t){if(!e)return null;const n=e(t);return 1===n.length?n[0]:n}const tl=In({name:"RouterView",inheritAttrs:!1,props:{name:{type:String,default:"default"},route:Object},setup(e,{attrs:t,slots:n}){const r=dr($o),o=Tr((()=>e.route||r.value)),s=dr(Po,0),l=Tr((()=>o.value.matched[s]));pr(Po,s+1),pr(Ao,l),pr($o,o);const i=ot(c);var c;return yn((()=>[i.value,l.value,e.name]),(([e,t,n],[r,o,s])=>{t&&(t.instances[n]=e,o&&o!==t&&e&&e===r&&(t.leaveGuards.size||(t.leaveGuards=o.leaveGuards),t.updateGuards.size||(t.updateGuards=o.updateGuards))),!e||!t||o&&Bo(t,o)&&r||(t.enterCallbacks[n]||[]).forEach((t=>t(e)))}),{flush:"post"}),()=>{const r=o.value,s=l.value,c=s&&s.components[e.name],a=e.name;if(!c)return el(n.default,{Component:c,route:r});const u=s.props[e.name],f=u?!0===u?r.params:"function"==typeof u?u(r):u:null,p=Vr(c,Io({},f,t,{onVnodeUnmounted:e=>{e.component.isUnmounted&&(s.instances[a]=null)},ref:i}));return el(n.default,{Component:p,route:r})||p}}});function nl(e){const t=ws(e.routes,e);let n=e.parseQuery||zs,r=e.stringifyQuery||Ws,o=e.history;const s=Gs(),l=Gs(),i=Gs(),c=ot(cs,!0);let a=cs;Mo&&e.scrollBehavior&&"scrollRestoration"in history&&(history.scrollRestoration="manual");const u=To.bind(null,(e=>""+e)),f=To.bind(null,Ds),p=To.bind(null,qs);function d(e,s){if(s=Io({},s||c.value),"string"==typeof e){let r=No(n,e,s.path),l=t.resolve({path:r.path},s),i=o.createHref(r.fullPath);return Io(r,l,{params:p(l.params),hash:qs(r.hash),redirectedFrom:void 0,href:i})}let l;"path"in e?l=Io({},e,{path:No(n,e.path,s.path).path}):(l=Io({},e,{params:f(e.params)}),s.params=f(s.params));let i=t.resolve(l,s);const a=e.hash||"";i.params=u(p(i.params));const d=function(e,t){let n=t.query?e(t.query):"";return t.path+(n&&"?")+n+(t.hash||"")}(r,Io({},e,{hash:(h=a,Ls(h).replace(Ts,"{").replace(Us,"}").replace(Ms,"^")),path:i.path}));var h;let m=o.createHref(d);return Io({fullPath:d,hash:a,query:r===Ws?Ks(e.query):e.query},i,{redirectedFrom:void 0,href:m})}function h(e){return"string"==typeof e?No(n,e,c.value.path):Io({},e)}function m(e,t){if(a!==e)return ps(8,{from:t,to:e})}function g(e){return y(e)}function v(e){const t=e.matched[e.matched.length-1];if(t&&t.redirect){const{redirect:n}=t;let r="function"==typeof n?n(e):n;return"string"==typeof r&&(r=r.indexOf("?")>-1||r.indexOf("#")>-1?r=h(r):{path:r}),Io({query:e.query,hash:e.hash,params:e.params},r)}}function y(e,t){const n=a=d(e),o=c.value,s=e.state,l=e.force,i=!0===e.replace,u=v(n);if(u)return y(Io(h(u),{state:s,force:l,replace:i}),t||n);const f=n;let p;return f.redirectedFrom=t,!l&&function(e,t,n){let r=t.matched.length-1,o=n.matched.length-1;return r>-1&&r===o&&Bo(t.matched[r],n.matched[o])&&Do(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}(r,o,n)&&(p=ps(16,{to:f,from:o}),P(o,o,!0,!1)),(p?Promise.resolve(p):_(f,o)).catch((e=>ds(e)?e:R(e))).then((e=>{if(e){if(ds(e,2))return y(Io(h(e.to),{state:s,force:l,replace:i}),t||f)}else e=x(f,o,!0,i,s);return w(f,o,e),e}))}function b(e,t){const n=m(e,t);return n?Promise.reject(n):Promise.resolve()}function _(e,t){let n;const[r,o,i]=function(e,t){const n=[],r=[],o=[],s=Math.max(t.matched.length,e.matched.length);for(let l=0;lBo(e,s)))?r.push(s):n.push(s));const i=e.matched[l];i&&(t.matched.find((e=>Bo(e,i)))||o.push(i))}return[n,r,o]}(e,t);n=Xs(r.reverse(),"beforeRouteLeave",e,t);for(const s of r)s.leaveGuards.forEach((r=>{n.push(Hs(r,e,t))}));const c=b.bind(null,e,t);return n.push(c),rl(n).then((()=>{n=[];for(const r of s.list())n.push(Hs(r,e,t));return n.push(c),rl(n)})).then((()=>{n=Xs(o,"beforeRouteUpdate",e,t);for(const r of o)r.updateGuards.forEach((r=>{n.push(Hs(r,e,t))}));return n.push(c),rl(n)})).then((()=>{n=[];for(const r of e.matched)if(r.beforeEnter&&t.matched.indexOf(r)<0)if(Array.isArray(r.beforeEnter))for(const o of r.beforeEnter)n.push(Hs(o,e,t));else n.push(Hs(r.beforeEnter,e,t));return n.push(c),rl(n)})).then((()=>(e.matched.forEach((e=>e.enterCallbacks={})),n=Xs(i,"beforeRouteEnter",e,t),n.push(c),rl(n)))).then((()=>{n=[];for(const r of l.list())n.push(Hs(r,e,t));return n.push(c),rl(n)})).catch((e=>ds(e,8)?e:Promise.reject(e)))}function w(e,t,n){for(const r of i.list())r(e,t,n)}function x(e,t,n,r,s){const l=m(e,t);if(l)return l;const i=t===cs,a=Mo?history.state:{};n&&(r||i?o.replace(e.fullPath,Io({scroll:i&&a&&a.scroll},s)):o.push(e.fullPath,s)),c.value=e,P(e,t,n,i),A()}let E;function k(){E=o.listen(((e,t,n)=>{let r=d(e);const s=v(r);if(s)return void y(Io(s,{replace:!0}),r).catch(Vo);a=r;const l=c.value;var i,u;Mo&&(i=es(l.fullPath,n.delta),u=Yo(),ts.set(i,u)),_(r,l).catch((e=>ds(e,12)?e:ds(e,2)?(y(e.to,r).catch(Vo),Promise.reject()):(n.delta&&o.go(-n.delta,!1),R(e)))).then((e=>{(e=e||x(r,l,!1))&&n.delta&&o.go(-n.delta,!1),w(r,l,e)})).catch(Vo)}))}let S,O=Gs(),C=Gs();function R(e){return A(e),C.list().forEach((t=>t(e))),Promise.reject(e)}function A(e){S||(S=!0,k(),O.list().forEach((([t,n])=>e?n(e):t())),O.reset())}function P(t,n,r,o){const{scrollBehavior:s}=e;if(!Mo||!s)return Promise.resolve();let l=!r&&function(e){const t=ts.get(e);return ts.delete(e),t}(es(t.fullPath,0))||(o||!r)&&history.state&&history.state.scroll||null;return Ot().then((()=>s(t,n,l))).then((e=>e&&Zo(e))).catch(R)}const F=e=>o.go(e);let j;const $=new Set;return{currentRoute:c,addRoute:function(e,n){let r,o;return is(e)?(r=t.getRecordMatcher(e),o=n):o=e,t.addRoute(o,r)},removeRoute:function(e){let n=t.getRecordMatcher(e);n&&t.removeRoute(n)},hasRoute:function(e){return!!t.getRecordMatcher(e)},getRoutes:function(){return t.getRoutes().map((e=>e.record))},resolve:d,options:e,push:g,replace:function(e){return g(Io(h(e),{replace:!0}))},go:F,back:()=>F(-1),forward:()=>F(1),beforeEach:s.add,beforeResolve:l.add,afterEach:i.add,onError:C.add,isReady:function(){return S&&c.value!==cs?Promise.resolve():new Promise(((e,t)=>{O.add([e,t])}))},install(e){e.component("RouterLink",Qs),e.component("RouterView",tl),e.config.globalProperties.$router=this,Object.defineProperty(e.config.globalProperties,"$route",{enumerable:!0,get:()=>st(c)}),Mo&&!j&&c.value===cs&&(j=!0,g(o.location).catch((e=>{})));const t={};for(let r in cs)t[r]=Tr((()=>c.value[r]));e.provide(Fo,this),e.provide(jo,He(t)),e.provide($o,c);let n=e.unmount;$.add(e),e.unmount=function(){$.delete(e),$.size<1&&(E(),c.value=cs,j=!1,S=!1),n()}}}}function rl(e){return e.reduce(((e,t)=>e.then((()=>t()))),Promise.resolve())}export{Wn as F,sr as a,cr as b,Zn as c,In as d,ir as e,Ur as f,bo as g,Pn as h,wo as i,zt as j,fo as k,po as l,Nt as m,ao as n,Qn as o,xo as p,nl as q,Dn as r,ls as s,u as t,Oo as u,co as v,Wt as w}; diff --git a/build/public/index.html b/build/public/index.html index 186b755..4579d2b 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,8 +4,8 @@ 🧩 jigsaw.hyottoko.club - - + + diff --git a/build/server/main.js b/build/server/main.js index 5cdd983..108304e 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -344,6 +344,7 @@ const INPUT_EV_PLAYER_COLOR = 7; const INPUT_EV_PLAYER_NAME = 8; const INPUT_EV_MOVE = 9; const INPUT_EV_TOGGLE_PREVIEW = 10; +const INPUT_EV_TOGGLE_SOUNDS = 11; const CHANGE_DATA = 1; const CHANGE_TILE = 2; const CHANGE_PLAYER = 3; @@ -366,6 +367,7 @@ var Protocol = { INPUT_EV_PLAYER_COLOR, INPUT_EV_PLAYER_NAME, INPUT_EV_TOGGLE_PREVIEW, + INPUT_EV_TOGGLE_SOUNDS, CHANGE_DATA, CHANGE_TILE, CHANGE_PLAYER, @@ -871,7 +873,7 @@ const getPuzzleWidth = (gameId) => { const getPuzzleHeight = (gameId) => { return GAMES[gameId].puzzle.info.height; }; -function handleInput$1(gameId, playerId, input, ts) { +function handleInput$1(gameId, playerId, input, ts, onSnap) { const puzzle = GAMES[gameId].puzzle; const evtInfo = getEvtInfo(gameId, playerId); const changes = []; @@ -1054,6 +1056,9 @@ function handleInput$1(gameId, playerId, input, ts) { changeData(gameId, { finished: ts }); _dataChange(); } + if (onSnap) { + onSnap(playerId); + } } else { // Snap to other tiles @@ -1101,6 +1106,9 @@ function handleInput$1(gameId, playerId, input, ts) { changePlayer(gameId, playerId, { d, ts }); _playerChange(); } + if (snapped && onSnap) { + onSnap(playerId); + } } } else { diff --git a/src/common/GameCommon.ts b/src/common/GameCommon.ts index 629248a..6e7f701 100644 --- a/src/common/GameCommon.ts +++ b/src/common/GameCommon.ts @@ -530,7 +530,8 @@ function handleInput( gameId: string, playerId: string, input: Input, - ts: Timestamp + ts: Timestamp, + onSnap?: (playerId: string) => void ): Array { const puzzle = GAMES[gameId].puzzle const evtInfo = getEvtInfo(gameId, playerId) @@ -734,6 +735,9 @@ function handleInput( changeData(gameId, { finished: ts }) _dataChange() } + if (onSnap) { + onSnap(playerId) + } } else { // Snap to other tiles const check = ( @@ -789,6 +793,9 @@ function handleInput( changePlayer(gameId, playerId, { d, ts }) _playerChange() } + if (snapped && onSnap) { + onSnap(playerId) + } } } else { changePlayer(gameId, playerId, { d, ts }) diff --git a/src/common/Protocol.ts b/src/common/Protocol.ts index 421b80a..1a5e124 100644 --- a/src/common/Protocol.ts +++ b/src/common/Protocol.ts @@ -58,6 +58,7 @@ const INPUT_EV_PLAYER_COLOR = 7 const INPUT_EV_PLAYER_NAME = 8 const INPUT_EV_MOVE = 9 const INPUT_EV_TOGGLE_PREVIEW = 10 +const INPUT_EV_TOGGLE_SOUNDS = 11 const CHANGE_DATA = 1 const CHANGE_TILE = 2 @@ -87,6 +88,7 @@ export default { INPUT_EV_PLAYER_NAME, INPUT_EV_TOGGLE_PREVIEW, + INPUT_EV_TOGGLE_SOUNDS, CHANGE_DATA, CHANGE_TILE, diff --git a/src/frontend/click.mp3 b/src/frontend/click.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..af958b7f947a40692fd0f3f03a0f84708936ed06 GIT binary patch literal 6783 zcmeI0XHb((|L+3?h=52{K#&$f73sZ54<(e)rFVjWz)eR@=phv89iPzlNhKU zo2ur3I)D1~2jnm~WI0x)^XN>Ti>`wCxmnl5{H2KAn?^Jad; zb#ZP5?K3Cac!`ItR^bnPUHr=b#UL^2^@}gXg-81wf!gq%9Fyy-tu^?yy8bzTW88W* z{OLZQSz}}KoZTDA7d-R>8KSM<)l=M-BA1PJ9r)f%IKTsUu7Yqpv)l7C%t4o_l9tW? zkX!EgRGePMRjNC81kTXyHQ3@m!Ht!UsEG&w@Bjip7HUFZ+SOglEGHti_&$ui1AbR8 zTWCfc2ue1c{RuzaL}IK5H6j`|(5=9p%x^`|5ZWg!0z|1c1!E=yLvtTLu%&vWLco{t zx;Gf)f#nXmZ&QQs#Ue1n1!Mc*5yIPv=M*JL35^mBEc9j>6H8QHq{+-uXoHtsWxYth~%q2 zu{~7*hh&4Zk@*yP$#2zyUP8y?rWujk+@+jmZT!NlH8VfXKhKX%vPOD!mp1Wy7i=^} z21zxU_OGNbmued|v^%H|WI8X(6(wiw1#Gx0(tIi;0gw>@i16?MC;^tt;BNAW)<2s& z*y??TVw!j$=U~zBGD0cL#HIpvqAhlf4&&x^N@;*MWLERZbz4Xbk%h!xBc)D~WHW^# z#;=M}hn}}TA-XpdHd4xx@~t&Jl)12Yk-vU_(aGgIn=j3gMevcQn@>?o#e@2Ecjj#yrck1boMf1HqdiV)(tkh*8k|WvRXgq+A-XgQ}R4y~s1yo1A zqQ=%3FcPh7>Sp{KA#xiN?AHMxeS7`aFTf$E!*jS1>kyu(0>krmRLG>zmb}n`E*EWm znxyxHv@xGypW(oTl)0Pc`58`VcH6`zWs~Y%?VwJYU2LZ2vsB|>-Mjw1INZ&6kIC9> z>i`=7`@e!IUL--jmK|Y)gq+7l-^Gt0>&*)ChX5IDKq^PC788rY7#l<=q!thy!<;Um z9{GSCjVfiOGNDnnRvs9&b><`?E6bp;iJ(NOed!vmxo-!72sDu-sOy11wdyfBj1s>i z?=k4vWHr*)))oOH@aXZ-0$93dvEU>EJTiMm3Zvdve6NbAxhW3%T)hgLa_`DM?#Zfr4r6sF!vxmS z;Z=82D3@7KuS#7^bSG5&z-Em^H_WVw^1&dMJMu%YE=+y8&w^|GL<=%=GL{;dX&|gu zATxF~>~oH^g!Wc-$MrPy2b;ISN~c+&uSB1T=D03rzMoHDh&8I8N;bgyGM+xv=N{xLhitvKCg!XSpJ<;d;ov~FtgovMjqwyZS=hCQ0t!t z&vJ#NwmOx%+eFBgoTbn z4fwE=sq1~)#h7iR>3BVfH4%N6?Ng&;{Hm`^)xv!oq#>C>&qxx#>(Sp<^d3mV| zuZ$|BI^KOP&<@&kXsZj7U-w^&47ylsH9puC7haqzn<awM~qb;b)=>oSq>{t1^`FtfpddUAMr@8CX;-{(D)9qHIE;oE3W~5>oO{af{U8_bq}cK&|Lv ztDgiesm>)~qVwx{7B+mCKVy3F9zf-a`Cuk80d=KdenCwtN=<0d<6wBQIQbtMg+inV zD=%EIH8+qnTpf2=WB(N&^3r(>X$Js!BU+d@asjj!Z{&Am?+r{BJ6#87_&>BPA*()C zVEB;~ai8BOvY^POir;W^`=kGRv?l*YTToM8feI`?Y$fy5tQ?0s)xlObX$F3SKzLqO zVHmj7R4Y3%N(+xjxa_BEg@*LURfiG+8HOh6P$5YP8u5$I z4IgVx)A|$H7pk07#~Lgjx2` z(As>&Lxo<;_X^jdxZ^aGXE$tAX5LU8l$F;T=uly0TZpz%)o5IZp1a4bg;T`OGY9wL zlTM8U?c*+gZ9NsUu-kw2aP@-_D!ldJuXxAR%Rk6zEefs^GctTux9x;l#V4dVO*i>pU?Oou5fnyvw}j*cb@qQ>%ZR)6q4vODpaP&AbnL z`)Xw$_Xk(z?EkOhhEc!`EuBlP`}rQvn{oEGui?uU3OnHOfwkhzm%kZ27k(6(9D?q? zCKCFx-GSTJOF$9C9oZk@>1KsjC}MK4pl?H5WGFf!y@$T~l2I0 zC2u)v3vPI7&MDu5jgQ^^zG5SjsCdczRkYM;>~gu?t1; zdDSm@dmp(`WD=6h?55`rcX{!J$?EzNBP0gUZjUTl<0V3UQqI>oIiBfBeT8Sj$nruP zO^<;+hymA4yCvqui-3cG@*_6n*#iV zahfz{-5KF`d+?5{4z7WPh6DX zKEkYHdgOlRLUJu9Ew8gI6DJ??+7@#iv$jVV8k7netq{?Z$>^3R1ypyO--H*cOpr96 zhjZ{n%JA2bbX&S?+{nqFX`YpphcQ8~C3@nXooDU1inU(0!?E%B4u1C@@+w+;63KA& zZOg!o3Oyu09!(?kO1q#K5W{z;$@c?QWF$N=7IcTu0Q0eRK;W* zz4|t>ING*ex977=I(`cmx-(`D=dF0vuUbb7pLc2)d)`a$h^28R=}~88?F}Ho{ z%k)QH&Ypc^{MT`-C}4^9SkOHUp;u%V%=XZ9xL&CIP7!6)@%xgigZ%LeKIm^tSy#7@ zuhL^!W1qN@CRS>QS{^QmmGu!9vWDo57NN=lWgx%bBm)-Q=-X& zr@y6lTC@Iy>VN*xcD1lsfG%y205EnKYOzehnkNHMo2Vo4n01}_F^LW_CIy3XBanR1O;!@6ud}-UxdM-Qt~(JsfK#}c4v<;-3~MAAtHCln_af|_e(FTi&o;wv8agV3cqux{ z&)Wz?K7D1Gmc?D>+Fzi6=K9(}WJ+Crq^3BVa1*^Xaq(S&VTE6i;(&;|)+qZtXE{7D zr3ec=N>uyoQblPgWzKf|zNzyblt%G2u(Ke_jeVk+uRKveYqwJRX^KO zIc14n$k$XA4qK_l^PdH{#6C^ZkP>!P)$sSWt3%LSvvzfSmH%cRrrDR5CMn1&&$0K- zO~beP=DJOGzb&gX^PYg3m3iZ7yRu}R>)um{!%yo0UQjZKf$2aUpC5}F2hr6qjDY3F zhNfntds)L&Z{GI(8+$fTz!7cDqjabTYWNq@A=Hv`mz{SP?1>R8Es_l-4-h?d(f~t9 zSoHV`KBv(%vM|Ou!t&$r2gtZQ#QeUm`x=u~AWhMP)hQm^(wJdN(p zuc*9;#f1JTygm;?2CfE(AUBNk5?#gYHL_Y{Wrq0#*J*gy@NC z7C1SJ@40+(IncSe`HHAz-si*&_b3aqL06#h9{5pRU3rP1c+)2s0(0#a8%dI!KI6xZ z;Z3nLK*G7Fo>8W|$!?62&z_pGyG)ysk}?kT1-48enqMZ_8a|>EC1OxP%aA;d(qkI9 z^8xF31lFxx;ch8h8-1)T-ytG90(&;R+~BiuxIrnrP{N4_xaoKy-f<-0&5*NCNeHbgQ+RRuyCW_ zqDhGQdmt~kJa}#$r+kP4qMS5??mYYvTOMF#gSOYJ%;DWBe1VaZ{MT^^(Kp_UXPcFN zS({VuD{{^u8Z&(X7PcU;@4cumf|3+2x~uz0DVUT|!hL&Ojq#XbLX+aE^R)DOkE3RY zYwK2()rJo8xc<%6?>PGjmJb|1u^XJ#~jK%9oeDqwSwC8%7T0SJ{D+5RkT3hH^{7on+lZRepPt!ONPuq=58H z*@W>oz~6TT31x#u3cP#ds*r)^B4T!2Zj8TteVd&JbL%YLObt$+o{Q=nhLgTXZdrBU z+rtD%ayso-uLo>YtYeXQ6m15>L;0#n2nr&aBD8?Qct^#YZKXk5F#AYd&9HR|?RW-i zC5a@9L5Ua^97FEY#haxB=hYQ0lio{O7@0EL?Q&%Ho(N7-T@49{(_n3cir12{JJ`_< z303ZSsL_||o6=J{_eiiUl^A~xG}Zbh%rjSf0Y1N6n&~hs9$f6OYJul&sjg1zaKhMp%HUG1_BBkHak;K+xA`Dh#Y}lZhZ5`!&&ONFtdLBGN z?UCKLeOu7tbUbAa4)~`gN|3j{7Vi6 z🖼️ Toggle preview:
Space
🧩✔️ Toggle fixed pieces:
F
🧩❓ Toggle loose pieces:
G
+ 🔉 Toggle sounds:
M
diff --git a/src/frontend/components/SettingsOverlay.vue b/src/frontend/components/SettingsOverlay.vue index 4a940db..01155e4 100644 --- a/src/frontend/components/SettingsOverlay.vue +++ b/src/frontend/components/SettingsOverlay.vue @@ -13,6 +13,10 @@ + + + + diff --git a/src/frontend/game.ts b/src/frontend/game.ts index b253bb9..002a26e 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -31,6 +31,9 @@ declare global { // @ts-ignore const images = import.meta.globEager('./*.png') +// @ts-ignore +const sounds = import.meta.globEager('./*.mp3') + export const MODE_PLAY = 'play' export const MODE_REPLAY = 'replay' @@ -46,6 +49,7 @@ interface Hud { setPiecesTotal: (v: number) => void setConnectionState: (v: number) => void togglePreview: () => void + toggleSoundsEnabled: () => void setReplaySpeed?: (v: number) => void setReplayPaused?: (v: boolean) => void } @@ -171,6 +175,9 @@ function EventAdapter (canvas: HTMLCanvasElement, window: any, viewport: any) { PIECE_VIEW_LOOSE = !PIECE_VIEW_LOOSE RERENDER = true } + if (ev.key === 'M' || ev.key === 'm') { + addEvent([Protocol.INPUT_EV_TOGGLE_SOUNDS]) + } }) const addEvent = (event: GameEvent) => { @@ -233,6 +240,9 @@ export async function main( return MODE === MODE_REPLAY || player.id !== clientId } + const click = sounds['./click.mp3'].default + const clickAudio = new Audio(click) + const cursorGrab = await Graphics.loadImageToBitmap(images['./grab.png'].default) const cursorHand = await Graphics.loadImageToBitmap(images['./hand.png'].default) const cursorGrabMask = await Graphics.loadImageToBitmap(images['./grab_mask.png'].default) @@ -404,6 +414,13 @@ export async function main( let finished = longFinished const justFinished = () => finished && !longFinished + const playerSoundEnabled = (): boolean => { + const enabled = localStorage.getItem('sound_enabled') + if (enabled === null) { + return false + } + return enabled === '1' + } const playerBgColor = () => { return (Game.getPlayerBgColor(gameId, clientId) || localStorage.getItem('bg_color') @@ -614,12 +631,24 @@ 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() } // LOCAL + SERVER CHANGES // ------------------------------------------------------------- const ts = TIME() - const changes = Game.handleInput(gameId, clientId, evt, ts) + const changes = Game.handleInput( + gameId, + clientId, + evt, + ts, + (playerId: string) => { + if (playerSoundEnabled()) { + clickAudio.play() + } + } + ) if (changes.length > 0) { RERENDER = true } @@ -787,6 +816,9 @@ export async function main( localStorage.setItem('player_name', value) evts.addEvent([Protocol.INPUT_EV_PLAYER_NAME, value]) }, + onSoundsEnabledChange: (value: boolean) => { + localStorage.setItem('sound_enabled', value ? '1' : '0') + }, replayOnSpeedUp, replayOnSpeedDown, replayOnPauseToggle, @@ -795,6 +827,7 @@ export async function main( background: playerBgColor(), color: playerColor(), name: playerName(), + soundsEnabled: playerSoundEnabled(), }, disconnect: Communication.disconnect, connect: connect, diff --git a/src/frontend/views/Game.vue b/src/frontend/views/Game.vue index 51ec604..fbe3b77 100644 --- a/src/frontend/views/Game.vue +++ b/src/frontend/views/Game.vue @@ -70,12 +70,14 @@ export default defineComponent({ background: '', color: '', name: '', + soundsEnabled: false, }, previewImageUrl: '', setHotkeys: (v: boolean) => {}, onBgChange: (v: string) => {}, onColorChange: (v: string) => {}, onNameChange: (v: string) => {}, + onSoundsEnabledChange: (v: boolean) => {}, disconnect: () => {}, connect: () => {}, }, @@ -94,6 +96,9 @@ export default defineComponent({ this.$watch(() => this.g.player.name, (value: string) => { this.g.onNameChange(value) }) + this.$watch(() => this.g.player.soundsEnabled, (value: boolean) => { + this.g.onSoundsEnabledChange(value) + }) this.g = await main( `${this.$route.params.id}`, // @ts-ignore @@ -111,6 +116,7 @@ export default defineComponent({ setPiecesTotal: (v: number) => { this.piecesTotal = v }, setConnectionState: (v: number) => { this.connectionState = v }, togglePreview: () => { this.toggle('preview', false) }, + toggleSoundsEnabled: () => { this.g.player.soundsEnabled = !this.g.player.soundsEnabled }, } ) }, diff --git a/src/frontend/views/Replay.vue b/src/frontend/views/Replay.vue index c4e1d33..23c13c5 100644 --- a/src/frontend/views/Replay.vue +++ b/src/frontend/views/Replay.vue @@ -69,12 +69,14 @@ export default defineComponent({ background: '', color: '', name: '', + soundsEnabled: false, }, previewImageUrl: '', setHotkeys: (v: boolean) => {}, onBgChange: (v: string) => {}, onColorChange: (v: string) => {}, onNameChange: (v: string) => {}, + onSoundsEnabledChange: (v: boolean) => {}, replayOnSpeedUp: () => {}, replayOnSpeedDown: () => {}, replayOnPauseToggle: () => {}, @@ -100,6 +102,9 @@ export default defineComponent({ this.$watch(() => this.g.player.name, (value: string) => { this.g.onNameChange(value) }) + this.$watch(() => this.g.player.soundsEnabled, (value: boolean) => { + this.g.onSoundsEnabledChange(value) + }) this.g = await main( `${this.$route.params.id}`, // @ts-ignore @@ -119,6 +124,7 @@ export default defineComponent({ setConnectionState: (v: number) => { this.connectionState = v }, setReplaySpeed: (v: number) => { this.replay.speed = v }, setReplayPaused: (v: boolean) => { this.replay.paused = v }, + toggleSoundsEnabled: () => { this.g.player.soundsEnabled = !this.g.player.soundsEnabled }, } ) }, From c2da0759b93bff6e267ed0affe9915a367bc0899 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sun, 30 May 2021 23:37:16 +0200 Subject: [PATCH 13/78] remove obsolete comment --- src/frontend/game.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/frontend/game.ts b/src/frontend/game.ts index 002a26e..36f12ea 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -485,7 +485,6 @@ export async function main( } if (MODE === MODE_PLAY) { - // TODO: register onServerChange function before connecting to server Communication.onServerChange((msg) => { const msgType = msg[0] const evClientId = msg[1] From 870f827e49b0ae680b84f3c248ba5f6dcd77a24e Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Mon, 31 May 2021 20:05:41 +0200 Subject: [PATCH 14/78] log only 5 min after game end + some type hinting :P --- build/server/main.js | 44 +++++++++++++++++++++--------- src/common/Types.ts | 1 + src/common/Util.ts | 7 +++-- src/frontend/Communication.ts | 14 +++++----- src/frontend/game.ts | 3 ++- src/server/Game.ts | 26 +++++++++++------- src/server/GameLog.ts | 17 ++++++++++++ src/server/WebSocketServer.ts | 2 +- src/server/main.ts | 50 ++++++++++++++++++++--------------- 9 files changed, 108 insertions(+), 56 deletions(-) diff --git a/build/server/main.js b/build/server/main.js index 108304e..39780ab 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -1186,6 +1186,17 @@ const PUBLIC_DIR = `${BASE_DIR}/build/public/`; const DB_PATCHES_DIR = `${BASE_DIR}/src/dbpatches`; const DB_FILE = `${BASE_DIR}/data/db.sqlite`; +const POST_GAME_LOG_DURATION = 5 * Time.MIN; +const shouldLog = (finishTs, currentTs) => { + // when not finished yet, always log + if (!finishTs) { + return true; + } + // in finished games, log max POST_GAME_LOG_DURATION after + // the game finished, to record winning dance moves etc :P + const timeSinceGameEnd = currentTs - finishTs; + return timeSinceGameEnd <= POST_GAME_LOG_DURATION; +}; const filename = (gameId) => `${DATA_DIR}/log_${gameId}.log`; const create = (gameId) => { const file = filename(gameId); @@ -1237,6 +1248,7 @@ const get = async (gameId, offset = 0, size = 10000) => { }); }; var GameLog = { + shouldLog, create, exists, log: _log, @@ -1689,21 +1701,25 @@ async function createGame(gameId, targetTiles, image, ts, scoreMode) { GameStorage.setDirty(gameId); } function addPlayer(gameId, playerId, ts) { - const idx = GameCommon.getPlayerIndexById(gameId, playerId); - const diff = ts - GameCommon.getStartTs(gameId); - if (idx === -1) { - GameLog.log(gameId, Protocol.LOG_ADD_PLAYER, playerId, diff); - } - else { - GameLog.log(gameId, Protocol.LOG_UPDATE_PLAYER, idx, diff); + 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); + } + else { + GameLog.log(gameId, Protocol.LOG_UPDATE_PLAYER, idx, diff); + } } GameCommon.addPlayer(gameId, playerId, ts); GameStorage.setDirty(gameId); } function handleInput(gameId, playerId, input, ts) { - const idx = GameCommon.getPlayerIndexById(gameId, playerId); - const diff = ts - GameCommon.getStartTs(gameId); - GameLog.log(gameId, Protocol.LOG_HANDLE_INPUT, idx, input, diff); + 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); + } const ret = GameCommon.handleInput(gameId, playerId, input, ts); GameStorage.setDirty(gameId); return ret; @@ -2069,6 +2085,8 @@ wss.on('message', async ({ socket, data }) => { const proto = socket.protocol.split('|'); const clientId = proto[0]; const gameId = proto[1]; + // TODO: maybe handle different types of data + // (but atm only string comes through) const msg = JSON.parse(data); const msgType = msg[0]; switch (msgType) { @@ -2152,12 +2170,12 @@ const gracefulShutdown = (signal) => { process.exit(); }; // used by nodemon -process.once('SIGUSR2', function () { +process.once('SIGUSR2', () => { gracefulShutdown('SIGUSR2'); }); -process.once('SIGINT', function () { +process.once('SIGINT', () => { gracefulShutdown('SIGINT'); }); -process.once('SIGTERM', function () { +process.once('SIGTERM', () => { gracefulShutdown('SIGTERM'); }); diff --git a/src/common/Types.ts b/src/common/Types.ts index f00c3e2..edb4a1e 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -15,6 +15,7 @@ export type Change = Array export type GameEvent = Array +export type ServerEvent = Array export type ClientEvent = Array export type EncodedPlayer = FixedLengthArray<[ diff --git a/src/common/Util.ts b/src/common/Util.ts index 020c568..a24ed05 100644 --- a/src/common/Util.ts +++ b/src/common/Util.ts @@ -29,8 +29,11 @@ const pad = (x: number, pad: string): string => { return pad.substr(0, pad.length - str.length) + str } -export const logger = (...pre: Array) => { - const log = (m: 'log'|'info'|'error') => (...args: Array) => { +type LogArgs = Array +type LogFn = (...args: LogArgs) => void + +export const logger = (...pre: string[]): { log: LogFn, error: LogFn, info: LogFn } => { + const log = (m: 'log'|'info'|'error') => (...args: LogArgs): void => { const d = new Date() const hh = pad(d.getHours(), '00') const mm = pad(d.getMinutes(), '00') diff --git a/src/frontend/Communication.ts b/src/frontend/Communication.ts index 0e1e9ea..5301faa 100644 --- a/src/frontend/Communication.ts +++ b/src/frontend/Communication.ts @@ -1,6 +1,6 @@ "use strict" -import { ClientEvent, EncodedGame, GameEvent, ReplayData } from '../common/Types' +import { ClientEvent, EncodedGame, GameEvent, ReplayData, ServerEvent } from '../common/Types' import Util, { logger } from '../common/Util' import Protocol from './../common/Protocol' @@ -17,8 +17,8 @@ const CONN_STATE_CLOSED = 4 // not connected (closed on purpose) let ws: WebSocket -let missedMessages: Array = [] -let changesCallback = (msg: Array) => { +let missedMessages: ServerEvent[] = [] +let changesCallback = (msg: ServerEvent) => { missedMessages.push(msg) } @@ -27,7 +27,7 @@ let connectionStateChangeCallback = (state: number) => { missedStateChanges.push(state) } -function onServerChange(callback: (msg: Array) => void): void { +function onServerChange(callback: (msg: ServerEvent) => void): void { changesCallback = callback for (const missedMessage of missedMessages) { changesCallback(missedMessage) @@ -77,8 +77,8 @@ function connect( setConnectionState(CONN_STATE_CONNECTED) send([Protocol.EV_CLIENT_INIT]) } - ws.onmessage = (e) => { - const msg = JSON.parse(e.data) + ws.onmessage = (e: MessageEvent) => { + const msg: ServerEvent = JSON.parse(e.data) const msgType = msg[0] if (msgType === Protocol.EV_SERVER_INIT) { const game = msg[1] @@ -102,7 +102,7 @@ function connect( throw `[ 2021-05-15 onerror ]` } - ws.onclose = (e) => { + ws.onclose = (e: CloseEvent) => { if (e.code === CODE_CUSTOM_DISCONNECT || e.code === CODE_GOING_AWAY) { setConnectionState(CONN_STATE_CLOSED) } else { diff --git a/src/frontend/game.ts b/src/frontend/game.ts index 36f12ea..c1e4317 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -21,6 +21,7 @@ import { ReplayData, Timestamp, GameEvent, + ServerEvent, } from '../common/Types' declare global { interface Window { @@ -485,7 +486,7 @@ export async function main( } if (MODE === MODE_PLAY) { - Communication.onServerChange((msg) => { + Communication.onServerChange((msg: ServerEvent) => { const msgType = msg[0] const evClientId = msg[1] const evClientSeq = msg[2] diff --git a/src/server/Game.ts b/src/server/Game.ts index a35a8d7..8ac87ff 100644 --- a/src/server/Game.ts +++ b/src/server/Game.ts @@ -1,12 +1,14 @@ import GameCommon from './../common/GameCommon' import { Change, Game, Input, ScoreMode, Timestamp } from './../common/Types' -import Util from './../common/Util' +import Util, { logger } from './../common/Util' import { Rng } from './../common/Rng' import GameLog from './GameLog' import { createPuzzle, PuzzleCreationImageInfo } from './Puzzle' import Protocol from './../common/Protocol' import GameStorage from './GameStorage' +const log = logger('Game.ts') + async function createGameObject( gameId: string, targetTiles: number, @@ -49,12 +51,14 @@ async function createGame( } function addPlayer(gameId: string, playerId: string, ts: Timestamp): void { - const idx = GameCommon.getPlayerIndexById(gameId, playerId) - const diff = ts - GameCommon.getStartTs(gameId) - if (idx === -1) { - GameLog.log(gameId, Protocol.LOG_ADD_PLAYER, playerId, diff) - } else { - GameLog.log(gameId, Protocol.LOG_UPDATE_PLAYER, idx, diff) + 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) + } else { + GameLog.log(gameId, Protocol.LOG_UPDATE_PLAYER, idx, diff) + } } GameCommon.addPlayer(gameId, playerId, ts) @@ -67,9 +71,11 @@ function handleInput( input: Input, ts: Timestamp ): Array { - const idx = GameCommon.getPlayerIndexById(gameId, playerId) - const diff = ts - GameCommon.getStartTs(gameId) - GameLog.log(gameId, Protocol.LOG_HANDLE_INPUT, idx, input, diff) + 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) + } const ret = GameCommon.handleInput(gameId, playerId, input, ts) GameStorage.setDirty(gameId) diff --git a/src/server/GameLog.ts b/src/server/GameLog.ts index ca1d306..d764304 100644 --- a/src/server/GameLog.ts +++ b/src/server/GameLog.ts @@ -1,11 +1,27 @@ 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' import { DATA_DIR } from './../server/Dirs' const log = logger('GameLog.js') +const POST_GAME_LOG_DURATION = 5 * Time.MIN + +const shouldLog = (finishTs: Timestamp, currentTs: Timestamp): boolean => { + // when not finished yet, always log + if (!finishTs) { + return true + } + + // in finished games, log max POST_GAME_LOG_DURATION after + // the game finished, to record winning dance moves etc :P + const timeSinceGameEnd = currentTs - finishTs + return timeSinceGameEnd <= POST_GAME_LOG_DURATION +} + const filename = (gameId: string) => `${DATA_DIR}/log_${gameId}.log` const create = (gameId: string): void => { @@ -66,6 +82,7 @@ const get = async ( } export default { + shouldLog, create, exists, log: _log, diff --git a/src/server/WebSocketServer.ts b/src/server/WebSocketServer.ts index 02f546f..7f648ba 100644 --- a/src/server/WebSocketServer.ts +++ b/src/server/WebSocketServer.ts @@ -56,7 +56,7 @@ class WebSocketServer { socket.close() return } - socket.on('message', (data: any) => { + socket.on('message', (data: WebSocket.Data) => { log.log(`ws`, socket.protocol, data) this.evt.dispatch('message', {socket, data}) }) diff --git a/src/server/main.ts b/src/server/main.ts index 4737326..4bfbfb8 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 { Game as GameType, GameSettings, ScoreMode } from '../common/Types' +import { ServerEvent, Game as GameType, GameSettings, ScoreMode } from '../common/Types' import GameStorage from './GameStorage' import Db from './Db' @@ -57,14 +57,14 @@ const storage = multer.diskStorage({ }) const upload = multer({storage}).single('file'); -app.get('/api/conf', (req, res) => { +app.get('/api/conf', (req, res): void => { res.send({ WS_ADDRESS: config.ws.connectstring, }) }) -app.get('/api/replay-data', async (req, res) => { - const q = req.query as any +app.get('/api/replay-data', async (req, res): Promise => { + const q: Record = req.query const offset = parseInt(q.offset, 10) || 0 if (offset < 0) { res.status(400).send({ reason: 'bad offset' }) @@ -95,8 +95,8 @@ app.get('/api/replay-data', async (req, res) => { res.send({ log, game: game ? Util.encodeGame(game) : null }) }) -app.get('/api/newgame-data', (req, res) => { - const q = req.query as any +app.get('/api/newgame-data', (req, res): void => { + const q: Record = req.query const tagSlugs: string[] = q.tags ? q.tags.split(',') : [] res.send({ images: Images.allImagesFromDb(db, tagSlugs, q.sort), @@ -104,10 +104,10 @@ app.get('/api/newgame-data', (req, res) => { }) }) -app.get('/api/index-data', (req, res) => { +app.get('/api/index-data', (req, res): void => { const ts = Time.timestamp() const games = [ - ...GameCommon.getAllGames().map((game: any) => ({ + ...GameCommon.getAllGames().map((game: GameType) => ({ id: game.id, hasReplay: GameLog.exists(game.id), started: GameCommon.getStartTs(game.id), @@ -131,7 +131,7 @@ interface SaveImageRequestData { tags: string[] } -const setImageTags = (db: Db, imageId: number, tags: string[]) => { +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') @@ -144,7 +144,7 @@ const setImageTags = (db: Db, imageId: number, tags: string[]) => { }) } -app.post('/api/save-image', express.json(), (req, res) => { +app.post('/api/save-image', express.json(), (req, res): void => { const data = req.body as SaveImageRequestData db.update('images', { title: data.title, @@ -160,8 +160,8 @@ app.post('/api/save-image', express.json(), (req, res) => { res.send({ ok: true }) }) -app.post('/api/upload', (req, res) => { - upload(req, res, async (err: any) => { +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!"); @@ -189,7 +189,7 @@ app.post('/api/upload', (req, res) => { }) }) -app.post('/api/newgame', express.json(), async (req, res) => { +app.post('/api/newgame', express.json(), async (req, res): Promise => { const gameSettings = req.body as GameSettings log.log(gameSettings) const gameId = Util.uniqId() @@ -211,13 +211,15 @@ app.use('/', express.static(PUBLIC_DIR)) const wss = new WebSocketServer(config.ws); -const notify = (data: any, sockets: Array) => { +const notify = (data: ServerEvent, sockets: Array): void => { for (const socket of sockets) { wss.notifyOne(data, socket) } } -wss.on('close', async ({socket} : {socket: WebSocket}) => { +wss.on('close', async ( + {socket} : { socket: WebSocket } +): Promise => { try { const proto = socket.protocol.split('|') // const clientId = proto[0] @@ -228,12 +230,16 @@ wss.on('close', async ({socket} : {socket: WebSocket}) => { } }) -wss.on('message', async ({socket, data} : { socket: WebSocket, data: any }) => { +wss.on('message', async ( + {socket, data} : { socket: WebSocket, data: WebSocket.Data } +): Promise => { try { const proto = socket.protocol.split('|') const clientId = proto[0] const gameId = proto[1] - const msg = JSON.parse(data) + // TODO: maybe handle different types of data + // (but atm only string comes through) + const msg = JSON.parse(data as string) const msgType = msg[0] switch (msgType) { case Protocol.EV_CLIENT_INIT: { @@ -303,7 +309,7 @@ const server = app.listen( wss.listen() -const memoryUsageHuman = () => { +const memoryUsageHuman = (): void => { const totalHeapSize = v8.getHeapStatistics().total_available_size const totalHeapSizeInGB = (totalHeapSize / 1024 / 1024 / 1024).toFixed(2) @@ -322,7 +328,7 @@ const persistInterval = setInterval(() => { memoryUsageHuman() }, config.persistence.interval) -const gracefulShutdown = (signal: any) => { +const gracefulShutdown = (signal: string): void => { log.log(`${signal} received...`) log.log('clearing persist interval...') @@ -342,14 +348,14 @@ const gracefulShutdown = (signal: any) => { } // used by nodemon -process.once('SIGUSR2', function () { +process.once('SIGUSR2', (): void => { gracefulShutdown('SIGUSR2') }) -process.once('SIGINT', function () { +process.once('SIGINT', (): void => { gracefulShutdown('SIGINT') }) -process.once('SIGTERM', function () { +process.once('SIGTERM', (): void => { gracefulShutdown('SIGTERM') }) From 69a949381f891a9bda72859fda19f1ecb67dbab8 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Mon, 31 May 2021 23:06:19 +0200 Subject: [PATCH 15/78] zoom to mouse even when using q/e --- src/common/GameCommon.ts | 10 +++++++++ src/frontend/Camera.ts | 15 ++++++++++++++ src/frontend/game.ts | 45 ++++++++++++++++++++++++++-------------- 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/common/GameCommon.ts b/src/common/GameCommon.ts index 6e7f701..610307d 100644 --- a/src/common/GameCommon.ts +++ b/src/common/GameCommon.ts @@ -625,6 +625,16 @@ function handleInput( const name = `${input[1]}`.substr(0, 16) changePlayer(gameId, playerId, { name, ts }) _playerChange() + } else if (type === Protocol.INPUT_EV_MOVE) { + const w = input[1] + const h = input[2] + const player = getPlayer(gameId, playerId) + if (player) { + const x = player.x - w + const y = player.y - h + changePlayer(gameId, playerId, { ts, x, y }) + _playerChange() + } } else if (type === Protocol.INPUT_EV_MOUSE_DOWN) { const x = input[1] const y = input[2] diff --git a/src/frontend/Camera.ts b/src/frontend/Camera.ts index fad305a..a336205 100644 --- a/src/frontend/Camera.ts +++ b/src/frontend/Camera.ts @@ -116,7 +116,20 @@ export default function Camera () { } } + const viewportDimToWorld = (viewportDim: Dim): Dim => { + const { w, h } = viewportDimToWorldRaw(viewportDim) + return { w: Math.round(w), h: Math.round(h) } + } + + const viewportDimToWorldRaw = (viewportDim: Dim): Dim => { + return { + w: viewportDim.w / curZoom, + h: viewportDim.h / curZoom, + } + } + return { + getCurrentZoom: () => curZoom, move, canZoom, zoom, @@ -126,5 +139,7 @@ export default function Camera () { worldDimToViewportRaw, viewportToWorld, viewportToWorldRaw, // not used outside + viewportDimToWorld, + viewportDimToWorldRaw, } } diff --git a/src/frontend/game.ts b/src/frontend/game.ts index c1e4317..c191e33 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -104,7 +104,7 @@ function EventAdapter (canvas: HTMLCanvasElement, window: any, viewport: any) { let ZOOM_OUT = false let SHIFT = false - const toWorldPoint = (x: number, y: number) => { + const toWorldPoint = (x: number, y: number): [number, number] => { const pos = viewport.viewportToWorld({x, y}) return [pos.x, pos.y] } @@ -133,28 +133,33 @@ function EventAdapter (canvas: HTMLCanvasElement, window: any, viewport: any) { } } + let lastMouse: [number, number]|null = null canvas.addEventListener('mousedown', (ev) => { + lastMouse = mousePos(ev) if (ev.button === 0) { - addEvent([Protocol.INPUT_EV_MOUSE_DOWN, ...mousePos(ev)]) + addEvent([Protocol.INPUT_EV_MOUSE_DOWN, ...lastMouse]) } }) canvas.addEventListener('mouseup', (ev) => { + lastMouse = mousePos(ev) if (ev.button === 0) { - addEvent([Protocol.INPUT_EV_MOUSE_UP, ...mousePos(ev)]) + addEvent([Protocol.INPUT_EV_MOUSE_UP, ...lastMouse]) } }) canvas.addEventListener('mousemove', (ev) => { - addEvent([Protocol.INPUT_EV_MOUSE_MOVE, ...mousePos(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, ...mousePos(ev)]) + addEvent([evt, ...lastMouse]) } }) @@ -194,23 +199,30 @@ function EventAdapter (canvas: HTMLCanvasElement, window: any, viewport: any) { return all } - const createKeyEvents = () => { - const amount = SHIFT ? 20 : 10 - const x = (LEFT ? amount : 0) - (RIGHT ? amount : 0) - const y = (UP ? amount : 0) - (DOWN ? amount : 0) - if (x !== 0 || y !== 0) { - addEvent([Protocol.INPUT_EV_MOVE, x, y]) + 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')) { - addEvent([Protocol.INPUT_EV_ZOOM_IN, ...canvasCenter()]) + const target = lastMouse || canvasCenter() + addEvent([Protocol.INPUT_EV_ZOOM_IN, ...target]) } } else if (ZOOM_OUT) { if (viewport.canZoom('out')) { - addEvent([Protocol.INPUT_EV_ZOOM_OUT, ...canvasCenter()]) + const target = lastMouse || canvasCenter() + addEvent([Protocol.INPUT_EV_ZOOM_OUT, ...target]) } } } @@ -596,10 +608,11 @@ export async function main( // ------------------------------------------------------------- const type = evt[0] if (type === Protocol.INPUT_EV_MOVE) { - const diffX = evt[1] - const diffY = evt[2] + const w = evt[1] + const h = evt[2] + const dim = viewport.worldDimToViewport({w, h}) RERENDER = true - viewport.move(diffX, diffY) + viewport.move(dim.w, dim.h) } else if (type === Protocol.INPUT_EV_MOUSE_MOVE) { if (_last_mouse_down && !Game.getFirstOwnedPiece(gameId, clientId)) { // move the cam From b7aecbb93365a7f97d6f8c708f42b1e6e1edfd35 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Mon, 31 May 2021 23:09:03 +0200 Subject: [PATCH 16/78] built --- .../assets/{index.d7fe3ee6.js => index.3ca85e0f.js} | 2 +- build/public/index.html | 2 +- build/server/main.js | 11 +++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) rename build/public/assets/{index.d7fe3ee6.js => index.3ca85e0f.js} (52%) diff --git a/build/public/assets/index.d7fe3ee6.js b/build/public/assets/index.3ca85e0f.js similarity index 52% rename from build/public/assets/index.d7fe3ee6.js rename to build/public/assets/index.3ca85e0f.js index 6f2483d..9e662aa 100644 --- a/build/public/assets/index.d7fe3ee6.js +++ b/build/public/assets/index.3ca85e0f.js @@ -1 +1 @@ -import{d as e,c as t,a as n,w as o,b as l,r as i,o as a,e as s,t as r,F as d,f as c,g as u,h as g,v as p,i as h,j as m,k as y,l as f,m as w,n as v,p as b,q as x,s as k,u as C}from"./vendor.18cd2d7e.js";var A=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const S={id:"app"},z={key:0,class:"nav"},P=s("Index"),I=s("New game");A.render=function(e,s,r,d,c,u){const g=i("router-link"),p=i("router-view");return a(),t("div",S,[e.showNav?(a(),t("ul",z,[n("li",null,[n(g,{class:"btn",to:{name:"index"}},{default:o((()=>[P])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:o((()=>[I])),_:1})])])):l("",!0),n(p)])};const T=864e5,_=e=>{const t=Math.floor(e/T);e%=T;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 E=1e3,D=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>_(t-e),O=_,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||D();return`${n} ${B(o,l)}`}}});const N={class:"game-info-text"},M=n("br",null,null,-1),G=n("br",null,null,-1),$=n("br",null,null,-1),R=s(" ↪️ Watch replay ");U.render=function(e,d,c,u,g,p){const h=i("router-link");return a(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",N,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),M,s(" 👥 "+r(e.game.players),1),G,s(" "+r(e.time(e.game.started,e.game.finished)),1),$])])),_:1},8,["to"]),e.game.hasReplay?(a(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[R])),_:1},8,["to"])):l("",!0)],4)};var V=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),F=n("h1",null,"Finished games",-1);V.render=function(e,o,l,s,r,u){const g=i("game-teaser");return a(),t("div",null,[j,(a(!0),t(d,null,c(e.gamesRunning,((e,o)=>(a(),t("div",{class:"game-teaser-wrap",key:o},[n(g,{game:e},null,8,["game"])])))),128)),F,(a(!0),t(d,null,c(e.gamesFinished,((e,o)=>(a(),t("div",{class:"game-teaser-wrap",key:o},[n(g,{game:e},null,8,["game"])])))),128))])};var L=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")}}});L.render=function(e,o,l,i,s,r){return a(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var W=e({name:"image-library",components:{ImageTeaser:L},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});W.render=function(e,n,o,l,s,r){const u=i("image-teaser");return a(),t("div",null,[(a(!0),t(d,null,c(e.images,((n,o)=>(a(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])};const q={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}}}};q.render=function(e,n,o,l,i,s){return a(),t("div",{style:s.style,title:o.title},null,12,["title"])};var H=e({name:"tags-input",props:{modelValue:{type:Array,required:!0}},emits:{"update:modelValue":null},data:()=>({input:"",values:[]}),created(){this.values=this.modelValue},methods:{onKeyUp(e){if(","===e.key)return this.add(),e.stopPropagation(),!1},add(){const e=this.input.replace(/,/g,"").trim();e&&(this.values.includes(e)||this.values.push(e),this.input="",this.$emit("update:modelValue",this.values))},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const Q=m()(((e,o,l,i,s,u)=>(a(),t("div",null,[g(n("input",{class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onKeydown:o[2]||(o[2]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[3]||(o[3]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[p,e.input]]),(a(!0),t(d,null,c(e.values,((n,o)=>(a(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));H.render=Q,H.__scopeId="data-v-771460ae";var Y=e({name:"new-image-dialog",components:{ResponsiveImage:q,TagsInput:H},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const o=new FileReader;o.readAsDataURL(n),o.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const K={key:0,class:"has-image"},Z={key:1},J={class:"upload"},X=n("span",{class:"btn"},"Upload File",-1),ee={class:"area-settings"},te=n("td",null,[n("label",null,"Title")],-1),ne=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),oe=n("td",null,[n("label",null,"Tags")],-1),le={class:"area-buttons"},ie=s("🧩 Post to gallery "),ae=n("br",null,null,-1),se=s(" + set up game");Y.render=function(e,o,l,s,r,d){const c=i("responsive-image"),h=i("tags-input");return a(),t("div",{class:"overlay new-image-dialog",onClick:o[8]||(o[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[7]||(o[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(a(),t("div",K,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(a(),t("div",Z,[n("label",J,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),X])]))],2),n("div",ee,[n("table",null,[n("tr",null,[te,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[3]||(o[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ne,n("tr",null,[oe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[4]||(o[4]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",le,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[5]||(o[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[6]||(o[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[ie,ae,se],8,["disabled"])])])])};var re=e({name:"edit-image-dialog",components:{ResponsiveImage:q,TagsInput:H},props:{image:{type:Object,required:!0}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const de={class:"area-image"},ce={class:"has-image"},ue={class:"area-settings"},ge=n("td",null,[n("label",null,"Title")],-1),pe=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"};var ye,fe,we,ve;re.render=function(e,o,l,s,r,d){const c=i("responsive-image"),h=i("tags-input");return a(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",de,[n("div",ce,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ue,[n("table",null,[n("tr",null,[ge,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),pe,n("tr",null,[he,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",me,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])},(fe=ye||(ye={}))[fe.Flat=0]="Flat",fe[fe.Out=1]="Out",fe[fe.In=-1]="In",(ve=we||(we={}))[ve.FINAL=0]="FINAL",ve[ve.ANY=1]="ANY";var be=e({name:"new-game-dialog",components:{ResponsiveImage:q},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:we.ANY}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const xe={class:"area-image"},ke={class:"has-image"},Ce={class:"area-settings"},Ae=n("td",null,[n("label",null,"Pieces")],-1),Se=n("td",null,[n("label",null,"Scoring: ")],-1),ze=s(" Any (Score when pieces are connected to each other or on final location)"),Pe=n("br",null,null,-1),Ie=s(" Final (Score when pieces are put to their final location)"),Te={class:"area-buttons"};be.render=function(e,o,l,s,r,d){const c=i("responsive-image");return a(),t("div",{class:"overlay new-game-dialog",onClick:o[6]||(o[6]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[5]||(o[5]=u((()=>{}),["stop"]))},[n("div",xe,[n("div",ke,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ce,[n("table",null,[n("tr",null,[Ae,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Se,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[y,e.scoreMode]]),ze]),Pe,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[y,e.scoreMode]]),Ie])])])])]),n("div",Te,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[4]||(o[4]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};class _e{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 _e(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const Ee=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},De=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=Ee(o.getHours(),"00"),i=Ee(o.getMinutes(),"00"),a=Ee(o.getSeconds(),"00");console[t](`${l}:${i}:${a}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var Be={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",_e.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||we.FINAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:_e.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}},Oe=e({components:{ImageLibrary:W,NewImageDialog:Y,EditImageDialog:re,NewGameDialog:be},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},methods:{toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${Be.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/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 Ue={class:"upload-image-teaser"},Ne=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),Me={key:0},Ge=s(" Tags: "),$e=s(" Sort by: "),Re=n("option",{value:"date_desc"},"Newest first",-1),Ve=n("option",{value:"date_asc"},"Oldest first",-1),je=n("option",{value:"alpha_asc"},"A-Z",-1),Fe=n("option",{value:"alpha_desc"},"Z-A",-1);Oe.render=function(e,o,s,u,p,h){const m=i("image-library"),y=i("new-image-dialog"),w=i("edit-image-dialog"),v=i("new-game-dialog");return a(),t("div",null,[n("div",Ue,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),Ne]),n("div",null,[e.tags.length>0?(a(),t("label",Me,[Ge,(a(!0),t(d,null,c(e.tags,((n,o)=>(a(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title),11,["onClick"])))),128))])):l("",!0),n("label",null,[$e,g(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[Re,Ve,je,Fe],544),[[f,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(a(),t(y,{key:0,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(a(),t(w,{key:1,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(a(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var Le=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 We={class:"scores"},qe=n("div",null,"Scores",-1),He=n("td",null,"⚡",-1),Qe=n("td",null,"💤",-1);Le.render=function(e,o,l,i,s,u){return a(),t("div",We,[qe,n("table",null,[(a(!0),t(d,null,c(e.actives,((e,o)=>(a(),t("tr",{key:o,style:{color:e.color}},[He,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(a(!0),t(d,null,c(e.idles,((e,o)=>(a(),t("tr",{key:o,style:{color:e.color}},[Qe,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var Ye=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 O(this.duration)}}});const Ke={class:"timer"};Ye.render=function(e,o,l,i,s,d){return a(),t("div",Ke,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),w(e.$slots,"default")])};var Ze=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 Je=n("td",null,[n("label",null,"Background: ")],-1),Xe=n("td",null,[n("label",null,"Color: ")],-1),et=n("td",null,[n("label",null,"Name: ")],-1),tt=n("td",null,[n("label",null,"Sounds: ")],-1);Ze.render=function(e,o,l,i,s,r){return a(),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,[Je,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[Xe,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[et,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])]),n("tr",null,[tt,n("td",null,[g(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[v,e.modelValue.soundsEnabled]])])])])])};var nt=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const ot={class:"preview"};nt.render=function(e,o,l,i,s,r){return a(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",ot,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var lt=1,it=4,at=2,st=3,rt=2,dt=4,ct=3,ut=9,gt=1,pt=2,ht=3,mt=4,yt=5,ft=6,wt=7,vt=8,bt=10,xt=11,kt=1,Ct=2,At=3;const St=De("Communication.js");let zt,Pt=[],It=e=>{Pt.push(e)},Tt=[],_t=e=>{Tt.push(e)};let Et=0;const Dt=e=>{Et!==e&&(Et=e,_t(e))};function Bt(e){if(2===Et)try{zt.send(JSON.stringify(e))}catch(t){St.info("unable to send message.. maybe because ws is invalid?")}}let Ot,Ut;var Nt={connect:function(e,t,n){return Ot=0,Ut={},Dt(3),new Promise((o=>{zt=new WebSocket(e,n+"|"+t),zt.onopen=()=>{Dt(2),Bt([st])},zt.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===it){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&&Ut[o])return void delete Ut[o];It(t)}}},zt.onerror=()=>{throw Dt(1),"[ 2021-05-15 onerror ]"},zt.onclose=e=>{4e3===e.code||1001===e.code?Dt(4):Dt(1)}}))},requestReplayData:async function(e,t,n){const o={gameId:e,offset:t,size:n},l=await fetch(`/api/replay-data${Be.asQueryArgs(o)}`);return await l.json()},disconnect:function(){zt&&zt.close(4e3),Ot=0,Ut={}},sendClientEvent:function(e){Ot++,Ut[Ot]=e,Bt([at,Ot,Ut[Ot]])},onServerChange:function(e){It=e;for(const t of Pt)It(t);Pt=[]},onConnectionStateChange:function(e){_t=e;for(const t of Tt)_t(t);Tt=[]},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},Mt=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Nt.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Nt.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const Gt={key:0,class:"overlay connection-lost"},$t={key:0,class:"overlay-content"},Rt=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Vt={key:1,class:"overlay-content"},jt=n("div",null,"Connecting...",-1);Mt.render=function(e,o,i,s,r,d){return e.show?(a(),t("div",Gt,[e.lostConnection?(a(),t("div",$t,[Rt,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(a(),t("div",Vt,[jt])):l("",!0)])):l("",!0)};var Ft=e({name:"help-overlay",emits:{bgclick:null}});const Lt=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),Wt=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),qt=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),Ht=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),Qt=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Yt=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),Kt=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),Zt=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Jt=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Xt=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),en=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1);Ft.render=function(e,o,l,i,s,r){return a(),t("div",{class:"overlay transparent",onClick:o[2]||(o[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:o[1]||(o[1]=u((()=>{}),["stop"]))},[Lt,Wt,qt,Ht,Qt,Yt,Kt,Zt,Jt,Xt,en])])};var tn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.550555f3.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="}),ln=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),an=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function sn(){let e=0,t=0,n=1;const o=(o,l)=>{e+=o/n,t+=l/n},l=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},i=o=>({x:o.x/n-e,y:o.y/n-t}),a=o=>({x:(o.x+e)*n,y:(o.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n});return{move:o,canZoom:e=>n!=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}=a(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:a,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:i}}function rn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var dn={createCanvas:rn,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=rn(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=rn(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 cn=De("Debug.js");let un=0,gn=0;var pn=e=>{un=performance.now(),gn=e},hn=e=>{const t=performance.now(),n=t-un;n>gn&&cn.log(e+": "+n),un=t};function mn(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 fn={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:mn,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 mn(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 wn=De("PuzzleGraphics.js");function vn(e,t){const n=Be.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var bn={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){wn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,i=n.tileDrawSize,a=o/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,i={x:l,y:l},r=fn.pointAdd(i,{x:o,y:0}),c=fn.pointAdd(r,{x:0,y:o}),u=fn.pointSub(c,{x:o,y:0});if(n.moveTo(i.x,i.y),0!==e.top)for(let o=0;oBe.decodePiece(xn[e].puzzle.tiles[t]),Un=(e,t)=>On(e,t).group,Nn=(e,t)=>{const n=xn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=xn[e].puzzle.info,o=Be.coordByPieceIdx(n,t),l=o.x*n.tileSize,i=o.y*n.tileSize;return{x:l,y:i}}(e,t);return fn.pointAdd(o,l)},Mn=(e,t)=>On(e,t).pos,Gn=e=>{const t=Xn(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}},$n=(e,t)=>{const n=Fn(e),o=On(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Rn=(e,t)=>On(e,t).z,Vn=(e,t)=>{for(const n of xn[e].puzzle.tiles){const e=Be.decodePiece(n);if(e.owner===t)return e.idx}return-1},jn=e=>xn[e].puzzle.info.tileDrawSize,Fn=e=>xn[e].puzzle.info.tileSize,Ln=e=>xn[e].puzzle.data.maxGroup,Wn=e=>xn[e].puzzle.data.maxZ;function qn(e,t){const n=xn[e].puzzle.info,o=Be.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Hn=(e,t,n)=>{for(const o of t)Bn(e,o,{z:n})},Qn=(e,t,n)=>{const o=Mn(e,t);Bn(e,t,{pos:fn.pointAdd(o,n)})},Yn=(e,t,n)=>{const o=jn(e),l=Gn(e),i=n;for(const a of t){const t=On(e,a);t.pos.x+n.xl.x+l.w&&(i.x=Math.min(l.x+l.w-t.pos.x+o,i.x)),t.pos.y+n.yl.y+l.h&&(i.y=Math.min(l.y+l.h-t.pos.y+o,i.y))}for(const a of t)Qn(e,a,i)},Kn=(e,t,n)=>{for(const o of t)Bn(e,o,{owner:n})};function Zn(e,t){const n=xn[e].puzzle.tiles,o=Be.decodePiece(n[t]),l=[];if(o.group)for(const i of n){const e=Be.decodePiece(i);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const Jn=(e,t)=>{const n=Cn(e,t);return n?n.points:0},Xn=e=>xn[e].puzzle.info.table.width,eo=e=>xn[e].puzzle.info.table.height;var to={setGame:function(e,t){xn[e]=t},exists:function(e){return!!xn[e]||!1},playerExists:Sn,getActivePlayers:function(e,t){const n=t-30*E;return zn(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*E;return zn(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Sn(e,t)?En(e,t,{ts:n}):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,n))},getFinishedPiecesCount:_n,getPieceCount:Pn,getImageUrl:function(e){return xn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){xn[e].puzzle.info.imageUrl=t},get:function(e){return xn[e]||null},getAllGames:function(){return Object.values(xn).sort(((e,t)=>Tn(e.id)===Tn(t.id)?t.puzzle.data.started-e.puzzle.data.started:Tn(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=Cn(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=Cn(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=Cn(e,t);return n?n.name:null},getPlayerIndexById:kn,getPlayerIdByIndex:function(e,t){return xn[e].players.length>t?Be.decodePlayer(xn[e].players[t]).id:null},changePlayer:En,setPlayer:An,setPiece:function(e,t,n){xn[e].puzzle.tiles[t]=Be.encodePiece(n)},setPuzzleData:function(e,t){xn[e].puzzle.data=t},getTableWidth:Xn,getTableHeight:eo,getPuzzle:e=>xn[e].puzzle,getRng:e=>xn[e].rng.obj,getPuzzleWidth:e=>xn[e].puzzle.info.width,getPuzzleHeight:e=>xn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return xn[e].puzzle.tiles.map(Be.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Vn(e,t);return n<0?null:xn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>xn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:jn,getFinalPiecePos:Nn,getStartTs:e=>xn[e].puzzle.data.started,getFinishTs:e=>xn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const i=xn[e].puzzle,a=function(e,t){return t in xn[e].evtInfos?xn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],r=()=>{s.push([kt,i.data])},d=t=>{s.push([Ct,Be.encodePiece(On(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=Cn(e,t);n&&s.push([At,Be.encodePlayer(n)])},g=n[0];if(g===ft){const l=n[1];En(e,t,{bgcolor:l,ts:o}),u()}else if(g===wt){const l=n[1];En(e,t,{color:l,ts:o}),u()}else if(g===vt){const l=`${n[1]}`.substr(0,16);En(e,t,{name:l,ts:o}),u()}else if(g===gt){const l={x:n[1],y:n[2]};En(e,t,{d:1,ts:o}),u(),a._last_mouse_down=l;const i=((e,t)=>{const n=xn[e].puzzle.info,o=xn[e].puzzle.tiles;let l=-1,i=-1;for(let a=0;al)&&(l=e.z,i=a)}return i})(e,l);if(i>=0){const n=Wn(e)+1;Dn(e,{maxZ:n}),r();const o=Zn(e,i);Hn(e,o,Wn(e)),Kn(e,o,t),c(o)}a._last_mouse=l}else if(g===ht){const l=n[1],i=n[2],s={x:l,y:i};if(null===a._last_mouse_down)En(e,t,{x:l,y:i,ts:o}),u();else{const n=Vn(e,t);if(n>=0){En(e,t,{x:l,y:i,ts:o}),u();const r=Zn(e,n);let d=fn.pointInBounds(s,Gn(e))&&fn.pointInBounds(a._last_mouse_down,Gn(e));for(const t of r){const n=$n(e,t);if(fn.pointInBounds(s,n)){d=!0;break}}if(d){const t=l-a._last_mouse_down.x,n=i-a._last_mouse_down.y;Yn(e,r,{x:t,y:n}),c(r)}}else En(e,t,{ts:o}),u();a._last_mouse_down=s}a._last_mouse=s}else if(g===pt){const s={x:n[1],y:n[2]},g=0;a._last_mouse_down=null;const p=Vn(e,t);if(p>=0){const n=Zn(e,p);Kn(e,n,0),c(n);const a=Mn(e,p),s=Nn(e,p);if(fn.pointDistance(s,a){for(const n of t)Bn(e,n,{owner:-1,z:1})})(e,n),c(n);let d=Jn(e,t);In(e)===we.FINAL?d+=n.length:In(e)===we.ANY&&(d+=1),En(e,t,{d:g,ts:o,points:d}),u(),_n(e)===Pn(e)&&(Dn(e,{finished:o}),r()),l&&l(t)}else{const n=(e,t,n,o)=>{const l=xn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=Un(e,t),l=Un(e,n);return!(!o||o!==l)})(e,t,n))return!1;const i=Mn(e,t),a=fn.pointAdd(Mn(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(fn.pointDistance(i,a){const o=xn[e].puzzle.tiles,l=Un(e,t),i=Un(e,n);let a;const s=[];l&&s.push(l),i&&s.push(i),l?a=l:i?a=i:(Dn(e,{maxGroup:Ln(e)+1}),r(),a=Ln(e));if(Bn(e,t,{group:a}),d(t),Bn(e,n,{group:a}),d(n),s.length>0)for(const r of o){const t=Be.decodePiece(r);s.includes(t.group)&&(Bn(e,t.idx,{group:a}),d(t.idx))}})(e,t,n),l=Zn(e,t);const s=((e,t)=>{let n=0;for(const o of t){const t=Rn(e,o);t>n&&(n=t)}return n})(e,l);return Hn(e,l,s),c(l),!0}return!1};let i=!1;for(const t of Zn(e,p)){const o=qn(e,t);if(n(e,t,o[0],[0,1])||n(e,t,o[1],[-1,0])||n(e,t,o[2],[0,-1])||n(e,t,o[3],[1,0])){i=!0;break}}if(i&&In(e)===we.ANY){const n=Jn(e,t)+1;En(e,t,{d:g,ts:o,points:n}),u()}else En(e,t,{d:g,ts:o}),u();i&&l&&l(t)}}else En(e,t,{d:g,ts:o}),u();a._last_mouse=s}else if(g===mt){const l=n[1],i=n[2];En(e,t,{x:l,y:i,ts:o}),u(),a._last_mouse={x:l,y:i}}else if(g===yt){const l=n[1],i=n[2];En(e,t,{x:l,y:i,ts:o}),u(),a._last_mouse={x:l,y:i}}else En(e,t,{ts:o}),u();return function(e,t,n){xn[e].evtInfos[t]=n}(e,t,a),s}};let no=-10,oo=20,lo=2,io=15;class ao{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*(lo+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;lo=t/2,io=t-lo;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 ao(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new ao(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]},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,ho=!0})),t}(l,dn.createCanvas()),v={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};Nt.onConnectionStateChange((e=>{i.setConnectionState(e)}));const b=async e=>{v.requesting=!0;const t=await Nt.requestReplayData(e,v.dataOffset,v.dataSize);return v.dataOffset+=v.dataSize,v.requesting=!1,t};let x=()=>0;const k=async()=>{if("play"===o){const o=await Nt.connect(n,e,t),l=Be.decodeGame(o);to.setGame(l.id,l),x=()=>D()}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=Be.decodeGame(t.game);to.setGame(n.id,n),v.requesting=!1,v.log=t.log,v.lastRealTs=D(),v.gameStartTs=parseInt(v.log[0][4],10),v.lastGameTs=v.gameStartTs,x=()=>v.lastGameTs}}ho=!0};await k();const C=to.getPieceDrawOffset(e),A=to.getPieceDrawSize(e),S=to.getPuzzleWidth(e),z=to.getPuzzleHeight(e),P=to.getTableWidth(e),I=to.getTableHeight(e),T={x:(P-S)/2,y:(I-z)/2},_={w:S,h:z},E={w:A,h:A},B=await bn.loadPuzzleBitmaps(to.getPuzzle(e)),O=new ro(w,to.getRng(e));O.init();const U=w.getContext("2d");w.classList.add("loaded");const N=sn();N.move(-(P-w.width)/2,-(I-w.height)/2);const M=function(e,t,n){let o=[],l=!0,i=!1,a=!1,s=!1,r=!1,d=!1,c=!1,u=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{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?i=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?a=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};e.addEventListener("mousedown",(e=>{0===e.button&&y([gt,...p(e)])})),e.addEventListener("mouseup",(e=>{0===e.button&&y([pt,...p(e)])})),e.addEventListener("mousemove",(e=>{y([ht,...p(e)])})),e.addEventListener("wheel",(e=>{if(n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?mt:yt;y([t,...p(e)])}})),t.addEventListener("keydown",(e=>m(!0,e))),t.addEventListener("keyup",(e=>m(!1,e))),t.addEventListener("keypress",(e=>{l&&(" "===e.key&&y([bt]),"F"!==e.key&&"f"!==e.key||(go=!go,ho=!0),"G"!==e.key&&"g"!==e.key||(po=!po,ho=!0),"M"!==e.key&&"m"!==e.key||y([xt]))}));const y=e=>{o.push(e)};return{addEvent:y,consumeAll:()=>{if(0===o.length)return[];const e=o.slice();return o=[],e},createKeyEvents:()=>{const e=u?20:10,t=(i?e:0)-(a?e:0),o=(s?e:0)-(r?e:0);0===t&&0===o||y([ut,t,o]),d&&c||(d?n.canZoom("in")&&y([mt,...h()]):c&&n.canZoom("out")&&y([yt,...h()]))},setHotkeys:e=>{l=e}}}(w,window,N),G=to.getImageUrl(e),$=()=>{const t=to.getStartTs(e),n=to.getFinishTs(e),o=x();i.setFinished(!!n),i.setDuration((n||o)-t)};$(),i.setPiecesDone(to.getFinishedPiecesCount(e)),i.setPiecesTotal(to.getPieceCount(e));const R=x();i.setActivePlayers(to.getActivePlayers(e,R)),i.setIdlePlayers(to.getIdlePlayers(e,R));const V=!!to.getFinishTs(e);let j=V;const F=()=>j&&!V,L=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},W=()=>to.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",q=()=>to.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let H="",Q="",Y=!1;const K=e=>{Y=e;const[t,n]=e?[H,"grab"]:[Q,"default"];w.style.cursor=`url('${t}') ${p} ${m}, ${n}`},Z=e=>{H=dn.colorizedCanvas(r,c,e).toDataURL(),Q=dn.colorizedCanvas(d,u,e).toDataURL(),K(Y)};Z(q());const J=()=>{i.setReplaySpeed&&i.setReplaySpeed(v.speeds[v.speedIdx]),i.setReplayPaused&&i.setReplayPaused(v.paused)};if("play"===o?setInterval($,1e3):"replay"===o&&J(),"play"===o)Nt.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,i]of o)switch(l){case At:{const n=Be.decodePlayer(i);n.id!==t&&(to.setPlayer(e,n.id,n),ho=!0)}break;case Ct:{const t=Be.decodePiece(i);to.setPiece(e,t.idx,t),ho=!0}break;case kt:to.setPuzzleData(e,i),ho=!0}j=!!to.getFinishTs(e)}));else if("replay"===o){const t=setInterval((()=>{const n=D();if(v.requesting)return void(v.lastRealTs=n);if(v.logPointer+1>=v.log.length)return v.lastRealTs=n,void(async e=>{const t=await b(e);v.log=v.log.slice(v.logPointer),v.logPointer=0,v.log.push(...t.log),t.log.length=v.log.length){v.final&&clearInterval(t);break}const o=v.log[n],i=v.gameStartTs+o[o.length-1];if(i>l)break;const a=o.slice();if(a[0]===rt){const t=a[1];to.addPlayer(e,t,i),ho=!0}else if(a[0]===dt){const t=to.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";to.addPlayer(e,t,i),ho=!0}else if(a[0]===ct){const t=to.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=a[2];to.handleInput(e,t,n,i),ho=!0}v.logPointer=n}v.lastRealTs=n,v.lastGameTs=l,$()}),50)}let X=null;return(e=>{const t=e.fps||60,n=e.slow||1,o=e.update,l=e.render,i=window.requestAnimationFrame,a=1/t,s=n*a;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>s;)d-=s,o(a);l(d/n),c=r,i(u)};i(u)})({update:()=>{M.createKeyEvents();for(const n of M.consumeAll())if("play"===o){const o=n[0];if(o===ut){const e=n[1],t=n[2];ho=!0,N.move(e,t)}else if(o===ht){if(X&&!to.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=N.worldToViewport(e),o=Math.round(t.x-X.x),l=Math.round(t.y-X.y);ho=!0,N.move(o,l),X=t}}else if(o===wt)Z(n[1]);else if(o===gt){const e={x:n[1],y:n[2]};X=N.worldToViewport(e),K(!0)}else if(o===pt)X=null,K(!1);else if(o===mt){const e={x:n[1],y:n[2]};ho=!0,N.zoom("in",N.worldToViewport(e))}else if(o===yt){const e={x:n[1],y:n[2]};ho=!0,N.zoom("out",N.worldToViewport(e))}else o===bt?i.togglePreview():o===xt&&i.toggleSoundsEnabled();const l=x();to.handleInput(e,t,n,l,(e=>{L()&&s.play()})).length>0&&(ho=!0),Nt.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===ut){const e=n[1],t=n[2];ho=!0,N.move(e,t)}else if(e===ht){if(X){const e={x:n[1],y:n[2]},t=N.worldToViewport(e),o=Math.round(t.x-X.x),l=Math.round(t.y-X.y);ho=!0,N.move(o,l),X=t}}else if(e===gt){const e={x:n[1],y:n[2]};X=N.worldToViewport(e)}else if(e===pt)X=null;else if(e===mt){const e={x:n[1],y:n[2]};ho=!0,N.zoom("in",N.worldToViewport(e))}else if(e===yt){const e={x:n[1],y:n[2]};ho=!0,N.zoom("out",N.worldToViewport(e))}else e===bt&&i.togglePreview()}j=!!to.getFinishTs(e),F()&&(O.update(),ho=!0)},render:async()=>{if(!ho)return;const n=x();let l,a,s;window.DEBUG&&pn(0),U.fillStyle=W(),U.fillRect(0,0,w.width,w.height),window.DEBUG&&hn("clear done"),l=N.worldToViewportRaw(T),a=N.worldDimToViewportRaw(_),U.fillStyle="rgba(255, 255, 255, .3)",U.fillRect(l.x,l.y,a.w,a.h),window.DEBUG&&hn("board done");const r=to.getPiecesSortedByZIndex(e);window.DEBUG&&hn("get tiles done"),a=N.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?go:po)&&(s=B[e.idx],l=N.worldToViewportRaw({x:C+e.pos.x,y:C+e.pos.y}),U.drawImage(s,0,0,s.width,s.height,l.x,l.y,a.w,a.h));window.DEBUG&&hn("tiles done");const d=[];for(const i of to.getActivePlayers(e,n))c=i,("replay"===o||c.id!==t)&&(s=await f(i),l=N.worldToViewport(i),U.drawImage(s,l.x-p,l.y-m),d.push([`${i.name} (${i.points})`,l.x,l.y+h]));var c;U.fillStyle="white",U.textAlign="center";for(const[e,t,o]of d)U.fillText(e,t,o);window.DEBUG&&hn("players done"),i.setActivePlayers(to.getActivePlayers(e,n)),i.setIdlePlayers(to.getIdlePlayers(e,n)),i.setPiecesDone(to.getFinishedPiecesCount(e)),window.DEBUG&&hn("HUD done"),F()&&O.render(),ho=!1}}),{setHotkeys:e=>{M.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),M.addEvent([ft,e])},onColorChange:e=>{localStorage.setItem("player_color",e),M.addEvent([wt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),M.addEvent([vt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},replayOnSpeedUp:()=>{v.speedIdx+1{v.speedIdx>=1&&(v.speedIdx--,J())},replayOnPauseToggle:()=>{v.paused=!v.paused,J()},previewImageUrl:G,player:{background:W(),color:q(),name:to.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:L()},disconnect:Nt.disconnect,connect:k}}var yo=e({name:"game",components:{PuzzleStatus:Ye,Scores:Le,SettingsOverlay:Ze,PreviewOverlay:nt,ConnectionOverlay:Mt,HelpOverlay:Ft},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=>{},disconnect:()=>{},connect:()=>{}}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await mo(`${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.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 fo={id:"game"},wo={class:"menu"},vo={class:"tabs"},bo=s("🧩 Puzzles");yo.render=function(e,l,s,r,d,c){const u=i("settings-overlay"),p=i("preview-overlay"),h=i("help-overlay"),m=i("connection-overlay"),y=i("puzzle-status"),f=i("router-link"),w=i("scores");return a(),t("div",fo,[g(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"]),[[b,"settings"===e.overlay]]),g(n(p,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[b,"preview"===e.overlay]]),g(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[b,"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",wo,[n("div",vo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[bo])),_: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(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var xo=e({name:"replay",components:{PuzzleStatus:Ye,Scores:Le,SettingsOverlay:Ze,PreviewOverlay:nt,HelpOverlay:Ft},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:()=>{},disconnect:()=>{}},replay:{speed:1,paused:!1}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await mo(`${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.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 ko={id:"replay"},Co={class:"menu"},Ao={class:"tabs"},So=s("🧩 Puzzles");xo.render=function(e,l,s,d,c,u){const p=i("settings-overlay"),h=i("preview-overlay"),m=i("help-overlay"),y=i("puzzle-status"),f=i("router-link"),w=i("scores");return a(),t("div",ko,[g(n(p,{onBgclick:l[1]||(l[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":l[2]||(l[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[b,"settings"===e.overlay]]),g(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[b,"preview"===e.overlay]]),g(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[b,"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",Co,[n("div",Ao,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[So])),_: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(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=x({history:k(),routes:[{name:"index",path:"/",component:V},{name:"new-game",path:"/new-game",component:Oe},{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=C(A);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=Be.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); +import{d as e,c as t,a as n,w as o,b as l,r as i,o as a,e as s,t as r,F as d,f as c,g as u,h as g,v as p,i as h,j as m,k as y,l as f,m as w,n as v,p as b,q as x,s as C,u as k}from"./vendor.18cd2d7e.js";var A=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const S={id:"app"},z={key:0,class:"nav"},P=s("Index"),I=s("New game");A.render=function(e,s,r,d,c,u){const g=i("router-link"),p=i("router-view");return a(),t("div",S,[e.showNav?(a(),t("ul",z,[n("li",null,[n(g,{class:"btn",to:{name:"index"}},{default:o((()=>[P])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:o((()=>[I])),_:1})])])):l("",!0),n(p)])};const T=864e5,_=e=>{const t=Math.floor(e/T);e%=T;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 E=1e3,D=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>_(t-e),O=_,M=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||D();return`${n} ${B(o,l)}`}}});const U={class:"game-info-text"},N=n("br",null,null,-1),G=n("br",null,null,-1),$=n("br",null,null,-1),R=s(" ↪️ Watch replay ");M.render=function(e,d,c,u,g,p){const h=i("router-link");return a(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",U,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),N,s(" 👥 "+r(e.game.players),1),G,s(" "+r(e.time(e.game.started,e.game.finished)),1),$])])),_:1},8,["to"]),e.game.hasReplay?(a(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[R])),_:1},8,["to"])):l("",!0)],4)};var V=e({components:{GameTeaser:M},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),F=n("h1",null,"Finished games",-1);V.render=function(e,o,l,s,r,u){const g=i("game-teaser");return a(),t("div",null,[j,(a(!0),t(d,null,c(e.gamesRunning,((e,o)=>(a(),t("div",{class:"game-teaser-wrap",key:o},[n(g,{game:e},null,8,["game"])])))),128)),F,(a(!0),t(d,null,c(e.gamesFinished,((e,o)=>(a(),t("div",{class:"game-teaser-wrap",key:o},[n(g,{game:e},null,8,["game"])])))),128))])};var L=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")}}});L.render=function(e,o,l,i,s,r){return a(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var W=e({name:"image-library",components:{ImageTeaser:L},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});W.render=function(e,n,o,l,s,r){const u=i("image-teaser");return a(),t("div",null,[(a(!0),t(d,null,c(e.images,((n,o)=>(a(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])};const q={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}}}};q.render=function(e,n,o,l,i,s){return a(),t("div",{style:s.style,title:o.title},null,12,["title"])};var H=e({name:"tags-input",props:{modelValue:{type:Array,required:!0}},emits:{"update:modelValue":null},data:()=>({input:"",values:[]}),created(){this.values=this.modelValue},methods:{onKeyUp(e){if(","===e.key)return this.add(),e.stopPropagation(),!1},add(){const e=this.input.replace(/,/g,"").trim();e&&(this.values.includes(e)||this.values.push(e),this.input="",this.$emit("update:modelValue",this.values))},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const Q=m()(((e,o,l,i,s,u)=>(a(),t("div",null,[g(n("input",{class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onKeydown:o[2]||(o[2]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[3]||(o[3]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[p,e.input]]),(a(!0),t(d,null,c(e.values,((n,o)=>(a(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));H.render=Q,H.__scopeId="data-v-771460ae";var Y=e({name:"new-image-dialog",components:{ResponsiveImage:q,TagsInput:H},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const o=new FileReader;o.readAsDataURL(n),o.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const Z={key:0,class:"has-image"},K={key:1},J={class:"upload"},X=n("span",{class:"btn"},"Upload File",-1),ee={class:"area-settings"},te=n("td",null,[n("label",null,"Title")],-1),ne=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),oe=n("td",null,[n("label",null,"Tags")],-1),le={class:"area-buttons"},ie=s("🧩 Post to gallery "),ae=n("br",null,null,-1),se=s(" + set up game");Y.render=function(e,o,l,s,r,d){const c=i("responsive-image"),h=i("tags-input");return a(),t("div",{class:"overlay new-image-dialog",onClick:o[8]||(o[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[7]||(o[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(a(),t("div",Z,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(a(),t("div",K,[n("label",J,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),X])]))],2),n("div",ee,[n("table",null,[n("tr",null,[te,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[3]||(o[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ne,n("tr",null,[oe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[4]||(o[4]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",le,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[5]||(o[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[6]||(o[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[ie,ae,se],8,["disabled"])])])])};var re=e({name:"edit-image-dialog",components:{ResponsiveImage:q,TagsInput:H},props:{image:{type:Object,required:!0}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const de={class:"area-image"},ce={class:"has-image"},ue={class:"area-settings"},ge=n("td",null,[n("label",null,"Title")],-1),pe=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"};var ye,fe,we,ve;re.render=function(e,o,l,s,r,d){const c=i("responsive-image"),h=i("tags-input");return a(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",de,[n("div",ce,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ue,[n("table",null,[n("tr",null,[ge,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),pe,n("tr",null,[he,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",me,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])},(fe=ye||(ye={}))[fe.Flat=0]="Flat",fe[fe.Out=1]="Out",fe[fe.In=-1]="In",(ve=we||(we={}))[ve.FINAL=0]="FINAL",ve[ve.ANY=1]="ANY";var be=e({name:"new-game-dialog",components:{ResponsiveImage:q},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:we.ANY}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const xe={class:"area-image"},Ce={class:"has-image"},ke={class:"area-settings"},Ae=n("td",null,[n("label",null,"Pieces")],-1),Se=n("td",null,[n("label",null,"Scoring: ")],-1),ze=s(" Any (Score when pieces are connected to each other or on final location)"),Pe=n("br",null,null,-1),Ie=s(" Final (Score when pieces are put to their final location)"),Te={class:"area-buttons"};be.render=function(e,o,l,s,r,d){const c=i("responsive-image");return a(),t("div",{class:"overlay new-game-dialog",onClick:o[6]||(o[6]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[5]||(o[5]=u((()=>{}),["stop"]))},[n("div",xe,[n("div",Ce,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ke,[n("table",null,[n("tr",null,[Ae,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Se,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[y,e.scoreMode]]),ze]),Pe,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[y,e.scoreMode]]),Ie])])])])]),n("div",Te,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[4]||(o[4]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};class _e{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 _e(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const Ee=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},De=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=Ee(o.getHours(),"00"),i=Ee(o.getMinutes(),"00"),a=Ee(o.getSeconds(),"00");console[t](`${l}:${i}:${a}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var Be={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",_e.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||we.FINAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:_e.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}},Oe=e({components:{ImageLibrary:W,NewImageDialog:Y,EditImageDialog:re,NewGameDialog:be},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},methods:{toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${Be.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/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 Me={class:"upload-image-teaser"},Ue=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),Ne={key:0},Ge=s(" Tags: "),$e=s(" Sort by: "),Re=n("option",{value:"date_desc"},"Newest first",-1),Ve=n("option",{value:"date_asc"},"Oldest first",-1),je=n("option",{value:"alpha_asc"},"A-Z",-1),Fe=n("option",{value:"alpha_desc"},"Z-A",-1);Oe.render=function(e,o,s,u,p,h){const m=i("image-library"),y=i("new-image-dialog"),w=i("edit-image-dialog"),v=i("new-game-dialog");return a(),t("div",null,[n("div",Me,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),Ue]),n("div",null,[e.tags.length>0?(a(),t("label",Ne,[Ge,(a(!0),t(d,null,c(e.tags,((n,o)=>(a(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title),11,["onClick"])))),128))])):l("",!0),n("label",null,[$e,g(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[Re,Ve,je,Fe],544),[[f,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(a(),t(y,{key:0,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(a(),t(w,{key:1,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(a(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var Le=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 We={class:"scores"},qe=n("div",null,"Scores",-1),He=n("td",null,"⚡",-1),Qe=n("td",null,"💤",-1);Le.render=function(e,o,l,i,s,u){return a(),t("div",We,[qe,n("table",null,[(a(!0),t(d,null,c(e.actives,((e,o)=>(a(),t("tr",{key:o,style:{color:e.color}},[He,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(a(!0),t(d,null,c(e.idles,((e,o)=>(a(),t("tr",{key:o,style:{color:e.color}},[Qe,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var Ye=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 O(this.duration)}}});const Ze={class:"timer"};Ye.render=function(e,o,l,i,s,d){return a(),t("div",Ze,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),w(e.$slots,"default")])};var Ke=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:Object},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const Je=n("td",null,[n("label",null,"Background: ")],-1),Xe=n("td",null,[n("label",null,"Color: ")],-1),et=n("td",null,[n("label",null,"Name: ")],-1),tt=n("td",null,[n("label",null,"Sounds: ")],-1);Ke.render=function(e,o,l,i,s,r){return a(),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,[Je,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[Xe,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[et,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])]),n("tr",null,[tt,n("td",null,[g(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[v,e.modelValue.soundsEnabled]])])])])])};var nt=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const ot={class:"preview"};nt.render=function(e,o,l,i,s,r){return a(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",ot,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var lt=1,it=4,at=2,st=3,rt=2,dt=4,ct=3,ut=9,gt=1,pt=2,ht=3,mt=4,yt=5,ft=6,wt=7,vt=8,bt=10,xt=11,Ct=1,kt=2,At=3;const St=De("Communication.js");let zt,Pt=[],It=e=>{Pt.push(e)},Tt=[],_t=e=>{Tt.push(e)};let Et=0;const Dt=e=>{Et!==e&&(Et=e,_t(e))};function Bt(e){if(2===Et)try{zt.send(JSON.stringify(e))}catch(t){St.info("unable to send message.. maybe because ws is invalid?")}}let Ot,Mt;var Ut={connect:function(e,t,n){return Ot=0,Mt={},Dt(3),new Promise((o=>{zt=new WebSocket(e,n+"|"+t),zt.onopen=()=>{Dt(2),Bt([st])},zt.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===it){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&&Mt[o])return void delete Mt[o];It(t)}}},zt.onerror=()=>{throw Dt(1),"[ 2021-05-15 onerror ]"},zt.onclose=e=>{4e3===e.code||1001===e.code?Dt(4):Dt(1)}}))},requestReplayData:async function(e,t,n){const o={gameId:e,offset:t,size:n},l=await fetch(`/api/replay-data${Be.asQueryArgs(o)}`);return await l.json()},disconnect:function(){zt&&zt.close(4e3),Ot=0,Mt={}},sendClientEvent:function(e){Ot++,Mt[Ot]=e,Bt([at,Ot,Mt[Ot]])},onServerChange:function(e){It=e;for(const t of Pt)It(t);Pt=[]},onConnectionStateChange:function(e){_t=e;for(const t of Tt)_t(t);Tt=[]},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},Nt=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Ut.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Ut.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const Gt={key:0,class:"overlay connection-lost"},$t={key:0,class:"overlay-content"},Rt=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Vt={key:1,class:"overlay-content"},jt=n("div",null,"Connecting...",-1);Nt.render=function(e,o,i,s,r,d){return e.show?(a(),t("div",Gt,[e.lostConnection?(a(),t("div",$t,[Rt,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(a(),t("div",Vt,[jt])):l("",!0)])):l("",!0)};var Ft=e({name:"help-overlay",emits:{bgclick:null}});const Lt=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),Wt=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),qt=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),Ht=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),Qt=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Yt=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),Zt=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),Kt=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Jt=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Xt=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),en=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1);Ft.render=function(e,o,l,i,s,r){return a(),t("div",{class:"overlay transparent",onClick:o[2]||(o[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:o[1]||(o[1]=u((()=>{}),["stop"]))},[Lt,Wt,qt,Ht,Qt,Yt,Zt,Kt,Jt,Xt,en])])};var tn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.550555f3.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="}),ln=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),an=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function sn(){let e=0,t=0,n=1;const o=(o,l)=>{e+=o/n,t+=l/n},l=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},i=o=>({x:o.x/n-e,y:o.y/n-t}),a=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}=a(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:a,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:i,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function rn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var dn={createCanvas:rn,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=rn(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=rn(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 cn=De("Debug.js");let un=0,gn=0;var pn=e=>{un=performance.now(),gn=e},hn=e=>{const t=performance.now(),n=t-un;n>gn&&cn.log(e+": "+n),un=t};function mn(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 fn={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:mn,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 mn(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 wn=De("PuzzleGraphics.js");function vn(e,t){const n=Be.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var bn={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){wn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,i=n.tileDrawSize,a=o/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,i={x:l,y:l},r=fn.pointAdd(i,{x:o,y:0}),c=fn.pointAdd(r,{x:0,y:o}),u=fn.pointSub(c,{x:o,y:0});if(n.moveTo(i.x,i.y),0!==e.top)for(let o=0;oBe.decodePiece(xn[e].puzzle.tiles[t]),Mn=(e,t)=>On(e,t).group,Un=(e,t)=>{const n=xn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=xn[e].puzzle.info,o=Be.coordByPieceIdx(n,t),l=o.x*n.tileSize,i=o.y*n.tileSize;return{x:l,y:i}}(e,t);return fn.pointAdd(o,l)},Nn=(e,t)=>On(e,t).pos,Gn=e=>{const t=Xn(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}},$n=(e,t)=>{const n=Fn(e),o=On(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Rn=(e,t)=>On(e,t).z,Vn=(e,t)=>{for(const n of xn[e].puzzle.tiles){const e=Be.decodePiece(n);if(e.owner===t)return e.idx}return-1},jn=e=>xn[e].puzzle.info.tileDrawSize,Fn=e=>xn[e].puzzle.info.tileSize,Ln=e=>xn[e].puzzle.data.maxGroup,Wn=e=>xn[e].puzzle.data.maxZ;function qn(e,t){const n=xn[e].puzzle.info,o=Be.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Hn=(e,t,n)=>{for(const o of t)Bn(e,o,{z:n})},Qn=(e,t,n)=>{const o=Nn(e,t);Bn(e,t,{pos:fn.pointAdd(o,n)})},Yn=(e,t,n)=>{const o=jn(e),l=Gn(e),i=n;for(const a of t){const t=On(e,a);t.pos.x+n.xl.x+l.w&&(i.x=Math.min(l.x+l.w-t.pos.x+o,i.x)),t.pos.y+n.yl.y+l.h&&(i.y=Math.min(l.y+l.h-t.pos.y+o,i.y))}for(const a of t)Qn(e,a,i)},Zn=(e,t,n)=>{for(const o of t)Bn(e,o,{owner:n})};function Kn(e,t){const n=xn[e].puzzle.tiles,o=Be.decodePiece(n[t]),l=[];if(o.group)for(const i of n){const e=Be.decodePiece(i);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const Jn=(e,t)=>{const n=kn(e,t);return n?n.points:0},Xn=e=>xn[e].puzzle.info.table.width,eo=e=>xn[e].puzzle.info.table.height;var to={setGame:function(e,t){xn[e]=t},exists:function(e){return!!xn[e]||!1},playerExists:Sn,getActivePlayers:function(e,t){const n=t-30*E;return zn(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*E;return zn(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Sn(e,t)?En(e,t,{ts:n}):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,n))},getFinishedPiecesCount:_n,getPieceCount:Pn,getImageUrl:function(e){return xn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){xn[e].puzzle.info.imageUrl=t},get:function(e){return xn[e]||null},getAllGames:function(){return Object.values(xn).sort(((e,t)=>Tn(e.id)===Tn(t.id)?t.puzzle.data.started-e.puzzle.data.started:Tn(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:Cn,getPlayerIdByIndex:function(e,t){return xn[e].players.length>t?Be.decodePlayer(xn[e].players[t]).id:null},changePlayer:En,setPlayer:An,setPiece:function(e,t,n){xn[e].puzzle.tiles[t]=Be.encodePiece(n)},setPuzzleData:function(e,t){xn[e].puzzle.data=t},getTableWidth:Xn,getTableHeight:eo,getPuzzle:e=>xn[e].puzzle,getRng:e=>xn[e].rng.obj,getPuzzleWidth:e=>xn[e].puzzle.info.width,getPuzzleHeight:e=>xn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return xn[e].puzzle.tiles.map(Be.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Vn(e,t);return n<0?null:xn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>xn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:jn,getFinalPiecePos:Un,getStartTs:e=>xn[e].puzzle.data.started,getFinishTs:e=>xn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const i=xn[e].puzzle,a=function(e,t){return t in xn[e].evtInfos?xn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],r=()=>{s.push([Ct,i.data])},d=t=>{s.push([kt,Be.encodePiece(On(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=kn(e,t);n&&s.push([At,Be.encodePlayer(n)])},g=n[0];if(g===ft){const l=n[1];En(e,t,{bgcolor:l,ts:o}),u()}else if(g===wt){const l=n[1];En(e,t,{color:l,ts:o}),u()}else if(g===vt){const l=`${n[1]}`.substr(0,16);En(e,t,{name:l,ts:o}),u()}else if(g===ut){const l=n[1],i=n[2],a=kn(e,t);if(a){const n=a.x-l,s=a.y-i;En(e,t,{ts:o,x:n,y:s}),u()}}else if(g===gt){const l={x:n[1],y:n[2]};En(e,t,{d:1,ts:o}),u(),a._last_mouse_down=l;const i=((e,t)=>{const n=xn[e].puzzle.info,o=xn[e].puzzle.tiles;let l=-1,i=-1;for(let a=0;al)&&(l=e.z,i=a)}return i})(e,l);if(i>=0){const n=Wn(e)+1;Dn(e,{maxZ:n}),r();const o=Kn(e,i);Hn(e,o,Wn(e)),Zn(e,o,t),c(o)}a._last_mouse=l}else if(g===ht){const l=n[1],i=n[2],s={x:l,y:i};if(null===a._last_mouse_down)En(e,t,{x:l,y:i,ts:o}),u();else{const n=Vn(e,t);if(n>=0){En(e,t,{x:l,y:i,ts:o}),u();const r=Kn(e,n);let d=fn.pointInBounds(s,Gn(e))&&fn.pointInBounds(a._last_mouse_down,Gn(e));for(const t of r){const n=$n(e,t);if(fn.pointInBounds(s,n)){d=!0;break}}if(d){const t=l-a._last_mouse_down.x,n=i-a._last_mouse_down.y;Yn(e,r,{x:t,y:n}),c(r)}}else En(e,t,{ts:o}),u();a._last_mouse_down=s}a._last_mouse=s}else if(g===pt){const s={x:n[1],y:n[2]},g=0;a._last_mouse_down=null;const p=Vn(e,t);if(p>=0){const n=Kn(e,p);Zn(e,n,0),c(n);const a=Nn(e,p),s=Un(e,p);if(fn.pointDistance(s,a){for(const n of t)Bn(e,n,{owner:-1,z:1})})(e,n),c(n);let d=Jn(e,t);In(e)===we.FINAL?d+=n.length:In(e)===we.ANY&&(d+=1),En(e,t,{d:g,ts:o,points:d}),u(),_n(e)===Pn(e)&&(Dn(e,{finished:o}),r()),l&&l(t)}else{const n=(e,t,n,o)=>{const l=xn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=Mn(e,t),l=Mn(e,n);return!(!o||o!==l)})(e,t,n))return!1;const i=Nn(e,t),a=fn.pointAdd(Nn(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(fn.pointDistance(i,a){const o=xn[e].puzzle.tiles,l=Mn(e,t),i=Mn(e,n);let a;const s=[];l&&s.push(l),i&&s.push(i),l?a=l:i?a=i:(Dn(e,{maxGroup:Ln(e)+1}),r(),a=Ln(e));if(Bn(e,t,{group:a}),d(t),Bn(e,n,{group:a}),d(n),s.length>0)for(const r of o){const t=Be.decodePiece(r);s.includes(t.group)&&(Bn(e,t.idx,{group:a}),d(t.idx))}})(e,t,n),l=Kn(e,t);const s=((e,t)=>{let n=0;for(const o of t){const t=Rn(e,o);t>n&&(n=t)}return n})(e,l);return Hn(e,l,s),c(l),!0}return!1};let i=!1;for(const t of Kn(e,p)){const o=qn(e,t);if(n(e,t,o[0],[0,1])||n(e,t,o[1],[-1,0])||n(e,t,o[2],[0,-1])||n(e,t,o[3],[1,0])){i=!0;break}}if(i&&In(e)===we.ANY){const n=Jn(e,t)+1;En(e,t,{d:g,ts:o,points:n}),u()}else En(e,t,{d:g,ts:o}),u();i&&l&&l(t)}}else En(e,t,{d:g,ts:o}),u();a._last_mouse=s}else if(g===mt){const l=n[1],i=n[2];En(e,t,{x:l,y:i,ts:o}),u(),a._last_mouse={x:l,y:i}}else if(g===yt){const l=n[1],i=n[2];En(e,t,{x:l,y:i,ts:o}),u(),a._last_mouse={x:l,y:i}}else En(e,t,{ts:o}),u();return function(e,t,n){xn[e].evtInfos[t]=n}(e,t,a),s}};let no=-10,oo=20,lo=2,io=15;class ao{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*(lo+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;lo=t/2,io=t-lo;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 ao(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new ao(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]},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,ho=!0})),t}(l,dn.createCanvas()),v={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};Ut.onConnectionStateChange((e=>{i.setConnectionState(e)}));const b=async e=>{v.requesting=!0;const t=await Ut.requestReplayData(e,v.dataOffset,v.dataSize);return v.dataOffset+=v.dataSize,v.requesting=!1,t};let x=()=>0;const C=async()=>{if("play"===o){const o=await Ut.connect(n,e,t),l=Be.decodeGame(o);to.setGame(l.id,l),x=()=>D()}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=Be.decodeGame(t.game);to.setGame(n.id,n),v.requesting=!1,v.log=t.log,v.lastRealTs=D(),v.gameStartTs=parseInt(v.log[0][4],10),v.lastGameTs=v.gameStartTs,x=()=>v.lastGameTs}}ho=!0};await C();const k=to.getPieceDrawOffset(e),A=to.getPieceDrawSize(e),S=to.getPuzzleWidth(e),z=to.getPuzzleHeight(e),P=to.getTableWidth(e),I=to.getTableHeight(e),T={x:(P-S)/2,y:(I-z)/2},_={w:S,h:z},E={w:A,h:A},B=await bn.loadPuzzleBitmaps(to.getPuzzle(e)),O=new ro(w,to.getRng(e));O.init();const M=w.getContext("2d");w.classList.add("loaded");const U=sn();U.move(-(P-w.width)/2,-(I-w.height)/2);const N=function(e,t,n){let o=[],l=!0,i=!1,a=!1,s=!1,r=!1,d=!1,c=!1,u=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{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?i=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?a=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};let y=null;e.addEventListener("mousedown",(e=>{y=p(e),0===e.button&&f([gt,...y])})),e.addEventListener("mouseup",(e=>{y=p(e),0===e.button&&f([pt,...y])})),e.addEventListener("mousemove",(e=>{y=p(e),f([ht,...y])})),e.addEventListener("wheel",(e=>{if(y=p(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?mt:yt;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([bt]),"F"!==e.key&&"f"!==e.key||(go=!go,ho=!0),"G"!==e.key&&"g"!==e.key||(po=!po,ho=!0),"M"!==e.key&&"m"!==e.key||f([xt]))}));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=(i?1:0)-(a?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([ut,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([mt,...e])}}else if(c&&n.canZoom("out")){const e=y||h();f([yt,...e])}},setHotkeys:e=>{l=e}}}(w,window,U),G=to.getImageUrl(e),$=()=>{const t=to.getStartTs(e),n=to.getFinishTs(e),o=x();i.setFinished(!!n),i.setDuration((n||o)-t)};$(),i.setPiecesDone(to.getFinishedPiecesCount(e)),i.setPiecesTotal(to.getPieceCount(e));const R=x();i.setActivePlayers(to.getActivePlayers(e,R)),i.setIdlePlayers(to.getIdlePlayers(e,R));const V=!!to.getFinishTs(e);let j=V;const F=()=>j&&!V,L=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},W=()=>to.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",q=()=>to.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let H="",Q="",Y=!1;const Z=e=>{Y=e;const[t,n]=e?[H,"grab"]:[Q,"default"];w.style.cursor=`url('${t}') ${p} ${m}, ${n}`},K=e=>{H=dn.colorizedCanvas(r,c,e).toDataURL(),Q=dn.colorizedCanvas(d,u,e).toDataURL(),Z(Y)};K(q());const J=()=>{i.setReplaySpeed&&i.setReplaySpeed(v.speeds[v.speedIdx]),i.setReplayPaused&&i.setReplayPaused(v.paused)};if("play"===o?setInterval($,1e3):"replay"===o&&J(),"play"===o)Ut.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,i]of o)switch(l){case At:{const n=Be.decodePlayer(i);n.id!==t&&(to.setPlayer(e,n.id,n),ho=!0)}break;case kt:{const t=Be.decodePiece(i);to.setPiece(e,t.idx,t),ho=!0}break;case Ct:to.setPuzzleData(e,i),ho=!0}j=!!to.getFinishTs(e)}));else if("replay"===o){const t=setInterval((()=>{const n=D();if(v.requesting)return void(v.lastRealTs=n);if(v.logPointer+1>=v.log.length)return v.lastRealTs=n,void(async e=>{const t=await b(e);v.log=v.log.slice(v.logPointer),v.logPointer=0,v.log.push(...t.log),t.log.length=v.log.length){v.final&&clearInterval(t);break}const o=v.log[n],i=v.gameStartTs+o[o.length-1];if(i>l)break;const a=o.slice();if(a[0]===rt){const t=a[1];to.addPlayer(e,t,i),ho=!0}else if(a[0]===dt){const t=to.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";to.addPlayer(e,t,i),ho=!0}else if(a[0]===ct){const t=to.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=a[2];to.handleInput(e,t,n,i),ho=!0}v.logPointer=n}v.lastRealTs=n,v.lastGameTs=l,$()}),50)}let X=null;return(e=>{const t=e.fps||60,n=e.slow||1,o=e.update,l=e.render,i=window.requestAnimationFrame,a=1/t,s=n*a;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>s;)d-=s,o(a);l(d/n),c=r,i(u)};i(u)})({update:()=>{N.createKeyEvents();for(const n of N.consumeAll())if("play"===o){const o=n[0];if(o===ut){const e=n[1],t=n[2],o=U.worldDimToViewport({w:e,h:t});ho=!0,U.move(o.w,o.h)}else if(o===ht){if(X&&!to.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-X.x),l=Math.round(t.y-X.y);ho=!0,U.move(o,l),X=t}}else if(o===wt)K(n[1]);else if(o===gt){const e={x:n[1],y:n[2]};X=U.worldToViewport(e),Z(!0)}else if(o===pt)X=null,Z(!1);else if(o===mt){const e={x:n[1],y:n[2]};ho=!0,U.zoom("in",U.worldToViewport(e))}else if(o===yt){const e={x:n[1],y:n[2]};ho=!0,U.zoom("out",U.worldToViewport(e))}else o===bt?i.togglePreview():o===xt&&i.toggleSoundsEnabled();const l=x();to.handleInput(e,t,n,l,(e=>{L()&&s.play()})).length>0&&(ho=!0),Ut.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===ut){const e=n[1],t=n[2];ho=!0,U.move(e,t)}else if(e===ht){if(X){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-X.x),l=Math.round(t.y-X.y);ho=!0,U.move(o,l),X=t}}else if(e===gt){const e={x:n[1],y:n[2]};X=U.worldToViewport(e)}else if(e===pt)X=null;else if(e===mt){const e={x:n[1],y:n[2]};ho=!0,U.zoom("in",U.worldToViewport(e))}else if(e===yt){const e={x:n[1],y:n[2]};ho=!0,U.zoom("out",U.worldToViewport(e))}else e===bt&&i.togglePreview()}j=!!to.getFinishTs(e),F()&&(O.update(),ho=!0)},render:async()=>{if(!ho)return;const n=x();let l,a,s;window.DEBUG&&pn(0),M.fillStyle=W(),M.fillRect(0,0,w.width,w.height),window.DEBUG&&hn("clear done"),l=U.worldToViewportRaw(T),a=U.worldDimToViewportRaw(_),M.fillStyle="rgba(255, 255, 255, .3)",M.fillRect(l.x,l.y,a.w,a.h),window.DEBUG&&hn("board done");const r=to.getPiecesSortedByZIndex(e);window.DEBUG&&hn("get tiles done"),a=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?go:po)&&(s=B[e.idx],l=U.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),M.drawImage(s,0,0,s.width,s.height,l.x,l.y,a.w,a.h));window.DEBUG&&hn("tiles done");const d=[];for(const i of to.getActivePlayers(e,n))c=i,("replay"===o||c.id!==t)&&(s=await f(i),l=U.worldToViewport(i),M.drawImage(s,l.x-p,l.y-m),d.push([`${i.name} (${i.points})`,l.x,l.y+h]));var c;M.fillStyle="white",M.textAlign="center";for(const[e,t,o]of d)M.fillText(e,t,o);window.DEBUG&&hn("players done"),i.setActivePlayers(to.getActivePlayers(e,n)),i.setIdlePlayers(to.getIdlePlayers(e,n)),i.setPiecesDone(to.getFinishedPiecesCount(e)),window.DEBUG&&hn("HUD done"),F()&&O.render(),ho=!1}}),{setHotkeys:e=>{N.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),N.addEvent([ft,e])},onColorChange:e=>{localStorage.setItem("player_color",e),N.addEvent([wt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),N.addEvent([vt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},replayOnSpeedUp:()=>{v.speedIdx+1{v.speedIdx>=1&&(v.speedIdx--,J())},replayOnPauseToggle:()=>{v.paused=!v.paused,J()},previewImageUrl:G,player:{background:W(),color:q(),name:to.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:L()},disconnect:Ut.disconnect,connect:C}}var yo=e({name:"game",components:{PuzzleStatus:Ye,Scores:Le,SettingsOverlay:Ke,PreviewOverlay:nt,ConnectionOverlay:Nt,HelpOverlay:Ft},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=>{},disconnect:()=>{},connect:()=>{}}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await mo(`${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.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 fo={id:"game"},wo={class:"menu"},vo={class:"tabs"},bo=s("🧩 Puzzles");yo.render=function(e,l,s,r,d,c){const u=i("settings-overlay"),p=i("preview-overlay"),h=i("help-overlay"),m=i("connection-overlay"),y=i("puzzle-status"),f=i("router-link"),w=i("scores");return a(),t("div",fo,[g(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"]),[[b,"settings"===e.overlay]]),g(n(p,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[b,"preview"===e.overlay]]),g(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[b,"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",wo,[n("div",vo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[bo])),_: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(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var xo=e({name:"replay",components:{PuzzleStatus:Ye,Scores:Le,SettingsOverlay:Ke,PreviewOverlay:nt,HelpOverlay:Ft},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:()=>{},disconnect:()=>{}},replay:{speed:1,paused:!1}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await mo(`${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.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 Co={id:"replay"},ko={class:"menu"},Ao={class:"tabs"},So=s("🧩 Puzzles");xo.render=function(e,l,s,d,c,u){const p=i("settings-overlay"),h=i("preview-overlay"),m=i("help-overlay"),y=i("puzzle-status"),f=i("router-link"),w=i("scores");return a(),t("div",Co,[g(n(p,{onBgclick:l[1]||(l[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":l[2]||(l[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[b,"settings"===e.overlay]]),g(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[b,"preview"===e.overlay]]),g(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[b,"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",ko,[n("div",Ao,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[So])),_: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(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=x({history:C(),routes:[{name:"index",path:"/",component:V},{name:"new-game",path:"/new-game",component:Oe},{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=k(A);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=Be.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); diff --git a/build/public/index.html b/build/public/index.html index 4579d2b..d8933e3 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 39780ab..3ee44e3 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -957,6 +957,17 @@ function handleInput$1(gameId, playerId, input, ts, onSnap) { changePlayer(gameId, playerId, { name, ts }); _playerChange(); } + else if (type === Protocol.INPUT_EV_MOVE) { + const w = input[1]; + const h = input[2]; + const player = getPlayer(gameId, playerId); + if (player) { + const x = player.x - w; + const y = player.y - h; + changePlayer(gameId, playerId, { ts, x, y }); + _playerChange(); + } + } else if (type === Protocol.INPUT_EV_MOUSE_DOWN) { const x = input[1]; const y = input[2]; From 57a1e9f24dd0b70742aad2d2bc46bb925e084bb7 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Wed, 2 Jun 2021 23:35:01 +0200 Subject: [PATCH 17/78] fix tag display --- build/server/main.js | 2 +- src/server/Images.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/server/main.js b/build/server/main.js index 3ee44e3..6f7b0f2 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -1317,7 +1317,7 @@ where ixc.image_id = ?`; return db._getMany(query, [imageId]).map(row => ({ id: parseInt(row.number, 10) || 0, slug: row.slug, - title: row.tittle, + title: row.title, })); }; const imageFromDb = (db, imageId) => { diff --git a/src/server/Images.ts b/src/server/Images.ts index e12011d..34a5189 100644 --- a/src/server/Images.ts +++ b/src/server/Images.ts @@ -80,7 +80,7 @@ where ixc.image_id = ?` return db._getMany(query, [imageId]).map(row => ({ id: parseInt(row.number, 10) || 0, slug: row.slug, - title: row.tittle, + title: row.title, })) } From b88321bb1ba6e6bed98364d36d52b2c0d330c5b7 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Thu, 3 Jun 2021 00:01:42 +0200 Subject: [PATCH 18/78] fix tags for new images --- build/server/main.js | 3 ++- src/server/main.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/build/server/main.js b/build/server/main.js index 6f7b0f2..116ee7d 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -2057,7 +2057,8 @@ app.post('/api/upload', (req, res) => { created: Time.timestamp(), }); if (req.body.tags) { - setImageTags(db, imageId, req.body.tags); + const tags = req.body.tags.split(',').filter((tag) => !!tag); + setImageTags(db, imageId, tags); } res.send(Images.imageFromDb(db, imageId)); }); diff --git a/src/server/main.ts b/src/server/main.ts index 4bfbfb8..a5018c9 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -182,7 +182,8 @@ app.post('/api/upload', (req, res): void => { }) if (req.body.tags) { - setImageTags(db, imageId as number, req.body.tags) + const tags = req.body.tags.split(',').filter((tag: string) => !!tag) + setImageTags(db, imageId as number, tags) } res.send(Images.imageFromDb(db, imageId as number)) From 2d83fd441f0c189fefacf52c063df77057f9df8c Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Thu, 3 Jun 2021 09:07:57 +0200 Subject: [PATCH 19/78] add shape modes any and flat --- build/public/assets/index.3208e2e9.js | 1 + build/public/assets/index.3ca85e0f.js | 1 - build/public/index.html | 2 +- build/server/main.js | 42 ++++++++++++++++------- src/common/Types.ts | 8 +++++ src/frontend/components/NewGameDialog.vue | 17 ++++++++- src/server/Game.ts | 25 ++++++++++---- src/server/Puzzle.ts | 24 +++++++++---- src/server/main.ts | 8 +++-- 9 files changed, 98 insertions(+), 30 deletions(-) create mode 100644 build/public/assets/index.3208e2e9.js delete mode 100644 build/public/assets/index.3ca85e0f.js diff --git a/build/public/assets/index.3208e2e9.js b/build/public/assets/index.3208e2e9.js new file mode 100644 index 0000000..a1e26f5 --- /dev/null +++ b/build/public/assets/index.3208e2e9.js @@ -0,0 +1 @@ +import{d as e,c as t,a as n,w as l,b as o,r as i,o as a,e as s,t as r,F as d,f as c,g as u,h as g,v as p,i as h,j as m,k as y,l as f,m as w,n as v,p as b,q as x,s as C,u as k}from"./vendor.18cd2d7e.js";var A=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const S={id:"app"},z={key:0,class:"nav"},P=s("Index"),I=s("New game");A.render=function(e,s,r,d,c,u){const g=i("router-link"),p=i("router-view");return a(),t("div",S,[e.showNav?(a(),t("ul",z,[n("li",null,[n(g,{class:"btn",to:{name:"index"}},{default:l((()=>[P])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:l((()=>[I])),_:1})])])):o("",!0),n(p)])};const T=864e5,_=e=>{const t=Math.floor(e/T);e%=T;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 E=1e3,D=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},M=(e,t)=>_(t-e),B=_,O=e({name:"game-teaser",props:{game:{type:Object,required:!0}},computed:{style(){return{"background-image":`url("${this.game.imageUrl.replace("uploads/","uploads/r/")+"-375x210.webp"}")`}}},methods:{time(e,t){const n=t?"🏁":"⏳",l=e,o=t||D();return`${n} ${M(l,o)}`}}});const N={class:"game-info-text"},U=n("br",null,null,-1),G=n("br",null,null,-1),$=n("br",null,null,-1),R=s(" ↪️ Watch replay ");O.render=function(e,d,c,u,g,p){const h=i("router-link");return a(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:l((()=>[n("span",N,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),U,s(" 👥 "+r(e.game.players),1),G,s(" "+r(e.time(e.game.started,e.game.finished)),1),$])])),_:1},8,["to"]),e.game.hasReplay?(a(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:l((()=>[R])),_:1},8,["to"])):o("",!0)],4)};var V=e({components:{GameTeaser:O},data:()=>({gamesRunning:[],gamesFinished:[]}),async created(){const e=await fetch("/api/index-data"),t=await e.json();this.gamesRunning=t.gamesRunning,this.gamesFinished=t.gamesFinished}});const F=n("h1",null,"Running games",-1),j=n("h1",null,"Finished games",-1);V.render=function(e,l,o,s,r,u){const g=i("game-teaser");return a(),t("div",null,[F,(a(!0),t(d,null,c(e.gamesRunning,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128)),j,(a(!0),t(d,null,c(e.gamesFinished,((e,l)=>(a(),t("div",{class:"game-teaser-wrap",key:l},[n(g,{game:e},null,8,["game"])])))),128))])};var L=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")}}});L.render=function(e,l,o,i,s,r){return a(),t("div",{class:"imageteaser",style:e.style,onClick:l[2]||(l[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:l[1]||(l[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var W=e({name:"image-library",components:{ImageTeaser:L},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});W.render=function(e,n,l,o,s,r){const u=i("image-teaser");return a(),t("div",null,[(a(!0),t(d,null,c(e.images,((n,l)=>(a(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:l},null,8,["image","onClick","onEditClick"])))),128))])};const q={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}}}};q.render=function(e,n,l,o,i,s){return a(),t("div",{style:s.style,title:l.title},null,12,["title"])};var H=e({name:"tags-input",props:{modelValue:{type:Array,required:!0}},emits:{"update:modelValue":null},data:()=>({input:"",values:[]}),created(){this.values=this.modelValue},methods:{onKeyUp(e){if(","===e.key)return this.add(),e.stopPropagation(),!1},add(){const e=this.input.replace(/,/g,"").trim();e&&(this.values.includes(e)||this.values.push(e),this.input="",this.$emit("update:modelValue",this.values))},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const Y=m()(((e,l,o,i,s,u)=>(a(),t("div",null,[g(n("input",{class:"input",type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.input=t),placeholder:"Plants, People",onKeydown:l[2]||(l[2]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:l[3]||(l[3]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[p,e.input]]),(a(!0),t(d,null,c(e.values,((n,l)=>(a(),t("span",{key:l,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));H.render=Y,H.__scopeId="data-v-771460ae";var Q=e({name:"new-image-dialog",components:{ResponsiveImage:q,TagsInput:H},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const l=new FileReader;l.readAsDataURL(n),l.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const Z={key:0,class:"has-image"},K={key:1},J={class:"upload"},X=n("span",{class:"btn"},"Upload File",-1),ee={class:"area-settings"},te=n("td",null,[n("label",null,"Title")],-1),ne=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),le=n("td",null,[n("label",null,"Tags")],-1),oe={class:"area-buttons"},ie=s("🧩 Post to gallery "),ae=n("br",null,null,-1),se=s(" + set up game");Q.render=function(e,l,o,s,r,d){const c=i("responsive-image"),h=i("tags-input");return a(),t("div",{class:"overlay new-image-dialog",onClick:l[8]||(l[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[7]||(l[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(a(),t("div",Z,[n("span",{class:"remove btn",onClick:l[1]||(l[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(a(),t("div",K,[n("label",J,[n("input",{type:"file",style:{display:"none"},onChange:l[2]||(l[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),X])]))],2),n("div",ee,[n("table",null,[n("tr",null,[te,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[3]||(l[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ne,n("tr",null,[le,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[4]||(l[4]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",oe,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:l[5]||(l[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:l[6]||(l[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[ie,ae,se],8,["disabled"])])])])};var re=e({name:"edit-image-dialog",components:{ResponsiveImage:q,TagsInput:H},props:{image:{type:Object,required:!0}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const de={class:"area-image"},ce={class:"has-image"},ue={class:"area-settings"},ge=n("td",null,[n("label",null,"Title")],-1),pe=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"};var ye,fe,we,ve,be,xe;re.render=function(e,l,o,s,r,d){const c=i("responsive-image"),h=i("tags-input");return a(),t("div",{class:"overlay edit-image-dialog",onClick:l[5]||(l[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[4]||(l[4]=u((()=>{}),["stop"]))},[n("div",de,[n("div",ce,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ue,[n("table",null,[n("tr",null,[ge,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),pe,n("tr",null,[he,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":l[2]||(l[2]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",me,[n("button",{class:"btn",onClick:l[3]||(l[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])},(fe=ye||(ye={}))[fe.Flat=0]="Flat",fe[fe.Out=1]="Out",fe[fe.In=-1]="In",(ve=we||(we={}))[ve.FINAL=0]="FINAL",ve[ve.ANY=1]="ANY",(xe=be||(be={}))[xe.NORMAL=0]="NORMAL",xe[xe.ANY=1]="ANY",xe[xe.FLAT=2]="FLAT";var Ce=e({name:"new-game-dialog",components:{ResponsiveImage:q},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:we.ANY,shapeMode:be.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt})}},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)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const ke={class:"area-image"},Ae={class:"has-image"},Se={class:"area-settings"},ze=n("td",null,[n("label",null,"Pieces")],-1),Pe=n("td",null,[n("label",null,"Scoring: ")],-1),Ie=s(" Any (Score when pieces are connected to each other or on final location)"),Te=n("br",null,null,-1),_e=s(" Final (Score when pieces are put to their final location)"),Ee=n("td",null,[n("label",null,"shapes: ")],-1),De=s(" Normal"),Me=n("br",null,null,-1),Be=s(" Any (flat pieces can occur anywhere)"),Oe=n("br",null,null,-1),Ne=s(" Flat (all pieces flat on all sides)"),Ue={class:"area-buttons"};Ce.render=function(e,l,o,s,r,d){const c=i("responsive-image");return a(),t("div",{class:"overlay new-game-dialog",onClick:l[9]||(l[9]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:l[8]||(l[8]=u((()=>{}),["stop"]))},[n("div",ke,[n("div",Ae,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Se,[n("table",null,[n("tr",null,[ze,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":l[1]||(l[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Pe,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[2]||(l[2]=t=>e.scoreMode=t),value:"1"},null,512),[[y,e.scoreMode]]),Ie]),Te,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[3]||(l[3]=t=>e.scoreMode=t),value:"0"},null,512),[[y,e.scoreMode]]),_e])])]),n("tr",null,[Ee,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[4]||(l[4]=t=>e.shapeMode=t),value:"0"},null,512),[[y,e.shapeMode]]),De]),Me,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[5]||(l[5]=t=>e.shapeMode=t),value:"1"},null,512),[[y,e.shapeMode]]),Be]),Oe,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":l[6]||(l[6]=t=>e.shapeMode=t),value:"2"},null,512),[[y,e.shapeMode]]),Ne])])])])]),n("div",Ue,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:l[7]||(l[7]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};class Ge{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 Ge(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const $e=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},Re=(...e)=>{const t=t=>(...n)=>{const l=new Date,o=$e(l.getHours(),"00"),i=$e(l.getMinutes(),"00"),a=$e(l.getSeconds(),"00");console[t](`${o}:${i}:${a}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var Ve={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",Ge.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||we.FINAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:Ge.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}},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("&")}`}},Fe=e({components:{ImageLibrary:W,NewImageDialog:Q,EditImageDialog:re,NewGameDialog:Ce},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},methods:{toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${Ve.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 je={class:"upload-image-teaser"},Le=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),We={key:0},qe=s(" Tags: "),He=s(" Sort by: "),Ye=n("option",{value:"date_desc"},"Newest first",-1),Qe=n("option",{value:"date_asc"},"Oldest first",-1),Ze=n("option",{value:"alpha_asc"},"A-Z",-1),Ke=n("option",{value:"alpha_desc"},"Z-A",-1);Fe.render=function(e,l,s,u,p,h){const m=i("image-library"),y=i("new-image-dialog"),w=i("edit-image-dialog"),v=i("new-game-dialog");return a(),t("div",null,[n("div",je,[n("div",{class:"btn btn-big",onClick:l[1]||(l[1]=t=>e.dialog="new-image")},"Upload your image"),Le]),n("div",null,[e.tags.length>0?(a(),t("label",We,[qe,(a(!0),t(d,null,c(e.tags,((n,l)=>(a(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:l,onClick:t=>e.toggleTag(n)},r(n.title),11,["onClick"])))),128))])):o("",!0),n("label",null,[He,g(n("select",{"onUpdate:modelValue":l[2]||(l[2]=t=>e.filters.sort=t),onChange:l[3]||(l[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[Ye,Qe,Ze,Ke],544),[[f,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(a(),t(y,{key:0,onBgclick:l[4]||(l[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["onPostToGalleryClick","onSetupGameClick"])):o("",!0),"edit-image"===e.dialog?(a(),t(w,{key:1,onBgclick:l[5]||(l[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["onSaveClick","image"])):o("",!0),e.image&&"new-game"===e.dialog?(a(),t(v,{key:2,onBgclick:l[6]||(l[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):o("",!0)])};var Je=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 Xe={class:"scores"},et=n("div",null,"Scores",-1),tt=n("td",null,"⚡",-1),nt=n("td",null,"💤",-1);Je.render=function(e,l,o,i,s,u){return a(),t("div",Xe,[et,n("table",null,[(a(!0),t(d,null,c(e.actives,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[tt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(a(!0),t(d,null,c(e.idles,((e,l)=>(a(),t("tr",{key:l,style:{color:e.color}},[nt,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var lt=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 ot={class:"timer"};lt.render=function(e,l,o,i,s,d){return a(),t("div",ot,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),w(e.$slots,"default")])};var it=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:Object},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const at=n("td",null,[n("label",null,"Background: ")],-1),st=n("td",null,[n("label",null,"Color: ")],-1),rt=n("td",null,[n("label",null,"Name: ")],-1),dt=n("td",null,[n("label",null,"Sounds: ")],-1);it.render=function(e,l,o,i,s,r){return a(),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,[at,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[1]||(l[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[st,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":l[2]||(l[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[rt,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":l[3]||(l[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])]),n("tr",null,[dt,n("td",null,[g(n("input",{type:"checkbox","onUpdate:modelValue":l[4]||(l[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[v,e.modelValue.soundsEnabled]])])])])])};var ct=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const ut={class:"preview"};ct.render=function(e,l,o,i,s,r){return a(),t("div",{class:"overlay",onClick:l[1]||(l[1]=t=>e.$emit("bgclick"))},[n("div",ut,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var gt=1,pt=4,ht=2,mt=3,yt=2,ft=4,wt=3,vt=9,bt=1,xt=2,Ct=3,kt=4,At=5,St=6,zt=7,Pt=8,It=10,Tt=11,_t=1,Et=2,Dt=3;const Mt=Re("Communication.js");let Bt,Ot=[],Nt=e=>{Ot.push(e)},Ut=[],Gt=e=>{Ut.push(e)};let $t=0;const Rt=e=>{$t!==e&&($t=e,Gt(e))};function Vt(e){if(2===$t)try{Bt.send(JSON.stringify(e))}catch(t){Mt.info("unable to send message.. maybe because ws is invalid?")}}let Ft,jt;var Lt={connect:function(e,t,n){return Ft=0,jt={},Rt(3),new Promise((l=>{Bt=new WebSocket(e,n+"|"+t),Bt.onopen=()=>{Rt(2),Vt([mt])},Bt.onmessage=e=>{const t=JSON.parse(e.data),o=t[0];if(o===pt){const e=t[1];l(e)}else{if(o!==gt)throw`[ 2021-05-09 invalid connect msgType ${o} ]`;{const e=t[1],l=t[2];if(e===n&&jt[l])return void delete jt[l];Nt(t)}}},Bt.onerror=()=>{throw Rt(1),"[ 2021-05-15 onerror ]"},Bt.onclose=e=>{4e3===e.code||1001===e.code?Rt(4):Rt(1)}}))},requestReplayData:async function(e,t,n){const l={gameId:e,offset:t,size:n},o=await fetch(`/api/replay-data${Ve.asQueryArgs(l)}`);return await o.json()},disconnect:function(){Bt&&Bt.close(4e3),Ft=0,jt={}},sendClientEvent:function(e){Ft++,jt[Ft]=e,Vt([ht,Ft,jt[Ft]])},onServerChange:function(e){Nt=e;for(const t of Ot)Nt(t);Ot=[]},onConnectionStateChange:function(e){Gt=e;for(const t of Ut)Gt(t);Ut=[]},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},Wt=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Lt.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Lt.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const qt={key:0,class:"overlay connection-lost"},Ht={key:0,class:"overlay-content"},Yt=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Qt={key:1,class:"overlay-content"},Zt=n("div",null,"Connecting...",-1);Wt.render=function(e,l,i,s,r,d){return e.show?(a(),t("div",qt,[e.lostConnection?(a(),t("div",Ht,[Yt,n("span",{class:"btn",onClick:l[1]||(l[1]=t=>e.$emit("reconnect"))},"Reconnect")])):o("",!0),e.connecting?(a(),t("div",Qt,[Zt])):o("",!0)])):o("",!0)};var Kt=e({name:"help-overlay",emits:{bgclick:null}});const Jt=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),Xt=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),en=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),tn=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),nn=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),ln=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),on=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),an=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),sn=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),rn=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);Kt.render=function(e,l,o,i,s,r){return a(),t("div",{class:"overlay transparent",onClick:l[2]||(l[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:l[1]||(l[1]=u((()=>{}),["stop"]))},[Jt,Xt,en,tn,nn,ln,on,an,sn,rn,dn])])};var cn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.550555f3.mp3"}),un=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),gn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),pn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),hn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function mn(){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)},i=l=>({x:l.x/n-e,y:l.y/n-t}),a=l=>({x:(l.x+e)*n,y:(l.y+t)*n}),s=e=>({w:e.w*n,h:e.h*n}),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}=a(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:a,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:i,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 fn={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 l=yn(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=yn(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 wn=Re("Debug.js");let vn=0,bn=0;var xn=e=>{vn=performance.now(),bn=e},Cn=e=>{const t=performance.now(),n=t-vn;n>bn&&wn.log(e+": "+n),vn=t};function kn(e,t){const n=e.x-t.x,l=e.y-t.y;return Math.sqrt(n*n+l*l)}function An(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var Sn={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:kn,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:An,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 kn(An(e),An(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=Re("PuzzleGraphics.js");function Pn(e,t){const n=Ve.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var In={loadPuzzleBitmaps:async function(e){const t=await fn.loadImageToBitmap(e.info.imageUrl),n=await fn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){zn.log("start createPuzzleTileBitmaps");const l=n.tileSize,o=n.tileMarginWidth,i=n.tileDrawSize,a=l/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,i={x:o,y:o},r=Sn.pointAdd(i,{x:l,y:0}),c=Sn.pointAdd(r,{x:0,y:l}),u=Sn.pointSub(c,{x:l,y:0});if(n.moveTo(i.x,i.y),0!==e.top)for(let l=0;lVe.decodePiece(Tn[e].puzzle.tiles[t]),jn=(e,t)=>Fn(e,t).group,Ln=(e,t)=>{const n=Tn[e].puzzle.info,l={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},o=function(e,t){const n=Tn[e].puzzle.info,l=Ve.coordByPieceIdx(n,t),o=l.x*n.tileSize,i=l.y*n.tileSize;return{x:o,y:i}}(e,t);return Sn.pointAdd(l,o)},Wn=(e,t)=>Fn(e,t).pos,qn=e=>{const t=sl(e),n=rl(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}},Hn=(e,t)=>{const n=Kn(e),l=Fn(e,t);return{x:l.pos.x,y:l.pos.y,w:n,h:n}},Yn=(e,t)=>Fn(e,t).z,Qn=(e,t)=>{for(const n of Tn[e].puzzle.tiles){const e=Ve.decodePiece(n);if(e.owner===t)return e.idx}return-1},Zn=e=>Tn[e].puzzle.info.tileDrawSize,Kn=e=>Tn[e].puzzle.info.tileSize,Jn=e=>Tn[e].puzzle.data.maxGroup,Xn=e=>Tn[e].puzzle.data.maxZ;function el(e,t){const n=Tn[e].puzzle.info,l=Ve.coordByPieceIdx(n,t);return[l.y>0?t-n.tilesX:-1,l.x0?t-1:-1]}const tl=(e,t,n)=>{for(const l of t)Vn(e,l,{z:n})},nl=(e,t,n)=>{const l=Wn(e,t);Vn(e,t,{pos:Sn.pointAdd(l,n)})},ll=(e,t,n)=>{const l=Zn(e),o=qn(e),i=n;for(const a of t){const t=Fn(e,a);t.pos.x+n.xo.x+o.w&&(i.x=Math.min(o.x+o.w-t.pos.x+l,i.x)),t.pos.y+n.yo.y+o.h&&(i.y=Math.min(o.y+o.h-t.pos.y+l,i.y))}for(const a of t)nl(e,a,i)},ol=(e,t,n)=>{for(const l of t)Vn(e,l,{owner:n})};function il(e,t){const n=Tn[e].puzzle.tiles,l=Ve.decodePiece(n[t]),o=[];if(l.group)for(const i of n){const e=Ve.decodePiece(i);e.group===l.group&&o.push(e.idx)}else o.push(l.idx);return o}const al=(e,t)=>{const n=En(e,t);return n?n.points:0},sl=e=>Tn[e].puzzle.info.table.width,rl=e=>Tn[e].puzzle.info.table.height;var dl={setGame:function(e,t){Tn[e]=t},exists:function(e){return!!Tn[e]||!1},playerExists:Mn,getActivePlayers:function(e,t){const n=t-30*E;return Bn(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*E;return Bn(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Mn(e,t)?$n(e,t,{ts:n}):Dn(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:Gn,getPieceCount:On,getImageUrl:function(e){return Tn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Tn[e].puzzle.info.imageUrl=t},get:function(e){return Tn[e]||null},getAllGames:function(){return Object.values(Tn).sort(((e,t)=>Un(e.id)===Un(t.id)?t.puzzle.data.started-e.puzzle.data.started:Un(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=En(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=En(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=En(e,t);return n?n.name:null},getPlayerIndexById:_n,getPlayerIdByIndex:function(e,t){return Tn[e].players.length>t?Ve.decodePlayer(Tn[e].players[t]).id:null},changePlayer:$n,setPlayer:Dn,setPiece:function(e,t,n){Tn[e].puzzle.tiles[t]=Ve.encodePiece(n)},setPuzzleData:function(e,t){Tn[e].puzzle.data=t},getTableWidth:sl,getTableHeight:rl,getPuzzle:e=>Tn[e].puzzle,getRng:e=>Tn[e].rng.obj,getPuzzleWidth:e=>Tn[e].puzzle.info.width,getPuzzleHeight:e=>Tn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Tn[e].puzzle.tiles.map(Ve.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Qn(e,t);return n<0?null:Tn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Tn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:Zn,getFinalPiecePos:Ln,getStartTs:e=>Tn[e].puzzle.data.started,getFinishTs:e=>Tn[e].puzzle.data.finished,handleInput:function(e,t,n,l,o){const i=Tn[e].puzzle,a=function(e,t){return t in Tn[e].evtInfos?Tn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],r=()=>{s.push([_t,i.data])},d=t=>{s.push([Et,Ve.encodePiece(Fn(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=En(e,t);n&&s.push([Dt,Ve.encodePlayer(n)])},g=n[0];if(g===St){const o=n[1];$n(e,t,{bgcolor:o,ts:l}),u()}else if(g===zt){const o=n[1];$n(e,t,{color:o,ts:l}),u()}else if(g===Pt){const o=`${n[1]}`.substr(0,16);$n(e,t,{name:o,ts:l}),u()}else if(g===vt){const o=n[1],i=n[2],a=En(e,t);if(a){const n=a.x-o,s=a.y-i;$n(e,t,{ts:l,x:n,y:s}),u()}}else if(g===bt){const o={x:n[1],y:n[2]};$n(e,t,{d:1,ts:l}),u(),a._last_mouse_down=o;const i=((e,t)=>{const n=Tn[e].puzzle.info,l=Tn[e].puzzle.tiles;let o=-1,i=-1;for(let a=0;ao)&&(o=e.z,i=a)}return i})(e,o);if(i>=0){const n=Xn(e)+1;Rn(e,{maxZ:n}),r();const l=il(e,i);tl(e,l,Xn(e)),ol(e,l,t),c(l)}a._last_mouse=o}else if(g===Ct){const o=n[1],i=n[2],s={x:o,y:i};if(null===a._last_mouse_down)$n(e,t,{x:o,y:i,ts:l}),u();else{const n=Qn(e,t);if(n>=0){$n(e,t,{x:o,y:i,ts:l}),u();const r=il(e,n);let d=Sn.pointInBounds(s,qn(e))&&Sn.pointInBounds(a._last_mouse_down,qn(e));for(const t of r){const n=Hn(e,t);if(Sn.pointInBounds(s,n)){d=!0;break}}if(d){const t=o-a._last_mouse_down.x,n=i-a._last_mouse_down.y;ll(e,r,{x:t,y:n}),c(r)}}else $n(e,t,{ts:l}),u();a._last_mouse_down=s}a._last_mouse=s}else if(g===xt){const s={x:n[1],y:n[2]},g=0;a._last_mouse_down=null;const p=Qn(e,t);if(p>=0){const n=il(e,p);ol(e,n,0),c(n);const a=Wn(e,p),s=Ln(e,p);if(Sn.pointDistance(s,a){for(const n of t)Vn(e,n,{owner:-1,z:1})})(e,n),c(n);let d=al(e,t);Nn(e)===we.FINAL?d+=n.length:Nn(e)===we.ANY&&(d+=1),$n(e,t,{d:g,ts:l,points:d}),u(),Gn(e)===On(e)&&(Rn(e,{finished:l}),r()),o&&o(t)}else{const n=(e,t,n,l)=>{const o=Tn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const l=jn(e,t),o=jn(e,n);return!(!l||l!==o)})(e,t,n))return!1;const i=Wn(e,t),a=Sn.pointAdd(Wn(e,n),{x:l[0]*o.tileSize,y:l[1]*o.tileSize});if(Sn.pointDistance(i,a){const l=Tn[e].puzzle.tiles,o=jn(e,t),i=jn(e,n);let a;const s=[];o&&s.push(o),i&&s.push(i),o?a=o:i?a=i:(Rn(e,{maxGroup:Jn(e)+1}),r(),a=Jn(e));if(Vn(e,t,{group:a}),d(t),Vn(e,n,{group:a}),d(n),s.length>0)for(const r of l){const t=Ve.decodePiece(r);s.includes(t.group)&&(Vn(e,t.idx,{group:a}),d(t.idx))}})(e,t,n),o=il(e,t);const s=((e,t)=>{let n=0;for(const l of t){const t=Yn(e,l);t>n&&(n=t)}return n})(e,o);return tl(e,o,s),c(o),!0}return!1};let i=!1;for(const t of il(e,p)){const l=el(e,t);if(n(e,t,l[0],[0,1])||n(e,t,l[1],[-1,0])||n(e,t,l[2],[0,-1])||n(e,t,l[3],[1,0])){i=!0;break}}if(i&&Nn(e)===we.ANY){const n=al(e,t)+1;$n(e,t,{d:g,ts:l,points:n}),u()}else $n(e,t,{d:g,ts:l}),u();i&&o&&o(t)}}else $n(e,t,{d:g,ts:l}),u();a._last_mouse=s}else if(g===kt){const o=n[1],i=n[2];$n(e,t,{x:o,y:i,ts:l}),u(),a._last_mouse={x:o,y:i}}else if(g===At){const o=n[1],i=n[2];$n(e,t,{x:o,y:i,ts:l}),u(),a._last_mouse={x:o,y:i}}else $n(e,t,{ts:l}),u();return function(e,t,n){Tn[e].evtInfos[t]=n}(e,t,a),s}};let cl=-10,ul=20,gl=2,pl=15;class hl{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=cl+Math.random()*ul,this.vy=-1*(gl+Math.random()*pl),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;gl=t/2,pl=t-gl;const n=1/4*this.canvas.width/(t/2);cl=-n,ul=2*n}resize(){this.setSpeedParams()}init(){this.readyBombs=[],this.explodedBombs=[],this.particles=[];for(let e=0;e<1;e++)this.readyBombs.push(new hl(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new hl(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(fn.colorizedCanvas(n,l,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,xl=!0})),t}(o,fn.createCanvas()),v={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};Lt.onConnectionStateChange((e=>{i.setConnectionState(e)}));const b=async e=>{v.requesting=!0;const t=await Lt.requestReplayData(e,v.dataOffset,v.dataSize);return v.dataOffset+=v.dataSize,v.requesting=!1,t};let x=()=>0;const C=async()=>{if("play"===l){const l=await Lt.connect(n,e,t),o=Ve.decodeGame(l);dl.setGame(o.id,o),x=()=>D()}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=Ve.decodeGame(t.game);dl.setGame(n.id,n),v.requesting=!1,v.log=t.log,v.lastRealTs=D(),v.gameStartTs=parseInt(v.log[0][4],10),v.lastGameTs=v.gameStartTs,x=()=>v.lastGameTs}}xl=!0};await C();const k=dl.getPieceDrawOffset(e),A=dl.getPieceDrawSize(e),S=dl.getPuzzleWidth(e),z=dl.getPuzzleHeight(e),P=dl.getTableWidth(e),I=dl.getTableHeight(e),T={x:(P-S)/2,y:(I-z)/2},_={w:S,h:z},E={w:A,h:A},M=await In.loadPuzzleBitmaps(dl.getPuzzle(e)),B=new yl(w,dl.getRng(e));B.init();const O=w.getContext("2d");w.classList.add("loaded");const N=mn();N.move(-(P-w.width)/2,-(I-w.height)/2);const U=function(e,t,n){let l=[],o=!0,i=!1,a=!1,s=!1,r=!1,d=!1,c=!1,u=!1;const g=(e,t)=>{const l=n.viewportToWorld({x:e,y:t});return[l.x,l.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{o&&("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?i=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?a=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};let y=null;e.addEventListener("mousedown",(e=>{y=p(e),0===e.button&&f([bt,...y])})),e.addEventListener("mouseup",(e=>{y=p(e),0===e.button&&f([xt,...y])})),e.addEventListener("mousemove",(e=>{y=p(e),f([Ct,...y])})),e.addEventListener("wheel",(e=>{if(y=p(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?kt:At;f([t,...y])}})),t.addEventListener("keydown",(e=>m(!0,e))),t.addEventListener("keyup",(e=>m(!1,e))),t.addEventListener("keypress",(e=>{o&&(" "===e.key&&f([It]),"F"!==e.key&&"f"!==e.key||(vl=!vl,xl=!0),"G"!==e.key&&"g"!==e.key||(bl=!bl,xl=!0),"M"!==e.key&&"m"!==e.key||f([Tt]))}));const f=e=>{l.push(e)};return{addEvent:f,consumeAll:()=>{if(0===l.length)return[];const e=l.slice();return l=[],e},createKeyEvents:()=>{const e=(i?1:0)-(a?1:0),t=(s?1:0)-(r?1:0);if(0!==e||0!==t){const l=(u?24:12)*Math.sqrt(n.getCurrentZoom()),o=n.viewportDimToWorld({w:e*l,h:t*l});f([vt,o.w,o.h]),y&&(y[0]-=o.w,y[1]-=o.h)}if(d&&c);else if(d){if(n.canZoom("in")){const e=y||h();f([kt,...e])}}else if(c&&n.canZoom("out")){const e=y||h();f([At,...e])}},setHotkeys:e=>{o=e}}}(w,window,N),G=dl.getImageUrl(e),$=()=>{const t=dl.getStartTs(e),n=dl.getFinishTs(e),l=x();i.setFinished(!!n),i.setDuration((n||l)-t)};$(),i.setPiecesDone(dl.getFinishedPiecesCount(e)),i.setPiecesTotal(dl.getPieceCount(e));const R=x();i.setActivePlayers(dl.getActivePlayers(e,R)),i.setIdlePlayers(dl.getIdlePlayers(e,R));const V=!!dl.getFinishTs(e);let F=V;const j=()=>F&&!V,L=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},W=()=>dl.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",q=()=>dl.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"];w.style.cursor=`url('${t}') ${p} ${m}, ${n}`},K=e=>{H=fn.colorizedCanvas(r,c,e).toDataURL(),Y=fn.colorizedCanvas(d,u,e).toDataURL(),Z(Q)};K(q());const J=()=>{i.setReplaySpeed&&i.setReplaySpeed(v.speeds[v.speedIdx]),i.setReplayPaused&&i.setReplayPaused(v.paused)};if("play"===l?setInterval($,1e3):"replay"===l&&J(),"play"===l)Lt.onServerChange((n=>{n[0],n[1],n[2];const l=n[3];for(const[o,i]of l)switch(o){case Dt:{const n=Ve.decodePlayer(i);n.id!==t&&(dl.setPlayer(e,n.id,n),xl=!0)}break;case Et:{const t=Ve.decodePiece(i);dl.setPiece(e,t.idx,t),xl=!0}break;case _t:dl.setPuzzleData(e,i),xl=!0}F=!!dl.getFinishTs(e)}));else if("replay"===l){const t=setInterval((()=>{const n=D();if(v.requesting)return void(v.lastRealTs=n);if(v.logPointer+1>=v.log.length)return v.lastRealTs=n,void(async e=>{const t=await b(e);v.log=v.log.slice(v.logPointer),v.logPointer=0,v.log.push(...t.log),t.log.length=v.log.length){v.final&&clearInterval(t);break}const l=v.log[n],i=v.gameStartTs+l[l.length-1];if(i>o)break;const a=l.slice();if(a[0]===yt){const t=a[1];dl.addPlayer(e,t,i),xl=!0}else if(a[0]===ft){const t=dl.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";dl.addPlayer(e,t,i),xl=!0}else if(a[0]===wt){const t=dl.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=a[2];dl.handleInput(e,t,n,i),xl=!0}v.logPointer=n}v.lastRealTs=n,v.lastGameTs=o,$()}),50)}let X=null;return(e=>{const t=e.fps||60,n=e.slow||1,l=e.update,o=e.render,i=window.requestAnimationFrame,a=1/t,s=n*a;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>s;)d-=s,l(a);o(d/n),c=r,i(u)};i(u)})({update:()=>{U.createKeyEvents();for(const n of U.consumeAll())if("play"===l){const l=n[0];if(l===vt){const e=n[1],t=n[2],l=N.worldDimToViewport({w:e,h:t});xl=!0,N.move(l.w,l.h)}else if(l===Ct){if(X&&!dl.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=N.worldToViewport(e),l=Math.round(t.x-X.x),o=Math.round(t.y-X.y);xl=!0,N.move(l,o),X=t}}else if(l===zt)K(n[1]);else if(l===bt){const e={x:n[1],y:n[2]};X=N.worldToViewport(e),Z(!0)}else if(l===xt)X=null,Z(!1);else if(l===kt){const e={x:n[1],y:n[2]};xl=!0,N.zoom("in",N.worldToViewport(e))}else if(l===At){const e={x:n[1],y:n[2]};xl=!0,N.zoom("out",N.worldToViewport(e))}else l===It?i.togglePreview():l===Tt&&i.toggleSoundsEnabled();const o=x();dl.handleInput(e,t,n,o,(e=>{L()&&s.play()})).length>0&&(xl=!0),Lt.sendClientEvent(n)}else if("replay"===l){const e=n[0];if(e===vt){const e=n[1],t=n[2];xl=!0,N.move(e,t)}else if(e===Ct){if(X){const e={x:n[1],y:n[2]},t=N.worldToViewport(e),l=Math.round(t.x-X.x),o=Math.round(t.y-X.y);xl=!0,N.move(l,o),X=t}}else if(e===bt){const e={x:n[1],y:n[2]};X=N.worldToViewport(e)}else if(e===xt)X=null;else if(e===kt){const e={x:n[1],y:n[2]};xl=!0,N.zoom("in",N.worldToViewport(e))}else if(e===At){const e={x:n[1],y:n[2]};xl=!0,N.zoom("out",N.worldToViewport(e))}else e===It&&i.togglePreview()}F=!!dl.getFinishTs(e),j()&&(B.update(),xl=!0)},render:async()=>{if(!xl)return;const n=x();let o,a,s;window.DEBUG&&xn(0),O.fillStyle=W(),O.fillRect(0,0,w.width,w.height),window.DEBUG&&Cn("clear done"),o=N.worldToViewportRaw(T),a=N.worldDimToViewportRaw(_),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(o.x,o.y,a.w,a.h),window.DEBUG&&Cn("board done");const r=dl.getPiecesSortedByZIndex(e);window.DEBUG&&Cn("get tiles done"),a=N.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?vl:bl)&&(s=M[e.idx],o=N.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(s,0,0,s.width,s.height,o.x,o.y,a.w,a.h));window.DEBUG&&Cn("tiles done");const d=[];for(const i of dl.getActivePlayers(e,n))c=i,("replay"===l||c.id!==t)&&(s=await f(i),o=N.worldToViewport(i),O.drawImage(s,o.x-p,o.y-m),d.push([`${i.name} (${i.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&&Cn("players done"),i.setActivePlayers(dl.getActivePlayers(e,n)),i.setIdlePlayers(dl.getIdlePlayers(e,n)),i.setPiecesDone(dl.getFinishedPiecesCount(e)),window.DEBUG&&Cn("HUD done"),j()&&B.render(),xl=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([St,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([zt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Pt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},replayOnSpeedUp:()=>{v.speedIdx+1{v.speedIdx>=1&&(v.speedIdx--,J())},replayOnPauseToggle:()=>{v.paused=!v.paused,J()},previewImageUrl:G,player:{background:W(),color:q(),name:dl.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:L()},disconnect:Lt.disconnect,connect:C}}var kl=e({name:"game",components:{PuzzleStatus:lt,Scores:Je,SettingsOverlay:it,PreviewOverlay:ct,ConnectionOverlay:Wt,HelpOverlay:Kt},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=>{},disconnect:()=>{},connect:()=>{}}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Cl(`${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.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"},zl={class:"tabs"},Pl=s("🧩 Puzzles");kl.render=function(e,o,s,r,d,c){const u=i("settings-overlay"),p=i("preview-overlay"),h=i("help-overlay"),m=i("connection-overlay"),y=i("puzzle-status"),f=i("router-link"),w=i("scores");return a(),t("div",Al,[g(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"]),[[b,"settings"===e.overlay]]),g(n(p,{onBgclick:o[3]||(o[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[b,"preview"===e.overlay]]),g(n(h,{onBgclick:o[4]||(o[4]=t=>e.toggle("help",!0))},null,512),[[b,"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",Sl,[n("div",zl,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[Pl])),_: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(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var Il=e({name:"replay",components:{PuzzleStatus:lt,Scores:Je,SettingsOverlay:it,PreviewOverlay:ct,HelpOverlay:Kt},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:()=>{},disconnect:()=>{}},replay:{speed:1,paused:!1}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await Cl(`${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.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"},_l={class:"menu"},El={class:"tabs"},Dl=s("🧩 Puzzles");Il.render=function(e,o,s,d,c,u){const p=i("settings-overlay"),h=i("preview-overlay"),m=i("help-overlay"),y=i("puzzle-status"),f=i("router-link"),w=i("scores");return a(),t("div",Tl,[g(n(p,{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"]),[[b,"settings"===e.overlay]]),g(n(h,{onBgclick:o[3]||(o[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[b,"preview"===e.overlay]]),g(n(m,{onBgclick:o[4]||(o[4]=t=>e.toggle("help",!0))},null,512),[[b,"help"===e.overlay]]),n(y,{finished:e.finished,duration:e.duration,piecesDone:e.piecesDone,piecesTotal:e.piecesTotal},{default:l((()=>[n("div",null,[n("div",null,r(e.replayText),1),n("button",{class:"btn",onClick:o[5]||(o[5]=t=>e.g.replayOnSpeedUp())},"⏫"),n("button",{class:"btn",onClick:o[6]||(o[6]=t=>e.g.replayOnSpeedDown())},"⏬"),n("button",{class:"btn",onClick:o[7]||(o[7]=t=>e.g.replayOnPauseToggle())},"⏸️")])])),_:1},8,["finished","duration","piecesDone","piecesTotal"]),n("div",_l,[n("div",El,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:l((()=>[Dl])),_:1}),n("div",{class:"opener",onClick:o[8]||(o[8]=t=>e.toggle("preview",!1))},"🖼️ Preview"),n("div",{class:"opener",onClick:o[9]||(o[9]=t=>e.toggle("settings",!0))},"🛠️ Settings"),n("div",{class:"opener",onClick:o[10]||(o[10]=t=>e.toggle("help",!0))},"ℹ️ Help")])]),n(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=x({history:C(),routes:[{name:"index",path:"/",component:V},{name:"new-game",path:"/new-game",component:Fe},{name:"game",path:"/g/:id",component:kl},{name:"replay",path:"/replay/:id",component:Il}]});n.beforeEach(((e,t)=>{t.name&&document.documentElement.classList.remove(`view-${String(t.name)}`),document.documentElement.classList.add(`view-${String(e.name)}`)}));const l=k(A);l.config.globalProperties.$config=t,l.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=Ve.uniqId(),localStorage.setItem("ID",e)),e}(),l.use(n),l.mount("#app")})(); diff --git a/build/public/assets/index.3ca85e0f.js b/build/public/assets/index.3ca85e0f.js deleted file mode 100644 index 9e662aa..0000000 --- a/build/public/assets/index.3ca85e0f.js +++ /dev/null @@ -1 +0,0 @@ -import{d as e,c as t,a as n,w as o,b as l,r as i,o as a,e as s,t as r,F as d,f as c,g as u,h as g,v as p,i as h,j as m,k as y,l as f,m as w,n as v,p as b,q as x,s as C,u as k}from"./vendor.18cd2d7e.js";var A=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const S={id:"app"},z={key:0,class:"nav"},P=s("Index"),I=s("New game");A.render=function(e,s,r,d,c,u){const g=i("router-link"),p=i("router-view");return a(),t("div",S,[e.showNav?(a(),t("ul",z,[n("li",null,[n(g,{class:"btn",to:{name:"index"}},{default:o((()=>[P])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:o((()=>[I])),_:1})])])):l("",!0),n(p)])};const T=864e5,_=e=>{const t=Math.floor(e/T);e%=T;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 E=1e3,D=()=>{const e=new Date;return Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate(),e.getUTCHours(),e.getUTCMinutes(),e.getUTCSeconds(),e.getUTCMilliseconds())},B=(e,t)=>_(t-e),O=_,M=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||D();return`${n} ${B(o,l)}`}}});const U={class:"game-info-text"},N=n("br",null,null,-1),G=n("br",null,null,-1),$=n("br",null,null,-1),R=s(" ↪️ Watch replay ");M.render=function(e,d,c,u,g,p){const h=i("router-link");return a(),t("div",{class:"game-teaser",style:e.style},[n(h,{class:"game-info",to:{name:"game",params:{id:e.game.id}}},{default:o((()=>[n("span",U,[s(" 🧩 "+r(e.game.tilesFinished)+"/"+r(e.game.tilesTotal),1),N,s(" 👥 "+r(e.game.players),1),G,s(" "+r(e.time(e.game.started,e.game.finished)),1),$])])),_:1},8,["to"]),e.game.hasReplay?(a(),t(h,{key:0,class:"game-replay",to:{name:"replay",params:{id:e.game.id}}},{default:o((()=>[R])),_:1},8,["to"])):l("",!0)],4)};var V=e({components:{GameTeaser:M},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),F=n("h1",null,"Finished games",-1);V.render=function(e,o,l,s,r,u){const g=i("game-teaser");return a(),t("div",null,[j,(a(!0),t(d,null,c(e.gamesRunning,((e,o)=>(a(),t("div",{class:"game-teaser-wrap",key:o},[n(g,{game:e},null,8,["game"])])))),128)),F,(a(!0),t(d,null,c(e.gamesFinished,((e,o)=>(a(),t("div",{class:"game-teaser-wrap",key:o},[n(g,{game:e},null,8,["game"])])))),128))])};var L=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")}}});L.render=function(e,o,l,i,s,r){return a(),t("div",{class:"imageteaser",style:e.style,onClick:o[2]||(o[2]=(...t)=>e.onClick&&e.onClick(...t))},[n("div",{class:"btn edit",onClick:o[1]||(o[1]=u(((...t)=>e.onEditClick&&e.onEditClick(...t)),["stop"]))},"✏️")],4)};var W=e({name:"image-library",components:{ImageTeaser:L},props:{images:{type:Array,required:!0}},emits:{imageClicked:null,imageEditClicked:null},methods:{imageClicked(e){this.$emit("imageClicked",e)},imageEditClicked(e){this.$emit("imageEditClicked",e)}}});W.render=function(e,n,o,l,s,r){const u=i("image-teaser");return a(),t("div",null,[(a(!0),t(d,null,c(e.images,((n,o)=>(a(),t(u,{image:n,onClick:t=>e.imageClicked(n),onEditClick:t=>e.imageEditClicked(n),key:o},null,8,["image","onClick","onEditClick"])))),128))])};const q={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}}}};q.render=function(e,n,o,l,i,s){return a(),t("div",{style:s.style,title:o.title},null,12,["title"])};var H=e({name:"tags-input",props:{modelValue:{type:Array,required:!0}},emits:{"update:modelValue":null},data:()=>({input:"",values:[]}),created(){this.values=this.modelValue},methods:{onKeyUp(e){if(","===e.key)return this.add(),e.stopPropagation(),!1},add(){const e=this.input.replace(/,/g,"").trim();e&&(this.values.includes(e)||this.values.push(e),this.input="",this.$emit("update:modelValue",this.values))},rm(e){this.values=this.values.filter((t=>t!==e)),this.$emit("update:modelValue",this.values)}}});const Q=m()(((e,o,l,i,s,u)=>(a(),t("div",null,[g(n("input",{class:"input",type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.input=t),placeholder:"Plants, People",onKeydown:o[2]||(o[2]=h(((...t)=>e.add&&e.add(...t)),["enter"])),onKeyup:o[3]||(o[3]=(...t)=>e.onKeyUp&&e.onKeyUp(...t))},null,544),[[p,e.input]]),(a(!0),t(d,null,c(e.values,((n,o)=>(a(),t("span",{key:o,class:"bit",onClick:t=>e.rm(n)},r(n)+" ✖",9,["onClick"])))),128))]))));H.render=Q,H.__scopeId="data-v-771460ae";var Y=e({name:"new-image-dialog",components:{ResponsiveImage:q,TagsInput:H},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const o=new FileReader;o.readAsDataURL(n),o.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const Z={key:0,class:"has-image"},K={key:1},J={class:"upload"},X=n("span",{class:"btn"},"Upload File",-1),ee={class:"area-settings"},te=n("td",null,[n("label",null,"Title")],-1),ne=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),oe=n("td",null,[n("label",null,"Tags")],-1),le={class:"area-buttons"},ie=s("🧩 Post to gallery "),ae=n("br",null,null,-1),se=s(" + set up game");Y.render=function(e,o,l,s,r,d){const c=i("responsive-image"),h=i("tags-input");return a(),t("div",{class:"overlay new-image-dialog",onClick:o[8]||(o[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[7]||(o[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(a(),t("div",Z,[n("span",{class:"remove btn",onClick:o[1]||(o[1]=t=>e.previewUrl="")},"X"),n(c,{src:e.previewUrl},null,8,["src"])])):(a(),t("div",K,[n("label",J,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),X])]))],2),n("div",ee,[n("table",null,[n("tr",null,[te,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[3]||(o[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ne,n("tr",null,[oe,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[4]||(o[4]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",le,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[5]||(o[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[6]||(o[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[ie,ae,se],8,["disabled"])])])])};var re=e({name:"edit-image-dialog",components:{ResponsiveImage:q,TagsInput:H},props:{image:{type:Object,required:!0}},emits:{bgclick:null,saveClick:null},data:()=>({title:"",tags:[]}),created(){this.title=this.image.title,this.tags=this.image.tags.map((e=>e.title))},methods:{saveImage(){this.$emit("saveClick",{id:this.image.id,title:this.title,tags:this.tags})}}});const de={class:"area-image"},ce={class:"has-image"},ue={class:"area-settings"},ge=n("td",null,[n("label",null,"Title")],-1),pe=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"};var ye,fe,we,ve;re.render=function(e,o,l,s,r,d){const c=i("responsive-image"),h=i("tags-input");return a(),t("div",{class:"overlay edit-image-dialog",onClick:o[5]||(o[5]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[4]||(o[4]=u((()=>{}),["stop"]))},[n("div",de,[n("div",ce,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ue,[n("table",null,[n("tr",null,[ge,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),pe,n("tr",null,[he,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[2]||(o[2]=t=>e.tags=t)},null,8,["modelValue"])])])])]),n("div",me,[n("button",{class:"btn",onClick:o[3]||(o[3]=(...t)=>e.saveImage&&e.saveImage(...t))},"🖼️ Save image")])])])},(fe=ye||(ye={}))[fe.Flat=0]="Flat",fe[fe.Out=1]="Out",fe[fe.In=-1]="In",(ve=we||(we={}))[ve.FINAL=0]="FINAL",ve[ve.ANY=1]="ANY";var be=e({name:"new-game-dialog",components:{ResponsiveImage:q},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:we.ANY}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt})}},computed:{canStartNewGame(){return!!(this.tilesInt&&this.image&&this.image.url&&[0,1].includes(this.scoreModeInt))},scoreModeInt(){return parseInt(`${this.scoreMode}`,10)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const xe={class:"area-image"},Ce={class:"has-image"},ke={class:"area-settings"},Ae=n("td",null,[n("label",null,"Pieces")],-1),Se=n("td",null,[n("label",null,"Scoring: ")],-1),ze=s(" Any (Score when pieces are connected to each other or on final location)"),Pe=n("br",null,null,-1),Ie=s(" Final (Score when pieces are put to their final location)"),Te={class:"area-buttons"};be.render=function(e,o,l,s,r,d){const c=i("responsive-image");return a(),t("div",{class:"overlay new-game-dialog",onClick:o[6]||(o[6]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[5]||(o[5]=u((()=>{}),["stop"]))},[n("div",xe,[n("div",Ce,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",ke,[n("table",null,[n("tr",null,[Ae,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Se,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[y,e.scoreMode]]),ze]),Pe,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[y,e.scoreMode]]),Ie])])])])]),n("div",Te,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[4]||(o[4]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};class _e{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 _e(0);return t.rand_high=e.rand_high,t.rand_low=e.rand_low,t}}const Ee=(e,t)=>{const n=`${e}`;return n.length>=t.length?n:t.substr(0,t.length-n.length)+n},De=(...e)=>{const t=t=>(...n)=>{const o=new Date,l=Ee(o.getHours(),"00"),i=Ee(o.getMinutes(),"00"),a=Ee(o.getSeconds(),"00");console[t](`${l}:${i}:${a}`,...e,...n)};return{log:t("log"),error:t("error"),info:t("info")}};var Be={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",_e.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||we.FINAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:_e.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}},Oe=e({components:{ImageLibrary:W,NewImageDialog:Y,EditImageDialog:re,NewGameDialog:be},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},methods:{toggleTag(e){this.filters.tags.includes(e.slug)?this.filters.tags=this.filters.tags.filter((t=>t!==e.slug)):this.filters.tags.push(e.slug),this.filtersChanged()},async loadImages(){const e=await fetch(`/api/newgame-data${Be.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/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 Me={class:"upload-image-teaser"},Ue=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),Ne={key:0},Ge=s(" Tags: "),$e=s(" Sort by: "),Re=n("option",{value:"date_desc"},"Newest first",-1),Ve=n("option",{value:"date_asc"},"Oldest first",-1),je=n("option",{value:"alpha_asc"},"A-Z",-1),Fe=n("option",{value:"alpha_desc"},"Z-A",-1);Oe.render=function(e,o,s,u,p,h){const m=i("image-library"),y=i("new-image-dialog"),w=i("edit-image-dialog"),v=i("new-game-dialog");return a(),t("div",null,[n("div",Me,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),Ue]),n("div",null,[e.tags.length>0?(a(),t("label",Ne,[Ge,(a(!0),t(d,null,c(e.tags,((n,o)=>(a(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title),11,["onClick"])))),128))])):l("",!0),n("label",null,[$e,g(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[Re,Ve,je,Fe],544),[[f,e.filters.sort]])])]),n(m,{images:e.images,onImageClicked:e.onImageClicked,onImageEditClicked:e.onImageEditClicked},null,8,["images","onImageClicked","onImageEditClicked"]),"new-image"===e.dialog?(a(),t(y,{key:0,onBgclick:o[4]||(o[4]=t=>e.dialog=""),onPostToGalleryClick:e.postToGalleryClick,onSetupGameClick:e.setupGameClick},null,8,["onPostToGalleryClick","onSetupGameClick"])):l("",!0),"edit-image"===e.dialog?(a(),t(w,{key:1,onBgclick:o[5]||(o[5]=t=>e.dialog=""),onSaveClick:e.onSaveImageClick,image:e.image},null,8,["onSaveClick","image"])):l("",!0),e.image&&"new-game"===e.dialog?(a(),t(v,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var Le=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 We={class:"scores"},qe=n("div",null,"Scores",-1),He=n("td",null,"⚡",-1),Qe=n("td",null,"💤",-1);Le.render=function(e,o,l,i,s,u){return a(),t("div",We,[qe,n("table",null,[(a(!0),t(d,null,c(e.actives,((e,o)=>(a(),t("tr",{key:o,style:{color:e.color}},[He,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128)),(a(!0),t(d,null,c(e.idles,((e,o)=>(a(),t("tr",{key:o,style:{color:e.color}},[Qe,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var Ye=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 O(this.duration)}}});const Ze={class:"timer"};Ye.render=function(e,o,l,i,s,d){return a(),t("div",Ze,[n("div",null," 🧩 "+r(e.piecesDone)+"/"+r(e.piecesTotal),1),n("div",null,r(e.icon)+" "+r(e.durationStr),1),w(e.$slots,"default")])};var Ke=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:Object},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const Je=n("td",null,[n("label",null,"Background: ")],-1),Xe=n("td",null,[n("label",null,"Color: ")],-1),et=n("td",null,[n("label",null,"Name: ")],-1),tt=n("td",null,[n("label",null,"Sounds: ")],-1);Ke.render=function(e,o,l,i,s,r){return a(),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,[Je,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[Xe,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[et,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])]),n("tr",null,[tt,n("td",null,[g(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[v,e.modelValue.soundsEnabled]])])])])])};var nt=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const ot={class:"preview"};nt.render=function(e,o,l,i,s,r){return a(),t("div",{class:"overlay",onClick:o[1]||(o[1]=t=>e.$emit("bgclick"))},[n("div",ot,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var lt=1,it=4,at=2,st=3,rt=2,dt=4,ct=3,ut=9,gt=1,pt=2,ht=3,mt=4,yt=5,ft=6,wt=7,vt=8,bt=10,xt=11,Ct=1,kt=2,At=3;const St=De("Communication.js");let zt,Pt=[],It=e=>{Pt.push(e)},Tt=[],_t=e=>{Tt.push(e)};let Et=0;const Dt=e=>{Et!==e&&(Et=e,_t(e))};function Bt(e){if(2===Et)try{zt.send(JSON.stringify(e))}catch(t){St.info("unable to send message.. maybe because ws is invalid?")}}let Ot,Mt;var Ut={connect:function(e,t,n){return Ot=0,Mt={},Dt(3),new Promise((o=>{zt=new WebSocket(e,n+"|"+t),zt.onopen=()=>{Dt(2),Bt([st])},zt.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===it){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&&Mt[o])return void delete Mt[o];It(t)}}},zt.onerror=()=>{throw Dt(1),"[ 2021-05-15 onerror ]"},zt.onclose=e=>{4e3===e.code||1001===e.code?Dt(4):Dt(1)}}))},requestReplayData:async function(e,t,n){const o={gameId:e,offset:t,size:n},l=await fetch(`/api/replay-data${Be.asQueryArgs(o)}`);return await l.json()},disconnect:function(){zt&&zt.close(4e3),Ot=0,Mt={}},sendClientEvent:function(e){Ot++,Mt[Ot]=e,Bt([at,Ot,Mt[Ot]])},onServerChange:function(e){It=e;for(const t of Pt)It(t);Pt=[]},onConnectionStateChange:function(e){_t=e;for(const t of Tt)_t(t);Tt=[]},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},Nt=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Ut.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Ut.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const Gt={key:0,class:"overlay connection-lost"},$t={key:0,class:"overlay-content"},Rt=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Vt={key:1,class:"overlay-content"},jt=n("div",null,"Connecting...",-1);Nt.render=function(e,o,i,s,r,d){return e.show?(a(),t("div",Gt,[e.lostConnection?(a(),t("div",$t,[Rt,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(a(),t("div",Vt,[jt])):l("",!0)])):l("",!0)};var Ft=e({name:"help-overlay",emits:{bgclick:null}});const Lt=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),Wt=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),qt=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),Ht=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),Qt=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("Move faster by holding "),n("kbd",null,"Shift")])])],-1),Yt=n("tr",null,[n("td",null,"🔍+ Zoom in:"),n("td",null,[n("div",null,[n("kbd",null,"E"),s("/🖱️-Wheel")])])],-1),Zt=n("tr",null,[n("td",null,"🔍- Zoom out:"),n("td",null,[n("div",null,[n("kbd",null,"Q"),s("/🖱️-Wheel")])])],-1),Kt=n("tr",null,[n("td",null,"🖼️ Toggle preview:"),n("td",null,[n("div",null,[n("kbd",null,"Space")])])],-1),Jt=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),Xt=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),en=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1);Ft.render=function(e,o,l,i,s,r){return a(),t("div",{class:"overlay transparent",onClick:o[2]||(o[2]=t=>e.$emit("bgclick"))},[n("table",{class:"overlay-content help",onClick:o[1]||(o[1]=u((()=>{}),["stop"]))},[Lt,Wt,qt,Ht,Qt,Yt,Zt,Kt,Jt,Xt,en])])};var tn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.550555f3.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="}),ln=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),an=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function sn(){let e=0,t=0,n=1;const o=(o,l)=>{e+=o/n,t+=l/n},l=e=>{const t=n+.05*n*("in"===e?1:-1);return Math.min(Math.max(t,.1),6)},i=o=>({x:o.x/n-e,y:o.y/n-t}),a=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}=a(e);return{x:Math.round(t),y:Math.round(n)}},worldToViewportRaw:a,worldDimToViewport:e=>{const{w:t,h:n}=s(e);return{w:Math.round(t),h:Math.round(n)}},worldDimToViewportRaw:s,viewportToWorld:e=>{const{x:t,y:n}=i(e);return{x:Math.round(t),y:Math.round(n)}},viewportToWorldRaw:i,viewportDimToWorld:e=>{const{w:t,h:n}=r(e);return{w:Math.round(t),h:Math.round(n)}},viewportDimToWorldRaw:r}}function rn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var dn={createCanvas:rn,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=rn(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=rn(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 cn=De("Debug.js");let un=0,gn=0;var pn=e=>{un=performance.now(),gn=e},hn=e=>{const t=performance.now(),n=t-un;n>gn&&cn.log(e+": "+n),un=t};function mn(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 fn={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:mn,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 mn(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 wn=De("PuzzleGraphics.js");function vn(e,t){const n=Be.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var bn={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){wn.log("start createPuzzleTileBitmaps");const o=n.tileSize,l=n.tileMarginWidth,i=n.tileDrawSize,a=o/100,s=[0,0,40,15,37,5,37,5,40,0,38,-5,38,-5,20,-20,50,-20,50,-20,80,-20,62,-5,62,-5,60,0,63,5,63,5,65,15,100,0],r=new Array(t.length),d={};function c(e){const t=`${e.top}${e.right}${e.left}${e.bottom}`;if(d[t])return d[t];const n=new Path2D,i={x:l,y:l},r=fn.pointAdd(i,{x:o,y:0}),c=fn.pointAdd(r,{x:0,y:o}),u=fn.pointSub(c,{x:o,y:0});if(n.moveTo(i.x,i.y),0!==e.top)for(let o=0;oBe.decodePiece(xn[e].puzzle.tiles[t]),Mn=(e,t)=>On(e,t).group,Un=(e,t)=>{const n=xn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=xn[e].puzzle.info,o=Be.coordByPieceIdx(n,t),l=o.x*n.tileSize,i=o.y*n.tileSize;return{x:l,y:i}}(e,t);return fn.pointAdd(o,l)},Nn=(e,t)=>On(e,t).pos,Gn=e=>{const t=Xn(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}},$n=(e,t)=>{const n=Fn(e),o=On(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Rn=(e,t)=>On(e,t).z,Vn=(e,t)=>{for(const n of xn[e].puzzle.tiles){const e=Be.decodePiece(n);if(e.owner===t)return e.idx}return-1},jn=e=>xn[e].puzzle.info.tileDrawSize,Fn=e=>xn[e].puzzle.info.tileSize,Ln=e=>xn[e].puzzle.data.maxGroup,Wn=e=>xn[e].puzzle.data.maxZ;function qn(e,t){const n=xn[e].puzzle.info,o=Be.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const Hn=(e,t,n)=>{for(const o of t)Bn(e,o,{z:n})},Qn=(e,t,n)=>{const o=Nn(e,t);Bn(e,t,{pos:fn.pointAdd(o,n)})},Yn=(e,t,n)=>{const o=jn(e),l=Gn(e),i=n;for(const a of t){const t=On(e,a);t.pos.x+n.xl.x+l.w&&(i.x=Math.min(l.x+l.w-t.pos.x+o,i.x)),t.pos.y+n.yl.y+l.h&&(i.y=Math.min(l.y+l.h-t.pos.y+o,i.y))}for(const a of t)Qn(e,a,i)},Zn=(e,t,n)=>{for(const o of t)Bn(e,o,{owner:n})};function Kn(e,t){const n=xn[e].puzzle.tiles,o=Be.decodePiece(n[t]),l=[];if(o.group)for(const i of n){const e=Be.decodePiece(i);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const Jn=(e,t)=>{const n=kn(e,t);return n?n.points:0},Xn=e=>xn[e].puzzle.info.table.width,eo=e=>xn[e].puzzle.info.table.height;var to={setGame:function(e,t){xn[e]=t},exists:function(e){return!!xn[e]||!1},playerExists:Sn,getActivePlayers:function(e,t){const n=t-30*E;return zn(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*E;return zn(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Sn(e,t)?En(e,t,{ts:n}):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,n))},getFinishedPiecesCount:_n,getPieceCount:Pn,getImageUrl:function(e){return xn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){xn[e].puzzle.info.imageUrl=t},get:function(e){return xn[e]||null},getAllGames:function(){return Object.values(xn).sort(((e,t)=>Tn(e.id)===Tn(t.id)?t.puzzle.data.started-e.puzzle.data.started:Tn(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:Cn,getPlayerIdByIndex:function(e,t){return xn[e].players.length>t?Be.decodePlayer(xn[e].players[t]).id:null},changePlayer:En,setPlayer:An,setPiece:function(e,t,n){xn[e].puzzle.tiles[t]=Be.encodePiece(n)},setPuzzleData:function(e,t){xn[e].puzzle.data=t},getTableWidth:Xn,getTableHeight:eo,getPuzzle:e=>xn[e].puzzle,getRng:e=>xn[e].rng.obj,getPuzzleWidth:e=>xn[e].puzzle.info.width,getPuzzleHeight:e=>xn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return xn[e].puzzle.tiles.map(Be.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Vn(e,t);return n<0?null:xn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>xn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:jn,getFinalPiecePos:Un,getStartTs:e=>xn[e].puzzle.data.started,getFinishTs:e=>xn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const i=xn[e].puzzle,a=function(e,t){return t in xn[e].evtInfos?xn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],r=()=>{s.push([Ct,i.data])},d=t=>{s.push([kt,Be.encodePiece(On(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=kn(e,t);n&&s.push([At,Be.encodePlayer(n)])},g=n[0];if(g===ft){const l=n[1];En(e,t,{bgcolor:l,ts:o}),u()}else if(g===wt){const l=n[1];En(e,t,{color:l,ts:o}),u()}else if(g===vt){const l=`${n[1]}`.substr(0,16);En(e,t,{name:l,ts:o}),u()}else if(g===ut){const l=n[1],i=n[2],a=kn(e,t);if(a){const n=a.x-l,s=a.y-i;En(e,t,{ts:o,x:n,y:s}),u()}}else if(g===gt){const l={x:n[1],y:n[2]};En(e,t,{d:1,ts:o}),u(),a._last_mouse_down=l;const i=((e,t)=>{const n=xn[e].puzzle.info,o=xn[e].puzzle.tiles;let l=-1,i=-1;for(let a=0;al)&&(l=e.z,i=a)}return i})(e,l);if(i>=0){const n=Wn(e)+1;Dn(e,{maxZ:n}),r();const o=Kn(e,i);Hn(e,o,Wn(e)),Zn(e,o,t),c(o)}a._last_mouse=l}else if(g===ht){const l=n[1],i=n[2],s={x:l,y:i};if(null===a._last_mouse_down)En(e,t,{x:l,y:i,ts:o}),u();else{const n=Vn(e,t);if(n>=0){En(e,t,{x:l,y:i,ts:o}),u();const r=Kn(e,n);let d=fn.pointInBounds(s,Gn(e))&&fn.pointInBounds(a._last_mouse_down,Gn(e));for(const t of r){const n=$n(e,t);if(fn.pointInBounds(s,n)){d=!0;break}}if(d){const t=l-a._last_mouse_down.x,n=i-a._last_mouse_down.y;Yn(e,r,{x:t,y:n}),c(r)}}else En(e,t,{ts:o}),u();a._last_mouse_down=s}a._last_mouse=s}else if(g===pt){const s={x:n[1],y:n[2]},g=0;a._last_mouse_down=null;const p=Vn(e,t);if(p>=0){const n=Kn(e,p);Zn(e,n,0),c(n);const a=Nn(e,p),s=Un(e,p);if(fn.pointDistance(s,a){for(const n of t)Bn(e,n,{owner:-1,z:1})})(e,n),c(n);let d=Jn(e,t);In(e)===we.FINAL?d+=n.length:In(e)===we.ANY&&(d+=1),En(e,t,{d:g,ts:o,points:d}),u(),_n(e)===Pn(e)&&(Dn(e,{finished:o}),r()),l&&l(t)}else{const n=(e,t,n,o)=>{const l=xn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=Mn(e,t),l=Mn(e,n);return!(!o||o!==l)})(e,t,n))return!1;const i=Nn(e,t),a=fn.pointAdd(Nn(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(fn.pointDistance(i,a){const o=xn[e].puzzle.tiles,l=Mn(e,t),i=Mn(e,n);let a;const s=[];l&&s.push(l),i&&s.push(i),l?a=l:i?a=i:(Dn(e,{maxGroup:Ln(e)+1}),r(),a=Ln(e));if(Bn(e,t,{group:a}),d(t),Bn(e,n,{group:a}),d(n),s.length>0)for(const r of o){const t=Be.decodePiece(r);s.includes(t.group)&&(Bn(e,t.idx,{group:a}),d(t.idx))}})(e,t,n),l=Kn(e,t);const s=((e,t)=>{let n=0;for(const o of t){const t=Rn(e,o);t>n&&(n=t)}return n})(e,l);return Hn(e,l,s),c(l),!0}return!1};let i=!1;for(const t of Kn(e,p)){const o=qn(e,t);if(n(e,t,o[0],[0,1])||n(e,t,o[1],[-1,0])||n(e,t,o[2],[0,-1])||n(e,t,o[3],[1,0])){i=!0;break}}if(i&&In(e)===we.ANY){const n=Jn(e,t)+1;En(e,t,{d:g,ts:o,points:n}),u()}else En(e,t,{d:g,ts:o}),u();i&&l&&l(t)}}else En(e,t,{d:g,ts:o}),u();a._last_mouse=s}else if(g===mt){const l=n[1],i=n[2];En(e,t,{x:l,y:i,ts:o}),u(),a._last_mouse={x:l,y:i}}else if(g===yt){const l=n[1],i=n[2];En(e,t,{x:l,y:i,ts:o}),u(),a._last_mouse={x:l,y:i}}else En(e,t,{ts:o}),u();return function(e,t,n){xn[e].evtInfos[t]=n}(e,t,a),s}};let no=-10,oo=20,lo=2,io=15;class ao{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*(lo+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;lo=t/2,io=t-lo;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 ao(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new ao(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]},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,ho=!0})),t}(l,dn.createCanvas()),v={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};Ut.onConnectionStateChange((e=>{i.setConnectionState(e)}));const b=async e=>{v.requesting=!0;const t=await Ut.requestReplayData(e,v.dataOffset,v.dataSize);return v.dataOffset+=v.dataSize,v.requesting=!1,t};let x=()=>0;const C=async()=>{if("play"===o){const o=await Ut.connect(n,e,t),l=Be.decodeGame(o);to.setGame(l.id,l),x=()=>D()}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=Be.decodeGame(t.game);to.setGame(n.id,n),v.requesting=!1,v.log=t.log,v.lastRealTs=D(),v.gameStartTs=parseInt(v.log[0][4],10),v.lastGameTs=v.gameStartTs,x=()=>v.lastGameTs}}ho=!0};await C();const k=to.getPieceDrawOffset(e),A=to.getPieceDrawSize(e),S=to.getPuzzleWidth(e),z=to.getPuzzleHeight(e),P=to.getTableWidth(e),I=to.getTableHeight(e),T={x:(P-S)/2,y:(I-z)/2},_={w:S,h:z},E={w:A,h:A},B=await bn.loadPuzzleBitmaps(to.getPuzzle(e)),O=new ro(w,to.getRng(e));O.init();const M=w.getContext("2d");w.classList.add("loaded");const U=sn();U.move(-(P-w.width)/2,-(I-w.height)/2);const N=function(e,t,n){let o=[],l=!0,i=!1,a=!1,s=!1,r=!1,d=!1,c=!1,u=!1;const g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{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?i=e:"ArrowRight"===t.key||"d"===t.key||"D"===t.key?a=e:"q"===t.key?c=e:"e"===t.key&&(d=e))};let y=null;e.addEventListener("mousedown",(e=>{y=p(e),0===e.button&&f([gt,...y])})),e.addEventListener("mouseup",(e=>{y=p(e),0===e.button&&f([pt,...y])})),e.addEventListener("mousemove",(e=>{y=p(e),f([ht,...y])})),e.addEventListener("wheel",(e=>{if(y=p(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?mt:yt;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([bt]),"F"!==e.key&&"f"!==e.key||(go=!go,ho=!0),"G"!==e.key&&"g"!==e.key||(po=!po,ho=!0),"M"!==e.key&&"m"!==e.key||f([xt]))}));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=(i?1:0)-(a?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([ut,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([mt,...e])}}else if(c&&n.canZoom("out")){const e=y||h();f([yt,...e])}},setHotkeys:e=>{l=e}}}(w,window,U),G=to.getImageUrl(e),$=()=>{const t=to.getStartTs(e),n=to.getFinishTs(e),o=x();i.setFinished(!!n),i.setDuration((n||o)-t)};$(),i.setPiecesDone(to.getFinishedPiecesCount(e)),i.setPiecesTotal(to.getPieceCount(e));const R=x();i.setActivePlayers(to.getActivePlayers(e,R)),i.setIdlePlayers(to.getIdlePlayers(e,R));const V=!!to.getFinishTs(e);let j=V;const F=()=>j&&!V,L=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},W=()=>to.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",q=()=>to.getPlayerColor(e,t)||localStorage.getItem("player_color")||"#ffffff";let H="",Q="",Y=!1;const Z=e=>{Y=e;const[t,n]=e?[H,"grab"]:[Q,"default"];w.style.cursor=`url('${t}') ${p} ${m}, ${n}`},K=e=>{H=dn.colorizedCanvas(r,c,e).toDataURL(),Q=dn.colorizedCanvas(d,u,e).toDataURL(),Z(Y)};K(q());const J=()=>{i.setReplaySpeed&&i.setReplaySpeed(v.speeds[v.speedIdx]),i.setReplayPaused&&i.setReplayPaused(v.paused)};if("play"===o?setInterval($,1e3):"replay"===o&&J(),"play"===o)Ut.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,i]of o)switch(l){case At:{const n=Be.decodePlayer(i);n.id!==t&&(to.setPlayer(e,n.id,n),ho=!0)}break;case kt:{const t=Be.decodePiece(i);to.setPiece(e,t.idx,t),ho=!0}break;case Ct:to.setPuzzleData(e,i),ho=!0}j=!!to.getFinishTs(e)}));else if("replay"===o){const t=setInterval((()=>{const n=D();if(v.requesting)return void(v.lastRealTs=n);if(v.logPointer+1>=v.log.length)return v.lastRealTs=n,void(async e=>{const t=await b(e);v.log=v.log.slice(v.logPointer),v.logPointer=0,v.log.push(...t.log),t.log.length=v.log.length){v.final&&clearInterval(t);break}const o=v.log[n],i=v.gameStartTs+o[o.length-1];if(i>l)break;const a=o.slice();if(a[0]===rt){const t=a[1];to.addPlayer(e,t,i),ho=!0}else if(a[0]===dt){const t=to.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";to.addPlayer(e,t,i),ho=!0}else if(a[0]===ct){const t=to.getPlayerIdByIndex(e,a[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=a[2];to.handleInput(e,t,n,i),ho=!0}v.logPointer=n}v.lastRealTs=n,v.lastGameTs=l,$()}),50)}let X=null;return(e=>{const t=e.fps||60,n=e.slow||1,o=e.update,l=e.render,i=window.requestAnimationFrame,a=1/t,s=n*a;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>s;)d-=s,o(a);l(d/n),c=r,i(u)};i(u)})({update:()=>{N.createKeyEvents();for(const n of N.consumeAll())if("play"===o){const o=n[0];if(o===ut){const e=n[1],t=n[2],o=U.worldDimToViewport({w:e,h:t});ho=!0,U.move(o.w,o.h)}else if(o===ht){if(X&&!to.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-X.x),l=Math.round(t.y-X.y);ho=!0,U.move(o,l),X=t}}else if(o===wt)K(n[1]);else if(o===gt){const e={x:n[1],y:n[2]};X=U.worldToViewport(e),Z(!0)}else if(o===pt)X=null,Z(!1);else if(o===mt){const e={x:n[1],y:n[2]};ho=!0,U.zoom("in",U.worldToViewport(e))}else if(o===yt){const e={x:n[1],y:n[2]};ho=!0,U.zoom("out",U.worldToViewport(e))}else o===bt?i.togglePreview():o===xt&&i.toggleSoundsEnabled();const l=x();to.handleInput(e,t,n,l,(e=>{L()&&s.play()})).length>0&&(ho=!0),Ut.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===ut){const e=n[1],t=n[2];ho=!0,U.move(e,t)}else if(e===ht){if(X){const e={x:n[1],y:n[2]},t=U.worldToViewport(e),o=Math.round(t.x-X.x),l=Math.round(t.y-X.y);ho=!0,U.move(o,l),X=t}}else if(e===gt){const e={x:n[1],y:n[2]};X=U.worldToViewport(e)}else if(e===pt)X=null;else if(e===mt){const e={x:n[1],y:n[2]};ho=!0,U.zoom("in",U.worldToViewport(e))}else if(e===yt){const e={x:n[1],y:n[2]};ho=!0,U.zoom("out",U.worldToViewport(e))}else e===bt&&i.togglePreview()}j=!!to.getFinishTs(e),F()&&(O.update(),ho=!0)},render:async()=>{if(!ho)return;const n=x();let l,a,s;window.DEBUG&&pn(0),M.fillStyle=W(),M.fillRect(0,0,w.width,w.height),window.DEBUG&&hn("clear done"),l=U.worldToViewportRaw(T),a=U.worldDimToViewportRaw(_),M.fillStyle="rgba(255, 255, 255, .3)",M.fillRect(l.x,l.y,a.w,a.h),window.DEBUG&&hn("board done");const r=to.getPiecesSortedByZIndex(e);window.DEBUG&&hn("get tiles done"),a=U.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?go:po)&&(s=B[e.idx],l=U.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),M.drawImage(s,0,0,s.width,s.height,l.x,l.y,a.w,a.h));window.DEBUG&&hn("tiles done");const d=[];for(const i of to.getActivePlayers(e,n))c=i,("replay"===o||c.id!==t)&&(s=await f(i),l=U.worldToViewport(i),M.drawImage(s,l.x-p,l.y-m),d.push([`${i.name} (${i.points})`,l.x,l.y+h]));var c;M.fillStyle="white",M.textAlign="center";for(const[e,t,o]of d)M.fillText(e,t,o);window.DEBUG&&hn("players done"),i.setActivePlayers(to.getActivePlayers(e,n)),i.setIdlePlayers(to.getIdlePlayers(e,n)),i.setPiecesDone(to.getFinishedPiecesCount(e)),window.DEBUG&&hn("HUD done"),F()&&O.render(),ho=!1}}),{setHotkeys:e=>{N.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),N.addEvent([ft,e])},onColorChange:e=>{localStorage.setItem("player_color",e),N.addEvent([wt,e])},onNameChange:e=>{localStorage.setItem("player_name",e),N.addEvent([vt,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},replayOnSpeedUp:()=>{v.speedIdx+1{v.speedIdx>=1&&(v.speedIdx--,J())},replayOnPauseToggle:()=>{v.paused=!v.paused,J()},previewImageUrl:G,player:{background:W(),color:q(),name:to.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:L()},disconnect:Ut.disconnect,connect:C}}var yo=e({name:"game",components:{PuzzleStatus:Ye,Scores:Le,SettingsOverlay:Ke,PreviewOverlay:nt,ConnectionOverlay:Nt,HelpOverlay:Ft},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=>{},disconnect:()=>{},connect:()=>{}}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await mo(`${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.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 fo={id:"game"},wo={class:"menu"},vo={class:"tabs"},bo=s("🧩 Puzzles");yo.render=function(e,l,s,r,d,c){const u=i("settings-overlay"),p=i("preview-overlay"),h=i("help-overlay"),m=i("connection-overlay"),y=i("puzzle-status"),f=i("router-link"),w=i("scores");return a(),t("div",fo,[g(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"]),[[b,"settings"===e.overlay]]),g(n(p,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[b,"preview"===e.overlay]]),g(n(h,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[b,"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",wo,[n("div",vo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[bo])),_: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(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var xo=e({name:"replay",components:{PuzzleStatus:Ye,Scores:Le,SettingsOverlay:Ke,PreviewOverlay:nt,HelpOverlay:Ft},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:()=>{},disconnect:()=>{}},replay:{speed:1,paused:!1}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await mo(`${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.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 Co={id:"replay"},ko={class:"menu"},Ao={class:"tabs"},So=s("🧩 Puzzles");xo.render=function(e,l,s,d,c,u){const p=i("settings-overlay"),h=i("preview-overlay"),m=i("help-overlay"),y=i("puzzle-status"),f=i("router-link"),w=i("scores");return a(),t("div",Co,[g(n(p,{onBgclick:l[1]||(l[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":l[2]||(l[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[b,"settings"===e.overlay]]),g(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[b,"preview"===e.overlay]]),g(n(m,{onBgclick:l[4]||(l[4]=t=>e.toggle("help",!0))},null,512),[[b,"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",ko,[n("div",Ao,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[So])),_: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(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])},(async()=>{const e=await fetch("/api/conf"),t=await e.json();const n=x({history:C(),routes:[{name:"index",path:"/",component:V},{name:"new-game",path:"/new-game",component:Oe},{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=k(A);o.config.globalProperties.$config=t,o.config.globalProperties.$clientId=function(){let e=localStorage.getItem("ID");return e||(e=Be.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); diff --git a/build/public/index.html b/build/public/index.html index d8933e3..cfd52f7 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 116ee7d..0b37ab5 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -23,7 +23,13 @@ var ScoreMode; (function (ScoreMode) { ScoreMode[ScoreMode["FINAL"] = 0] = "FINAL"; ScoreMode[ScoreMode["ANY"] = 1] = "ANY"; -})(ScoreMode || (ScoreMode = {})); +})(ScoreMode || (ScoreMode = {})); +var ShapeMode; +(function (ShapeMode) { + ShapeMode[ShapeMode["NORMAL"] = 0] = "NORMAL"; + ShapeMode[ShapeMode["ANY"] = 1] = "ANY"; + ShapeMode[ShapeMode["FLAT"] = 2] = "FLAT"; +})(ShapeMode || (ShapeMode = {})); class Rng { constructor(seed) { @@ -1433,7 +1439,7 @@ var Images = { // cut size of each puzzle tile in the // final resized version of the puzzle image const TILE_SIZE = 64; -async function createPuzzle(rng, targetTiles, image, ts) { +async function createPuzzle(rng, targetTiles, image, ts, shapeMode) { const imagePath = image.file; const imageUrl = image.url; // determine puzzle information from the image dimensions @@ -1446,7 +1452,7 @@ async function createPuzzle(rng, targetTiles, image, ts) { for (let i = 0; i < rawPieces.length; i++) { rawPieces[i] = { idx: i }; } - const shapes = determinePuzzleTileShapes(rng, info); + const shapes = determinePuzzleTileShapes(rng, info, shapeMode); let positions = new Array(info.tiles); for (const piece of rawPieces) { const coord = Util.coordByPieceIdx(info, piece.idx); @@ -1555,8 +1561,19 @@ async function createPuzzle(rng, targetTiles, image, ts) { }, }; } -function determinePuzzleTileShapes(rng, info) { - const tabs = [-1, 1]; +function determineTabs(shapeMode) { + switch (shapeMode) { + case ShapeMode.ANY: + return [-1, 0, 1]; + case ShapeMode.FLAT: + return [0]; + case ShapeMode.NORMAL: + default: + return [-1, 1]; + } +} +function determinePuzzleTileShapes(rng, info, shapeMode) { + const tabs = determineTabs(shapeMode); const shapes = new Array(info.tiles); for (let i = 0; i < info.tiles; i++) { const coord = Util.coordByPieceIdx(info, i); @@ -1692,22 +1709,23 @@ var GameStorage = { setDirty, }; -async function createGameObject(gameId, targetTiles, image, ts, scoreMode) { +async function createGameObject(gameId, targetTiles, image, ts, scoreMode, shapeMode) { const seed = Util.hash(gameId + ' ' + ts); const rng = new Rng(seed); return { id: gameId, rng: { type: 'Rng', obj: rng }, - puzzle: await createPuzzle(rng, targetTiles, image, ts), + puzzle: await createPuzzle(rng, targetTiles, image, ts, shapeMode), players: [], evtInfos: {}, scoreMode, + shapeMode, }; } -async function createGame(gameId, targetTiles, image, ts, scoreMode) { - const gameObject = await createGameObject(gameId, targetTiles, image, ts, scoreMode); +async function createGame(gameId, targetTiles, image, ts, scoreMode, shapeMode) { + const gameObject = await createGameObject(gameId, targetTiles, image, ts, scoreMode, shapeMode); GameLog.create(gameId); - GameLog.log(gameId, Protocol.LOG_HEADER, 1, targetTiles, image, ts, scoreMode); + GameLog.log(gameId, Protocol.LOG_HEADER, 1, targetTiles, image, ts, scoreMode, shapeMode); GameCommon.setGame(gameObject.id, gameObject); GameStorage.setDirty(gameId); } @@ -1981,7 +1999,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); + game = await Game.createGameObject(gameId, log[0][2], log[0][3], log[0][4], log[0][5] || ScoreMode.FINAL, log[0][6] || ShapeMode.NORMAL); } res.send({ log, game: game ? Util.encodeGame(game) : null }); }); @@ -2069,7 +2087,7 @@ app.post('/api/newgame', express.json(), async (req, res) => { const gameId = Util.uniqId(); if (!GameCommon.exists(gameId)) { const ts = Time.timestamp(); - await Game.createGame(gameId, gameSettings.tiles, gameSettings.image, ts, gameSettings.scoreMode); + await Game.createGame(gameId, gameSettings.tiles, gameSettings.image, ts, gameSettings.scoreMode, gameSettings.shapeMode); } res.send({ id: gameId }); }); diff --git a/src/common/Types.ts b/src/common/Types.ts index edb4a1e..6f16971 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -73,6 +73,7 @@ export interface Game { puzzle: Puzzle evtInfos: Record scoreMode?: ScoreMode + shapeMode?: ShapeMode rng: GameRng } @@ -90,6 +91,7 @@ export interface GameSettings { tiles: number image: Image scoreMode: ScoreMode + shapeMode: ShapeMode } export interface Puzzle { @@ -198,3 +200,9 @@ export enum ScoreMode { FINAL = 0, ANY = 1, } + +export enum ShapeMode { + NORMAL = 0, + ANY = 1, + FLAT = 2, +} diff --git a/src/frontend/components/NewGameDialog.vue b/src/frontend/components/NewGameDialog.vue index 04a7eff..6bbd2b5 100644 --- a/src/frontend/components/NewGameDialog.vue +++ b/src/frontend/components/NewGameDialog.vue @@ -22,6 +22,16 @@ + + + + +
+ +
+ + + @@ -36,7 +46,7 @@ + diff --git a/src/frontend/components/NewGameDialog.vue b/src/frontend/components/NewGameDialog.vue index 6bbd2b5..052be5f 100644 --- a/src/frontend/components/NewGameDialog.vue +++ b/src/frontend/components/NewGameDialog.vue @@ -23,7 +23,7 @@ - +
From df7584f19d97bccc333440e337a7dc138ed2c215 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Thu, 3 Jun 2021 23:30:08 +0200 Subject: [PATCH 21/78] add cheap autocomplete for tags --- src/frontend/components/EditImageDialog.vue | 5 +- src/frontend/components/NewImageDialog.vue | 7 +- src/frontend/components/TagsInput.vue | 98 +++++++++++++++++++-- src/frontend/views/NewGame.vue | 38 ++++++-- 4 files changed, 135 insertions(+), 13 deletions(-) diff --git a/src/frontend/components/EditImageDialog.vue b/src/frontend/components/EditImageDialog.vue index 10743ef..ea8e34f 100644 --- a/src/frontend/components/EditImageDialog.vue +++ b/src/frontend/components/EditImageDialog.vue @@ -23,7 +23,7 @@ - + @@ -54,6 +54,9 @@ export default defineComponent({ type: Object as PropType, required: true, }, + autocompleteTags: { + type: Function, + }, }, emits: { bgclick: null, diff --git a/src/frontend/components/NewImageDialog.vue b/src/frontend/components/NewImageDialog.vue index 372a395..c9db112 100644 --- a/src/frontend/components/NewImageDialog.vue +++ b/src/frontend/components/NewImageDialog.vue @@ -36,7 +36,7 @@ gallery", if possible! - + @@ -62,6 +62,11 @@ export default defineComponent({ ResponsiveImage, TagsInput, }, + props: { + autocompleteTags: { + type: Function, + }, + }, emits: { bgclick: null, setupGameClick: null, diff --git a/src/frontend/components/TagsInput.vue b/src/frontend/components/TagsInput.vue index c6fe16a..e0d4f1f 100644 --- a/src/frontend/components/TagsInput.vue +++ b/src/frontend/components/TagsInput.vue @@ -1,19 +1,42 @@ - - + + +
From 22585b92fe61b299b5d935c3c2a0db66480068e3 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Thu, 3 Jun 2021 23:46:09 +0200 Subject: [PATCH 23/78] show number of tag usage --- build/public/assets/index.a4bac37f.js | 1 - build/public/assets/index.e2df6757.js | 1 + build/public/index.html | 2 +- build/server/main.js | 18 ++++++++++++++++-- src/common/Types.ts | 1 + src/frontend/views/NewGame.vue | 12 +++++++++++- src/server/Images.ts | 17 ++++++++++++++++- src/server/main.ts | 2 +- 8 files changed, 47 insertions(+), 7 deletions(-) delete mode 100644 build/public/assets/index.a4bac37f.js create mode 100644 build/public/assets/index.e2df6757.js diff --git a/build/public/assets/index.a4bac37f.js b/build/public/assets/index.a4bac37f.js deleted file mode 100644 index ba8b505..0000000 --- a/build/public/assets/index.a4bac37f.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 g,v as p,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 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"),_=s("New game");z.render=function(e,s,r,d,c,u){const g=a("router-link"),p=a("router-view");return i(),t("div",T,[e.showNav?(i(),t("ul",P,[n("li",null,[n(g,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:o((()=>[_])),_:1})])])):l("",!0),n(p)])};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 M=1e3,B=()=>{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),N=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||B();return`${n} ${O(o,l)}`}}});const G={class:"game-info-text"},V=n("br",null,null,-1),$=n("br",null,null,-1),R=n("br",null,null,-1),F=s(" ↪️ Watch replay ");U.render=function(e,d,c,u,g,p){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",G,[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),R])])),_: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 g=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(g,{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(g,{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=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)}}});H.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))])};const Y={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}}}};Y.render=function(e,n,o,l,a,s){return i(),t("div",{style:s.style,title:o.title},null,12,["title"])};var Q=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 Z=m();y("data-v-39ed99c7");const K={key:0,class:"autocomplete"};f();const J=Z(((e,o,a,s,u,m)=>(i(),t("div",null,[g(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),[[p,e.input]]),e.autocomplete.values?(i(),t("div",K,[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))]))));Q.render=J,Q.__scopeId="data-v-39ed99c7";var X=e({name:"new-image-dialog",components:{ResponsiveImage:Y,TagsInput:Q},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const o=new FileReader;o.readAsDataURL(n),o.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const ee={key:0,class:"has-image"},te={key:1},ne={class:"upload"},oe=n("span",{class:"btn"},"Upload File",-1),le={class:"area-settings"},ae=n("td",null,[n("label",null,"Title")],-1),ie=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),re={class:"area-buttons"},de=s("🧩 Post to gallery "),ce=n("br",null,null,-1),ue=s(" + set up game");X.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[8]||(o[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[7]||(o[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(i(),t("div",ee,[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",te,[n("label",ne,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),oe])]))],2),n("div",le,[n("table",null,[n("tr",null,[ae,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[3]||(o[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ie,n("tr",null,[se,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[4]||(o[4]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",re,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[5]||(o[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[6]||(o[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[de,ce,ue],8,["disabled"])])])])};var ge=e({name:"edit-image-dialog",components:{ResponsiveImage:Y,TagsInput:Q},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"},he={class:"has-image"},me={class:"area-settings"},ye=n("td",null,[n("label",null,"Title")],-1),fe=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),we=n("td",null,[n("label",null,"Tags")],-1),ve={class:"area-buttons"};var be,xe,Ce,ke,Ae,Se;ge.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",he,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",me,[n("table",null,[n("tr",null,[ye,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),fe,n("tr",null,[we,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")])])])},(xe=be||(be={}))[xe.Flat=0]="Flat",xe[xe.Out=1]="Out",xe[xe.In=-1]="In",(ke=Ce||(Ce={}))[ke.FINAL=0]="FINAL",ke[ke.ANY=1]="ANY",(Se=Ae||(Ae={}))[Se.NORMAL=0]="NORMAL",Se[Se.ANY=1]="ANY",Se[Se.FLAT=2]="FLAT";var ze=e({name:"new-game-dialog",components:{ResponsiveImage:Y},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Ce.ANY,shapeMode:Ae.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt})}},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)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const Te={class:"area-image"},Pe={class:"has-image"},Ie={class:"area-settings"},_e=n("td",null,[n("label",null,"Pieces")],-1),Ee=n("td",null,[n("label",null,"Scoring: ")],-1),De=s(" Any (Score when pieces are connected to each other or on final location)"),Me=n("br",null,null,-1),Be=s(" Final (Score when pieces are put to their final location)"),Oe=n("td",null,[n("label",null,"Shapes: ")],-1),Ne=s(" Normal"),Ue=n("br",null,null,-1),Ge=s(" Any (flat pieces can occur anywhere)"),Ve=n("br",null,null,-1),$e=s(" Flat (all pieces flat on all sides)"),Re={class:"area-buttons"};ze.render=function(e,o,l,s,r,d){const c=a("responsive-image");return i(),t("div",{class:"overlay new-game-dialog",onClick:o[9]||(o[9]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[8]||(o[8]=u((()=>{}),["stop"]))},[n("div",Te,[n("div",Pe,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ie,[n("table",null,[n("tr",null,[_e,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Ee,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[w,e.scoreMode]]),De]),Me,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[w,e.scoreMode]]),Be])])]),n("tr",null,[Oe,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[w,e.shapeMode]]),Ne]),Ue,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[w,e.shapeMode]]),Ge]),Ve,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[w,e.shapeMode]]),$e])])])])]),n("div",Re,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[7]||(o[7]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};class Fe{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 Fe(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},je=(...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 We={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",Fe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Ce.FINAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:Fe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}},qe=e({components:{ImageLibrary:H,NewImageDialog:X,EditImageDialog:ge,NewGameDialog:ze},data:()=>({filters:{sort:"date_desc",tags:[]},images:[],tags:[],image:{id:0,filename:"",file:"",url:"",title:"",tags:[],created:0},dialog:""}),async created(){await this.loadImages()},methods:{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${We.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/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 He={class:"upload-image-teaser"},Ye=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),Qe={key:0},Ze=s(" Tags: "),Ke=s(" Sort by: "),Je=n("option",{value:"date_desc"},"Newest first",-1),Xe=n("option",{value:"date_asc"},"Oldest first",-1),et=n("option",{value:"alpha_asc"},"A-Z",-1),tt=n("option",{value:"alpha_desc"},"Z-A",-1);qe.render=function(e,o,s,u,p,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),w=a("new-game-dialog");return i(),t("div",null,[n("div",He,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),Ye]),n("div",null,[e.tags.length>0?(i(),t("label",Qe,[Ze,(i(!0),t(d,null,c(e.tags,((n,o)=>(i(),t("span",{class:["bit",{on:e.filters.tags.includes(n.slug)}],key:o,onClick:t=>e.toggleTag(n)},r(n.title),11,["onClick"])))),128))])):l("",!0),n("label",null,[Ke,g(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[Je,Xe,et,tt],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?(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(w,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var nt=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 ot={class:"scores"},lt=n("div",null,"Scores",-1),at=n("td",null,"⚡",-1),it=n("td",null,"💤",-1);nt.render=function(e,o,l,a,s,u){return i(),t("div",ot,[lt,n("table",null,[(i(!0),t(d,null,c(e.actives,((e,o)=>(i(),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)),(i(!0),t(d,null,c(e.idles,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[it,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var st=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 N(this.duration)}}});const rt={class:"timer"};st.render=function(e,o,l,a,s,d){return i(),t("div",rt,[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 dt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:Object},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const ct=n("td",null,[n("label",null,"Background: ")],-1),ut=n("td",null,[n("label",null,"Color: ")],-1),gt=n("td",null,[n("label",null,"Name: ")],-1),pt=n("td",null,[n("label",null,"Sounds: ")],-1);dt.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,[ct,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[ut,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[gt,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])]),n("tr",null,[pt,n("td",null,[g(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[x,e.modelValue.soundsEnabled]])])])])])};var ht=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const mt={class:"preview"};ht.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",mt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var yt=1,ft=4,wt=2,vt=3,bt=2,xt=4,Ct=3,kt=9,At=1,St=2,zt=3,Tt=4,Pt=5,It=6,_t=7,Et=8,Dt=10,Mt=11,Bt=1,Ot=2,Nt=3;const Ut=je("Communication.js");let Gt,Vt=[],$t=e=>{Vt.push(e)},Rt=[],Ft=e=>{Rt.push(e)};let Lt=0;const jt=e=>{Lt!==e&&(Lt=e,Ft(e))};function Wt(e){if(2===Lt)try{Gt.send(JSON.stringify(e))}catch(t){Ut.info("unable to send message.. maybe because ws is invalid?")}}let qt,Ht;var Yt={connect:function(e,t,n){return qt=0,Ht={},jt(3),new Promise((o=>{Gt=new WebSocket(e,n+"|"+t),Gt.onopen=()=>{jt(2),Wt([vt])},Gt.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===ft){const e=t[1];o(e)}else{if(l!==yt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&Ht[o])return void delete Ht[o];$t(t)}}},Gt.onerror=()=>{throw jt(1),"[ 2021-05-15 onerror ]"},Gt.onclose=e=>{4e3===e.code||1001===e.code?jt(4):jt(1)}}))},requestReplayData:async function(e,t,n){const o={gameId:e,offset:t,size:n},l=await fetch(`/api/replay-data${We.asQueryArgs(o)}`);return await l.json()},disconnect:function(){Gt&&Gt.close(4e3),qt=0,Ht={}},sendClientEvent:function(e){qt++,Ht[qt]=e,Wt([wt,qt,Ht[qt]])},onServerChange:function(e){$t=e;for(const t of Vt)$t(t);Vt=[]},onConnectionStateChange:function(e){Ft=e;for(const t of Rt)Ft(t);Rt=[]},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},Qt=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Yt.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Yt.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const Zt={key:0,class:"overlay connection-lost"},Kt={key:0,class:"overlay-content"},Jt=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Xt={key:1,class:"overlay-content"},en=n("div",null,"Connecting...",-1);Qt.render=function(e,o,a,s,r,d){return e.show?(i(),t("div",Zt,[e.lostConnection?(i(),t("div",Kt,[Jt,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(i(),t("div",Xt,[en])):l("",!0)])):l("",!0)};var tn=e({name:"help-overlay",emits:{bgclick:null}});const nn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),on=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),ln=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),an=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),sn=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("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"),s("/🖱️-Wheel")])])],-1),dn=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),un=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),gn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),pn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1);tn.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"]))},[nn,on,ln,an,sn,rn,dn,cn,un,gn,pn])])};var hn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.550555f3.mp3"}),mn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),yn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),fn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),wn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function vn(){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 bn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var xn={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 o=bn(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=bn(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 Cn=je("Debug.js");let kn=0,An=0;var Sn=e=>{kn=performance.now(),An=e},zn=e=>{const t=performance.now(),n=t-kn;n>An&&Cn.log(e+": "+n),kn=t};function Tn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function Pn(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var In={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:Tn,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:Pn,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 Tn(Pn(e),Pn(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 _n=je("PuzzleGraphics.js");function En(e,t){const n=We.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Dn={loadPuzzleBitmaps:async function(e){const t=await xn.loadImageToBitmap(e.info.imageUrl),n=await xn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){_n.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=In.pointAdd(a,{x:o,y:0}),c=In.pointAdd(r,{x:0,y:o}),u=In.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;oWe.decodePiece(Mn[e].puzzle.tiles[t]),Hn=(e,t)=>qn(e,t).group,Yn=(e,t)=>{const n=Mn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Mn[e].puzzle.info,o=We.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return In.pointAdd(o,l)},Qn=(e,t)=>qn(e,t).pos,Zn=e=>{const t=go(e),n=po(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}},Kn=(e,t)=>{const n=to(e),o=qn(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Jn=(e,t)=>qn(e,t).z,Xn=(e,t)=>{for(const n of Mn[e].puzzle.tiles){const e=We.decodePiece(n);if(e.owner===t)return e.idx}return-1},eo=e=>Mn[e].puzzle.info.tileDrawSize,to=e=>Mn[e].puzzle.info.tileSize,no=e=>Mn[e].puzzle.data.maxGroup,oo=e=>Mn[e].puzzle.data.maxZ;function lo(e,t){const n=Mn[e].puzzle.info,o=We.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const ao=(e,t,n)=>{for(const o of t)Wn(e,o,{z:n})},io=(e,t,n)=>{const o=Qn(e,t);Wn(e,t,{pos:In.pointAdd(o,n)})},so=(e,t,n)=>{const o=eo(e),l=Zn(e),a=n;for(const i of t){const t=qn(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)io(e,i,a)},ro=(e,t,n)=>{for(const o of t)Wn(e,o,{owner:n})};function co(e,t){const n=Mn[e].puzzle.tiles,o=We.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=We.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const uo=(e,t)=>{const n=On(e,t);return n?n.points:0},go=e=>Mn[e].puzzle.info.table.width,po=e=>Mn[e].puzzle.info.table.height;var ho={setGame:function(e,t){Mn[e]=t},exists:function(e){return!!Mn[e]||!1},playerExists:Un,getActivePlayers:function(e,t){const n=t-30*M;return Gn(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*M;return Gn(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Un(e,t)?Ln(e,t,{ts:n}):Nn(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:Fn,getPieceCount:Vn,getImageUrl:function(e){return Mn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Mn[e].puzzle.info.imageUrl=t},get:function(e){return Mn[e]||null},getAllGames:function(){return Object.values(Mn).sort(((e,t)=>Rn(e.id)===Rn(t.id)?t.puzzle.data.started-e.puzzle.data.started:Rn(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=On(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=On(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=On(e,t);return n?n.name:null},getPlayerIndexById:Bn,getPlayerIdByIndex:function(e,t){return Mn[e].players.length>t?We.decodePlayer(Mn[e].players[t]).id:null},changePlayer:Ln,setPlayer:Nn,setPiece:function(e,t,n){Mn[e].puzzle.tiles[t]=We.encodePiece(n)},setPuzzleData:function(e,t){Mn[e].puzzle.data=t},getTableWidth:go,getTableHeight:po,getPuzzle:e=>Mn[e].puzzle,getRng:e=>Mn[e].rng.obj,getPuzzleWidth:e=>Mn[e].puzzle.info.width,getPuzzleHeight:e=>Mn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Mn[e].puzzle.tiles.map(We.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Xn(e,t);return n<0?null:Mn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Mn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:eo,getFinalPiecePos:Yn,getStartTs:e=>Mn[e].puzzle.data.started,getFinishTs:e=>Mn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Mn[e].puzzle,i=function(e,t){return t in Mn[e].evtInfos?Mn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],r=()=>{s.push([Bt,a.data])},d=t=>{s.push([Ot,We.encodePiece(qn(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=On(e,t);n&&s.push([Nt,We.encodePlayer(n)])},g=n[0];if(g===It){const l=n[1];Ln(e,t,{bgcolor:l,ts:o}),u()}else if(g===_t){const l=n[1];Ln(e,t,{color:l,ts:o}),u()}else if(g===Et){const l=`${n[1]}`.substr(0,16);Ln(e,t,{name:l,ts:o}),u()}else if(g===kt){const l=n[1],a=n[2],i=On(e,t);if(i){const n=i.x-l,s=i.y-a;Ln(e,t,{ts:o,x:n,y:s}),u()}}else if(g===At){const l={x:n[1],y:n[2]};Ln(e,t,{d:1,ts:o}),u(),i._last_mouse_down=l;const a=((e,t)=>{const n=Mn[e].puzzle.info,o=Mn[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=oo(e)+1;jn(e,{maxZ:n}),r();const o=co(e,a);ao(e,o,oo(e)),ro(e,o,t),c(o)}i._last_mouse=l}else if(g===zt){const l=n[1],a=n[2],s={x:l,y:a};if(null===i._last_mouse_down)Ln(e,t,{x:l,y:a,ts:o}),u();else{const n=Xn(e,t);if(n>=0){Ln(e,t,{x:l,y:a,ts:o}),u();const r=co(e,n);let d=In.pointInBounds(s,Zn(e))&&In.pointInBounds(i._last_mouse_down,Zn(e));for(const t of r){const n=Kn(e,t);if(In.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 Ln(e,t,{ts:o}),u();i._last_mouse_down=s}i._last_mouse=s}else if(g===St){const s={x:n[1],y:n[2]},g=0;i._last_mouse_down=null;const p=Xn(e,t);if(p>=0){const n=co(e,p);ro(e,n,0),c(n);const i=Qn(e,p),s=Yn(e,p);if(In.pointDistance(s,i){for(const n of t)Wn(e,n,{owner:-1,z:1})})(e,n),c(n);let d=uo(e,t);$n(e)===Ce.FINAL?d+=n.length:$n(e)===Ce.ANY&&(d+=1),Ln(e,t,{d:g,ts:o,points:d}),u(),Fn(e)===Vn(e)&&(jn(e,{finished:o}),r()),l&&l(t)}else{const n=(e,t,n,o)=>{const l=Mn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=Hn(e,t),l=Hn(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=Qn(e,t),i=In.pointAdd(Qn(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(In.pointDistance(a,i){const o=Mn[e].puzzle.tiles,l=Hn(e,t),a=Hn(e,n);let i;const s=[];l&&s.push(l),a&&s.push(a),l?i=l:a?i=a:(jn(e,{maxGroup:no(e)+1}),r(),i=no(e));if(Wn(e,t,{group:i}),d(t),Wn(e,n,{group:i}),d(n),s.length>0)for(const r of o){const t=We.decodePiece(r);s.includes(t.group)&&(Wn(e,t.idx,{group:i}),d(t.idx))}})(e,t,n),l=co(e,t);const s=((e,t)=>{let n=0;for(const o of t){const t=Jn(e,o);t>n&&(n=t)}return n})(e,l);return ao(e,l,s),c(l),!0}return!1};let a=!1;for(const t of co(e,p)){const o=lo(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&&$n(e)===Ce.ANY){const n=uo(e,t)+1;Ln(e,t,{d:g,ts:o,points:n}),u()}else Ln(e,t,{d:g,ts:o}),u();a&&l&&l(t)}}else Ln(e,t,{d:g,ts:o}),u();i._last_mouse=s}else if(g===Tt){const l=n[1],a=n[2];Ln(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else if(g===Pt){const l=n[1],a=n[2];Ln(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else Ln(e,t,{ts:o}),u();return function(e,t,n){Mn[e].evtInfos[t]=n}(e,t,i),s}};let mo=-10,yo=20,fo=2,wo=15;class vo{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()*yo,this.vy=-1*(fo+Math.random()*wo),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;fo=t/2,wo=t-fo;const n=1/4*this.canvas.width/(t/2);mo=-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 vo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new vo(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(xn.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,zo=!0})),t}(l,xn.createCanvas()),v={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};Yt.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{v.requesting=!0;const t=await Yt.requestReplayData(e,v.dataOffset,v.dataSize);return v.dataOffset+=v.dataSize,v.requesting=!1,t};let x=()=>0;const C=async()=>{if("play"===o){const o=await Yt.connect(n,e,t),l=We.decodeGame(o);ho.setGame(l.id,l),x=()=>B()}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=We.decodeGame(t.game);ho.setGame(n.id,n),v.requesting=!1,v.log=t.log,v.lastRealTs=B(),v.gameStartTs=parseInt(v.log[0][4],10),v.lastGameTs=v.gameStartTs,x=()=>v.lastGameTs}}zo=!0};await C();const k=ho.getPieceDrawOffset(e),A=ho.getPieceDrawSize(e),S=ho.getPuzzleWidth(e),z=ho.getPuzzleHeight(e),T=ho.getTableWidth(e),P=ho.getTableHeight(e),I={x:(T-S)/2,y:(P-z)/2},_={w:S,h:z},E={w:A,h:A},D=await Dn.loadPuzzleBitmaps(ho.getPuzzle(e)),M=new xo(w,ho.getRng(e));M.init();const O=w.getContext("2d");w.classList.add("loaded");const N=vn();N.move(-(T-w.width)/2,-(P-w.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 g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{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=p(e),0===e.button&&f([At,...y])})),e.addEventListener("mouseup",(e=>{y=p(e),0===e.button&&f([St,...y])})),e.addEventListener("mousemove",(e=>{y=p(e),f([zt,...y])})),e.addEventListener("wheel",(e=>{if(y=p(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?Tt:Pt;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([Dt]),"F"!==e.key&&"f"!==e.key||(Ao=!Ao,zo=!0),"G"!==e.key&&"g"!==e.key||(So=!So,zo=!0),"M"!==e.key&&"m"!==e.key||f([Mt]))}));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([kt,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([Tt,...e])}}else if(c&&n.canZoom("out")){const e=y||h();f([Pt,...e])}},setHotkeys:e=>{l=e}}}(w,window,N),G=ho.getImageUrl(e),V=()=>{const t=ho.getStartTs(e),n=ho.getFinishTs(e),o=x();a.setFinished(!!n),a.setDuration((n||o)-t)};V(),a.setPiecesDone(ho.getFinishedPiecesCount(e)),a.setPiecesTotal(ho.getPieceCount(e));const $=x();a.setActivePlayers(ho.getActivePlayers(e,$)),a.setIdlePlayers(ho.getIdlePlayers(e,$));const R=!!ho.getFinishTs(e);let F=R;const L=()=>F&&!R,j=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},W=()=>ho.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",q=()=>ho.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"];w.style.cursor=`url('${t}') ${p} ${m}, ${n}`},K=e=>{H=xn.colorizedCanvas(r,c,e).toDataURL(),Y=xn.colorizedCanvas(d,u,e).toDataURL(),Z(Q)};K(q());const J=()=>{a.setReplaySpeed&&a.setReplaySpeed(v.speeds[v.speedIdx]),a.setReplayPaused&&a.setReplayPaused(v.paused)};if("play"===o?setInterval(V,1e3):"replay"===o&&J(),"play"===o)Yt.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Nt:{const n=We.decodePlayer(a);n.id!==t&&(ho.setPlayer(e,n.id,n),zo=!0)}break;case Ot:{const t=We.decodePiece(a);ho.setPiece(e,t.idx,t),zo=!0}break;case Bt:ho.setPuzzleData(e,a),zo=!0}F=!!ho.getFinishTs(e)}));else if("replay"===o){const t=setInterval((()=>{const n=B();if(v.requesting)return void(v.lastRealTs=n);if(v.logPointer+1>=v.log.length)return v.lastRealTs=n,void(async e=>{const t=await b(e);v.log=v.log.slice(v.logPointer),v.logPointer=0,v.log.push(...t.log),t.log.length=v.log.length){v.final&&clearInterval(t);break}const o=v.log[n],a=v.gameStartTs+o[o.length-1];if(a>l)break;const i=o.slice();if(i[0]===bt){const t=i[1];ho.addPlayer(e,t,a),zo=!0}else if(i[0]===xt){const t=ho.getPlayerIdByIndex(e,i[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";ho.addPlayer(e,t,a),zo=!0}else if(i[0]===Ct){const t=ho.getPlayerIdByIndex(e,i[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=i[2];ho.handleInput(e,t,n,a),zo=!0}v.logPointer=n}v.lastRealTs=n,v.lastGameTs=l,V()}),50)}let X=null;return(e=>{const t=e.fps||60,n=e.slow||1,o=e.update,l=e.render,a=window.requestAnimationFrame,i=1/t,s=n*i;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>s;)d-=s,o(i);l(d/n),c=r,a(u)};a(u)})({update:()=>{U.createKeyEvents();for(const n of U.consumeAll())if("play"===o){const o=n[0];if(o===kt){const e=n[1],t=n[2],o=N.worldDimToViewport({w:e,h:t});zo=!0,N.move(o.w,o.h)}else if(o===zt){if(X&&!ho.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=N.worldToViewport(e),o=Math.round(t.x-X.x),l=Math.round(t.y-X.y);zo=!0,N.move(o,l),X=t}}else if(o===_t)K(n[1]);else if(o===At){const e={x:n[1],y:n[2]};X=N.worldToViewport(e),Z(!0)}else if(o===St)X=null,Z(!1);else if(o===Tt){const e={x:n[1],y:n[2]};zo=!0,N.zoom("in",N.worldToViewport(e))}else if(o===Pt){const e={x:n[1],y:n[2]};zo=!0,N.zoom("out",N.worldToViewport(e))}else o===Dt?a.togglePreview():o===Mt&&a.toggleSoundsEnabled();const l=x();ho.handleInput(e,t,n,l,(e=>{j()&&s.play()})).length>0&&(zo=!0),Yt.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===kt){const e=n[1],t=n[2];zo=!0,N.move(e,t)}else if(e===zt){if(X){const e={x:n[1],y:n[2]},t=N.worldToViewport(e),o=Math.round(t.x-X.x),l=Math.round(t.y-X.y);zo=!0,N.move(o,l),X=t}}else if(e===At){const e={x:n[1],y:n[2]};X=N.worldToViewport(e)}else if(e===St)X=null;else if(e===Tt){const e={x:n[1],y:n[2]};zo=!0,N.zoom("in",N.worldToViewport(e))}else if(e===Pt){const e={x:n[1],y:n[2]};zo=!0,N.zoom("out",N.worldToViewport(e))}else e===Dt&&a.togglePreview()}F=!!ho.getFinishTs(e),L()&&(M.update(),zo=!0)},render:async()=>{if(!zo)return;const n=x();let l,i,s;window.DEBUG&&Sn(0),O.fillStyle=W(),O.fillRect(0,0,w.width,w.height),window.DEBUG&&zn("clear done"),l=N.worldToViewportRaw(I),i=N.worldDimToViewportRaw(_),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,i.w,i.h),window.DEBUG&&zn("board done");const r=ho.getPiecesSortedByZIndex(e);window.DEBUG&&zn("get tiles done"),i=N.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?Ao:So)&&(s=D[e.idx],l=N.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(s,0,0,s.width,s.height,l.x,l.y,i.w,i.h));window.DEBUG&&zn("tiles done");const d=[];for(const a of ho.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(s=await f(a),l=N.worldToViewport(a),O.drawImage(s,l.x-p,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&&zn("players done"),a.setActivePlayers(ho.getActivePlayers(e,n)),a.setIdlePlayers(ho.getIdlePlayers(e,n)),a.setPiecesDone(ho.getFinishedPiecesCount(e)),window.DEBUG&&zn("HUD done"),L()&&M.render(),zo=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([It,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([_t,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Et,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},replayOnSpeedUp:()=>{v.speedIdx+1{v.speedIdx>=1&&(v.speedIdx--,J())},replayOnPauseToggle:()=>{v.paused=!v.paused,J()},previewImageUrl:G,player:{background:W(),color:q(),name:ho.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:j()},disconnect:Yt.disconnect,connect:C}}var Po=e({name:"game",components:{PuzzleStatus:st,Scores:nt,SettingsOverlay:dt,PreviewOverlay:ht,ConnectionOverlay:Qt,HelpOverlay:tn},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=>{},disconnect:()=>{},connect:()=>{}}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await To(`${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.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 Io={id:"game"},_o={class:"menu"},Eo={class:"tabs"},Do=s("🧩 Puzzles");Po.render=function(e,l,s,r,d,c){const u=a("settings-overlay"),p=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),w=a("scores");return i(),t("div",Io,[g(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]]),g(n(p,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),g(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",_o,[n("div",Eo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Do])),_: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(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var Mo=e({name:"replay",components:{PuzzleStatus:st,Scores:nt,SettingsOverlay:dt,PreviewOverlay:ht,HelpOverlay:tn},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:()=>{},disconnect:()=>{}},replay:{speed:1,paused:!1}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await To(`${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.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 Bo={id:"replay"},Oo={class:"menu"},No={class:"tabs"},Uo=s("🧩 Puzzles");Mo.render=function(e,l,s,d,c,u){const p=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),w=a("scores");return i(),t("div",Bo,[g(n(p,{onBgclick:l[1]||(l[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":l[2]||(l[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[C,"settings"===e.overlay]]),g(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),g(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",Oo,[n("div",No,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Uo])),_: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(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:A(),routes:[{name:"index",path:"/",component:L},{name:"new-game",path:"/new-game",component:qe},{name:"game",path:"/g/:id",component:Po},{name:"replay",path:"/replay/:id",component:Mo}]});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=We.uniqId(),localStorage.setItem("ID",e)),e}(),o.use(n),o.mount("#app")})(); diff --git a/build/public/assets/index.e2df6757.js b/build/public/assets/index.e2df6757.js new file mode 100644 index 0000000..5ed9514 --- /dev/null +++ b/build/public/assets/index.e2df6757.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 g,v as p,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 A,y as S}from"./vendor.684f7bc8.js";var T=e({name:"app",computed:{showNav(){return!["game","replay"].includes(String(this.$route.name))}}});const z={id:"app"},P={key:0,class:"nav"},I=s("Index"),_=s("New game");T.render=function(e,s,r,d,c,u){const g=a("router-link"),p=a("router-view");return i(),t("div",z,[e.showNav?(i(),t("ul",P,[n("li",null,[n(g,{class:"btn",to:{name:"index"}},{default:o((()=>[I])),_:1})]),n("li",null,[n(g,{class:"btn",to:{name:"new-game"}},{default:o((()=>[_])),_:1})])])):l("",!0),n(p)])};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 M=1e3,B=()=>{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),N=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||B();return`${n} ${O(o,l)}`}}});const G={class:"game-info-text"},V=n("br",null,null,-1),$=n("br",null,null,-1),R=n("br",null,null,-1),F=s(" ↪️ Watch replay ");U.render=function(e,d,c,u,g,p){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",G,[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),R])])),_: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 g=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(g,{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(g,{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=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)}}});H.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))])};const Y={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}}}};Y.render=function(e,n,o,l,a,s){return i(),t("div",{style:s.style,title:o.title},null,12,["title"])};var Q=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 Z=m();y("data-v-39ed99c7");const K={key:0,class:"autocomplete"};f();const J=Z(((e,o,a,s,u,m)=>(i(),t("div",null,[g(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),[[p,e.input]]),e.autocomplete.values?(i(),t("div",K,[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))]))));Q.render=J,Q.__scopeId="data-v-39ed99c7";var X=e({name:"new-image-dialog",components:{ResponsiveImage:Y,TagsInput:Q},props:{autocompleteTags:{type:Function}},emits:{bgclick:null,setupGameClick:null,postToGalleryClick:null},data:()=>({previewUrl:"",file:null,title:"",tags:[]}),computed:{canPostToGallery(){return!(!this.previewUrl||!this.file)},canSetupGameClick(){return!(!this.previewUrl||!this.file)}},methods:{preview(e){const t=e.target;if(!t.files)return;const n=t.files[0];if(!n)return;const o=new FileReader;o.readAsDataURL(n),o.onload=e=>{this.previewUrl=e.target.result,this.file=n}},postToGallery(){this.$emit("postToGalleryClick",{file:this.file,title:this.title,tags:this.tags})},setupGameClick(){this.$emit("setupGameClick",{file:this.file,title:this.title,tags:this.tags})}}});const ee={key:0,class:"has-image"},te={key:1},ne={class:"upload"},oe=n("span",{class:"btn"},"Upload File",-1),le={class:"area-settings"},ae=n("td",null,[n("label",null,"Title")],-1),ie=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),re={class:"area-buttons"},de=s("🧩 Post to gallery "),ce=n("br",null,null,-1),ue=s(" + set up game");X.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[8]||(o[8]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[7]||(o[7]=u((()=>{}),["stop"]))},[n("div",{class:["area-image",{"has-image":!!e.previewUrl,"no-image":!e.previewUrl}]},[e.previewUrl?(i(),t("div",ee,[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",te,[n("label",ne,[n("input",{type:"file",style:{display:"none"},onChange:o[2]||(o[2]=(...t)=>e.preview&&e.preview(...t)),accept:"image/*"},null,32),oe])]))],2),n("div",le,[n("table",null,[n("tr",null,[ae,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[3]||(o[3]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),ie,n("tr",null,[se,n("td",null,[n(h,{modelValue:e.tags,"onUpdate:modelValue":o[4]||(o[4]=t=>e.tags=t),autocompleteTags:e.autocompleteTags},null,8,["modelValue","autocompleteTags"])])])])]),n("div",re,[n("button",{class:"btn",disabled:!e.canPostToGallery,onClick:o[5]||(o[5]=(...t)=>e.postToGallery&&e.postToGallery(...t))},"🖼️ Post to gallery",8,["disabled"]),n("button",{class:"btn",disabled:!e.canSetupGameClick,onClick:o[6]||(o[6]=(...t)=>e.setupGameClick&&e.setupGameClick(...t))},[de,ce,ue],8,["disabled"])])])])};var ge=e({name:"edit-image-dialog",components:{ResponsiveImage:Y,TagsInput:Q},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"},he={class:"has-image"},me={class:"area-settings"},ye=n("td",null,[n("label",null,"Title")],-1),fe=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),we=n("td",null,[n("label",null,"Tags")],-1),ve={class:"area-buttons"};var be,xe,Ce,ke,Ae,Se;ge.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",he,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",me,[n("table",null,[n("tr",null,[ye,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.title=t),placeholder:"Flower by @artist"},null,512),[[p,e.title]])])]),fe,n("tr",null,[we,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")])])])},(xe=be||(be={}))[xe.Flat=0]="Flat",xe[xe.Out=1]="Out",xe[xe.In=-1]="In",(ke=Ce||(Ce={}))[ke.FINAL=0]="FINAL",ke[ke.ANY=1]="ANY",(Se=Ae||(Ae={}))[Se.NORMAL=0]="NORMAL",Se[Se.ANY=1]="ANY",Se[Se.FLAT=2]="FLAT";var Te=e({name:"new-game-dialog",components:{ResponsiveImage:Y},props:{image:{type:Object,required:!0}},emits:{newGame:null,bgclick:null},data:()=>({tiles:1e3,scoreMode:Ce.ANY,shapeMode:Ae.NORMAL}),methods:{onNewGameClick(){this.$emit("newGame",{tiles:this.tilesInt,image:this.image,scoreMode:this.scoreModeInt,shapeMode:this.shapeModeInt})}},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)},tilesInt(){return parseInt(`${this.tiles}`,10)}}});const ze={class:"area-image"},Pe={class:"has-image"},Ie={class:"area-settings"},_e=n("td",null,[n("label",null,"Pieces")],-1),Ee=n("td",null,[n("label",null,"Scoring: ")],-1),De=s(" Any (Score when pieces are connected to each other or on final location)"),Me=n("br",null,null,-1),Be=s(" Final (Score when pieces are put to their final location)"),Oe=n("td",null,[n("label",null,"Shapes: ")],-1),Ne=s(" Normal"),Ue=n("br",null,null,-1),Ge=s(" Any (flat pieces can occur anywhere)"),Ve=n("br",null,null,-1),$e=s(" Flat (all pieces flat on all sides)"),Re={class:"area-buttons"};Te.render=function(e,o,l,s,r,d){const c=a("responsive-image");return i(),t("div",{class:"overlay new-game-dialog",onClick:o[9]||(o[9]=t=>e.$emit("bgclick"))},[n("div",{class:"overlay-content",onClick:o[8]||(o[8]=u((()=>{}),["stop"]))},[n("div",ze,[n("div",Pe,[n(c,{src:e.image.url,title:e.image.title},null,8,["src","title"])])]),n("div",Ie,[n("table",null,[n("tr",null,[_e,n("td",null,[g(n("input",{type:"text","onUpdate:modelValue":o[1]||(o[1]=t=>e.tiles=t)},null,512),[[p,e.tiles]])])]),n("tr",null,[Ee,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[2]||(o[2]=t=>e.scoreMode=t),value:"1"},null,512),[[w,e.scoreMode]]),De]),Me,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[3]||(o[3]=t=>e.scoreMode=t),value:"0"},null,512),[[w,e.scoreMode]]),Be])])]),n("tr",null,[Oe,n("td",null,[n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[4]||(o[4]=t=>e.shapeMode=t),value:"0"},null,512),[[w,e.shapeMode]]),Ne]),Ue,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[5]||(o[5]=t=>e.shapeMode=t),value:"1"},null,512),[[w,e.shapeMode]]),Ge]),Ve,n("label",null,[g(n("input",{type:"radio","onUpdate:modelValue":o[6]||(o[6]=t=>e.shapeMode=t),value:"2"},null,512),[[w,e.shapeMode]]),$e])])])])]),n("div",Re,[n("button",{class:"btn",disabled:!e.canStartNewGame,onClick:o[7]||(o[7]=(...t)=>e.onNewGameClick&&e.onNewGameClick(...t))}," 🧩 Generate Puzzle ",8,["disabled"])])])])};class Fe{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 Fe(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},je=(...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 We={hash:e=>{let t=0;for(let n=0;n{let t=e.toLowerCase();return t=t.replace(/[^a-z0-9]+/g,"-"),t=t.replace(/^-|-$/,""),t},uniqId:()=>Date.now().toString(36)+Math.random().toString(36).substring(2),encodeShape:function(e){return e.top+1<<0|e.right+1<<2|e.bottom+1<<4|e.left+1<<6},decodeShape:function(e){return{top:(e>>0&3)-1,right:(e>>2&3)-1,bottom:(e>>4&3)-1,left:(e>>6&3)-1}},encodePiece:function(e){return[e.idx,e.pos.x,e.pos.y,e.z,e.owner,e.group]},decodePiece:function(e){return{idx:e[0],pos:{x:e[1],y:e[2]},z:e[3],owner:e[4],group:e[5]}},encodePlayer:function(e){return[e.id,e.x,e.y,e.d,e.name,e.color,e.bgcolor,e.points,e.ts]},decodePlayer:function(e){return{id:e[0],x:e[1],y:e[2],d:e[3],name:e[4],color:e[5],bgcolor:e[6],points:e[7],ts:e[8]}},encodeGame:function(e){return[e.id,e.rng.type||"",Fe.serialize(e.rng.obj),e.puzzle,e.players,e.evtInfos,e.scoreMode||Ce.FINAL]},decodeGame:function(e){return{id:e[0],rng:{type:e[1],obj:Fe.unserialize(e[2])},puzzle:e[3],players:e[4],evtInfos:e[5],scoreMode:e[6]}},coordByPieceIdx:function(e,t){const n=e.width/e.tileSize;return{x:t%n,y:Math.floor(t/n)}},asQueryArgs:function(e){const t=[];for(const n in e){const o=[n,e[n]].map(encodeURIComponent);t.push(o.join("="))}return 0===t.length?"":`?${t.join("&")}`}},qe=e({components:{ImageLibrary:H,NewImageDialog:X,EditImageDialog:ge,NewGameDialog:Te},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${We.asQueryArgs(this.filters)}`),t=await e.json();this.images=t.images,this.tags=t.tags},async filtersChanged(){await this.loadImages()},onImageClicked(e){this.image=e,this.dialog="new-game"},onImageEditClicked(e){this.image=e,this.dialog="edit-image"},async uploadImage(e){const t=new FormData;t.append("file",e.file,e.file.name),t.append("title",e.title),t.append("tags",e.tags);const n=await fetch("/api/upload",{method:"post",body:t});return await n.json()},async saveImage(e){const t=await fetch("/api/save-image",{method:"post",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({id:e.id,title:e.title,tags:e.tags})});return await t.json()},async onSaveImageClick(e){await this.saveImage(e),this.dialog="",await this.loadImages()},async postToGalleryClick(e){await this.uploadImage(e),this.dialog="",await this.loadImages()},async setupGameClick(e){const t=await this.uploadImage(e);this.loadImages(),this.image=t,this.dialog="new-game"},async onNewGame(e){const t=await fetch("/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 He={class:"upload-image-teaser"},Ye=n("div",{class:"hint"},"(The image you upload will be added to the public gallery.)",-1),Qe={key:0},Ze=s(" Tags: "),Ke=s(" Sort by: "),Je=n("option",{value:"date_desc"},"Newest first",-1),Xe=n("option",{value:"date_asc"},"Oldest first",-1),et=n("option",{value:"alpha_asc"},"A-Z",-1),tt=n("option",{value:"alpha_desc"},"Z-A",-1);qe.render=function(e,o,s,u,p,h){const m=a("image-library"),y=a("new-image-dialog"),f=a("edit-image-dialog"),w=a("new-game-dialog");return i(),t("div",null,[n("div",He,[n("div",{class:"btn btn-big",onClick:o[1]||(o[1]=t=>e.dialog="new-image")},"Upload your image"),Ye]),n("div",null,[e.tags.length>0?(i(),t("label",Qe,[Ze,(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,[Ke,g(n("select",{"onUpdate:modelValue":o[2]||(o[2]=t=>e.filters.sort=t),onChange:o[3]||(o[3]=(...t)=>e.filtersChanged&&e.filtersChanged(...t))},[Je,Xe,et,tt],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?(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(w,{key:2,onBgclick:o[6]||(o[6]=t=>e.dialog=""),onNewGame:e.onNewGame,image:e.image},null,8,["onNewGame","image"])):l("",!0)])};var nt=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 ot={class:"scores"},lt=n("div",null,"Scores",-1),at=n("td",null,"⚡",-1),it=n("td",null,"💤",-1);nt.render=function(e,o,l,a,s,u){return i(),t("div",ot,[lt,n("table",null,[(i(!0),t(d,null,c(e.actives,((e,o)=>(i(),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)),(i(!0),t(d,null,c(e.idles,((e,o)=>(i(),t("tr",{key:o,style:{color:e.color}},[it,n("td",null,r(e.name),1),n("td",null,r(e.points),1)],4)))),128))])])};var st=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 N(this.duration)}}});const rt={class:"timer"};st.render=function(e,o,l,a,s,d){return i(),t("div",rt,[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 dt=e({name:"settings-overlay",emits:{bgclick:null,"update:modelValue":null},props:{modelValue:Object},created(){this.$watch("modelValue",(e=>{this.$emit("update:modelValue",e)}),{deep:!0})}});const ct=n("td",null,[n("label",null,"Background: ")],-1),ut=n("td",null,[n("label",null,"Color: ")],-1),gt=n("td",null,[n("label",null,"Name: ")],-1),pt=n("td",null,[n("label",null,"Sounds: ")],-1);dt.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,[ct,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":o[1]||(o[1]=t=>e.modelValue.background=t)},null,512),[[p,e.modelValue.background]])])]),n("tr",null,[ut,n("td",null,[g(n("input",{type:"color","onUpdate:modelValue":o[2]||(o[2]=t=>e.modelValue.color=t)},null,512),[[p,e.modelValue.color]])])]),n("tr",null,[gt,n("td",null,[g(n("input",{type:"text",maxLength:"16","onUpdate:modelValue":o[3]||(o[3]=t=>e.modelValue.name=t)},null,512),[[p,e.modelValue.name]])])]),n("tr",null,[pt,n("td",null,[g(n("input",{type:"checkbox","onUpdate:modelValue":o[4]||(o[4]=t=>e.modelValue.soundsEnabled=t)},null,512),[[x,e.modelValue.soundsEnabled]])])])])])};var ht=e({name:"preview-overlay",props:{img:String},emits:{bgclick:null},computed:{previewStyle(){return{backgroundImage:`url('${this.img}')`}}}});const mt={class:"preview"};ht.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",mt,[n("div",{class:"img",style:e.previewStyle},null,4)])])};var yt=1,ft=4,wt=2,vt=3,bt=2,xt=4,Ct=3,kt=9,At=1,St=2,Tt=3,zt=4,Pt=5,It=6,_t=7,Et=8,Dt=10,Mt=11,Bt=1,Ot=2,Nt=3;const Ut=je("Communication.js");let Gt,Vt=[],$t=e=>{Vt.push(e)},Rt=[],Ft=e=>{Rt.push(e)};let Lt=0;const jt=e=>{Lt!==e&&(Lt=e,Ft(e))};function Wt(e){if(2===Lt)try{Gt.send(JSON.stringify(e))}catch(t){Ut.info("unable to send message.. maybe because ws is invalid?")}}let qt,Ht;var Yt={connect:function(e,t,n){return qt=0,Ht={},jt(3),new Promise((o=>{Gt=new WebSocket(e,n+"|"+t),Gt.onopen=()=>{jt(2),Wt([vt])},Gt.onmessage=e=>{const t=JSON.parse(e.data),l=t[0];if(l===ft){const e=t[1];o(e)}else{if(l!==yt)throw`[ 2021-05-09 invalid connect msgType ${l} ]`;{const e=t[1],o=t[2];if(e===n&&Ht[o])return void delete Ht[o];$t(t)}}},Gt.onerror=()=>{throw jt(1),"[ 2021-05-15 onerror ]"},Gt.onclose=e=>{4e3===e.code||1001===e.code?jt(4):jt(1)}}))},requestReplayData:async function(e,t,n){const o={gameId:e,offset:t,size:n},l=await fetch(`/api/replay-data${We.asQueryArgs(o)}`);return await l.json()},disconnect:function(){Gt&&Gt.close(4e3),qt=0,Ht={}},sendClientEvent:function(e){qt++,Ht[qt]=e,Wt([wt,qt,Ht[qt]])},onServerChange:function(e){$t=e;for(const t of Vt)$t(t);Vt=[]},onConnectionStateChange:function(e){Ft=e;for(const t of Rt)Ft(t);Rt=[]},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},Qt=e({name:"connection-overlay",emits:{reconnect:null},props:{connectionState:Number},computed:{lostConnection(){return this.connectionState===Yt.CONN_STATE_DISCONNECTED},connecting(){return this.connectionState===Yt.CONN_STATE_CONNECTING},show(){return!(!this.lostConnection&&!this.connecting)}}});const Zt={key:0,class:"overlay connection-lost"},Kt={key:0,class:"overlay-content"},Jt=n("div",null,"⁉️ LOST CONNECTION ⁉️",-1),Xt={key:1,class:"overlay-content"},en=n("div",null,"Connecting...",-1);Qt.render=function(e,o,a,s,r,d){return e.show?(i(),t("div",Zt,[e.lostConnection?(i(),t("div",Kt,[Jt,n("span",{class:"btn",onClick:o[1]||(o[1]=t=>e.$emit("reconnect"))},"Reconnect")])):l("",!0),e.connecting?(i(),t("div",Xt,[en])):l("",!0)])):l("",!0)};var tn=e({name:"help-overlay",emits:{bgclick:null}});const nn=n("tr",null,[n("td",null,"⬆️ Move up:"),n("td",null,[n("div",null,[n("kbd",null,"W"),s("/"),n("kbd",null,"↑"),s("/🖱️")])])],-1),on=n("tr",null,[n("td",null,"⬇️ Move down:"),n("td",null,[n("div",null,[n("kbd",null,"S"),s("/"),n("kbd",null,"↓"),s("/🖱️")])])],-1),ln=n("tr",null,[n("td",null,"⬅️ Move left:"),n("td",null,[n("div",null,[n("kbd",null,"A"),s("/"),n("kbd",null,"←"),s("/🖱️")])])],-1),an=n("tr",null,[n("td",null,"➡️ Move right:"),n("td",null,[n("div",null,[n("kbd",null,"D"),s("/"),n("kbd",null,"→"),s("/🖱️")])])],-1),sn=n("tr",null,[n("td"),n("td",null,[n("div",null,[s("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"),s("/🖱️-Wheel")])])],-1),dn=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),un=n("tr",null,[n("td",null,"🧩✔️ Toggle fixed pieces:"),n("td",null,[n("div",null,[n("kbd",null,"F")])])],-1),gn=n("tr",null,[n("td",null,"🧩❓ Toggle loose pieces:"),n("td",null,[n("div",null,[n("kbd",null,"G")])])],-1),pn=n("tr",null,[n("td",null,"🔉 Toggle sounds:"),n("td",null,[n("div",null,[n("kbd",null,"M")])])],-1);tn.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"]))},[nn,on,ln,an,sn,rn,dn,cn,un,gn,pn])])};var hn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"/assets/click.550555f3.mp3"}),mn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAW0lEQVQ4je1RywrAIAxLxP//5exixRWlVgZelpOKeTQFfnDypgy3eLIkSLLL8mxGPoHsU2hPAgDHBLvRX6hZZw/fwT0BtlLSONqCbWAmEIqMZOCDDlaDR3N03gOyDCn+y4DWmAAAAABJRU5ErkJggg=="}),yn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARElEQVQ4jWNgGAU0Af+hmBCbgYGBgYERhwHEAEYGBgYGJtIdiApYyLAZBVDsAqoagC1ACQJyY4ERg0GCISh6KA4DigEAou8LC+LnIJoAAAAASUVORK5CYII="}),fn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAcUlEQVQ4ja1TQQ7AIAgD///n7jCozA2Hbk00jbG1KIrcARszTugoBs49qioZj7r2kKACptkyAOCJsJuA+GzglwHjvMSSWFVaENWVASxh5eRLiq5fN/ASjI89VcP2K3hHpq1cEXNaMfnrL3TDZP2tDuoOA6MzCCXWr38AAAAASUVORK5CYII="}),wn=Object.freeze({__proto__:null,[Symbol.toStringTag]:"Module",default:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAU0lEQVQ4jWNgoAH4D8X42HDARKlt5BoAd82AuQAOGLGIYQQUPv0wF5CiCQUge4EsQ5C9QI4BjMguwBYeBAElscCIy1ZivMKIwSDBEBQ9FCckigEAU3QOD7TGvY4AAAAASUVORK5CYII="});function vn(){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 bn(e=0,t=0){const n=document.createElement("canvas");return n.width=e,n.height=t,n}var xn={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 o=bn(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=bn(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 Cn=je("Debug.js");let kn=0,An=0;var Sn=e=>{kn=performance.now(),An=e},Tn=e=>{const t=performance.now(),n=t-kn;n>An&&Cn.log(e+": "+n),kn=t};function zn(e,t){const n=e.x-t.x,o=e.y-t.y;return Math.sqrt(n*n+o*o)}function Pn(e){return{x:e.x+e.w/2,y:e.y+e.h/2}}var In={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:zn,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:Pn,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 zn(Pn(e),Pn(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 _n=je("PuzzleGraphics.js");function En(e,t){const n=We.coordByPieceIdx(e,t);return{x:n.x*e.tileSize,y:n.y*e.tileSize,w:e.tileSize,h:e.tileSize}}var Dn={loadPuzzleBitmaps:async function(e){const t=await xn.loadImageToBitmap(e.info.imageUrl),n=await xn.resizeBitmap(t,e.info.width,e.info.height);return await async function(e,t,n){_n.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=In.pointAdd(a,{x:o,y:0}),c=In.pointAdd(r,{x:0,y:o}),u=In.pointSub(c,{x:o,y:0});if(n.moveTo(a.x,a.y),0!==e.top)for(let o=0;oWe.decodePiece(Mn[e].puzzle.tiles[t]),Hn=(e,t)=>qn(e,t).group,Yn=(e,t)=>{const n=Mn[e].puzzle.info,o={x:(n.table.width-n.width)/2,y:(n.table.height-n.height)/2},l=function(e,t){const n=Mn[e].puzzle.info,o=We.coordByPieceIdx(n,t),l=o.x*n.tileSize,a=o.y*n.tileSize;return{x:l,y:a}}(e,t);return In.pointAdd(o,l)},Qn=(e,t)=>qn(e,t).pos,Zn=e=>{const t=go(e),n=po(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}},Kn=(e,t)=>{const n=to(e),o=qn(e,t);return{x:o.pos.x,y:o.pos.y,w:n,h:n}},Jn=(e,t)=>qn(e,t).z,Xn=(e,t)=>{for(const n of Mn[e].puzzle.tiles){const e=We.decodePiece(n);if(e.owner===t)return e.idx}return-1},eo=e=>Mn[e].puzzle.info.tileDrawSize,to=e=>Mn[e].puzzle.info.tileSize,no=e=>Mn[e].puzzle.data.maxGroup,oo=e=>Mn[e].puzzle.data.maxZ;function lo(e,t){const n=Mn[e].puzzle.info,o=We.coordByPieceIdx(n,t);return[o.y>0?t-n.tilesX:-1,o.x0?t-1:-1]}const ao=(e,t,n)=>{for(const o of t)Wn(e,o,{z:n})},io=(e,t,n)=>{const o=Qn(e,t);Wn(e,t,{pos:In.pointAdd(o,n)})},so=(e,t,n)=>{const o=eo(e),l=Zn(e),a=n;for(const i of t){const t=qn(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)io(e,i,a)},ro=(e,t,n)=>{for(const o of t)Wn(e,o,{owner:n})};function co(e,t){const n=Mn[e].puzzle.tiles,o=We.decodePiece(n[t]),l=[];if(o.group)for(const a of n){const e=We.decodePiece(a);e.group===o.group&&l.push(e.idx)}else l.push(o.idx);return l}const uo=(e,t)=>{const n=On(e,t);return n?n.points:0},go=e=>Mn[e].puzzle.info.table.width,po=e=>Mn[e].puzzle.info.table.height;var ho={setGame:function(e,t){Mn[e]=t},exists:function(e){return!!Mn[e]||!1},playerExists:Un,getActivePlayers:function(e,t){const n=t-30*M;return Gn(e).filter((e=>e.ts>=n))},getIdlePlayers:function(e,t){const n=t-30*M;return Gn(e).filter((e=>e.ts0))},addPlayer:function(e,t,n){Un(e,t)?Ln(e,t,{ts:n}):Nn(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:Fn,getPieceCount:Vn,getImageUrl:function(e){return Mn[e].puzzle.info.imageUrl},setImageUrl:function(e,t){Mn[e].puzzle.info.imageUrl=t},get:function(e){return Mn[e]||null},getAllGames:function(){return Object.values(Mn).sort(((e,t)=>Rn(e.id)===Rn(t.id)?t.puzzle.data.started-e.puzzle.data.started:Rn(e.id)?1:-1))},getPlayerBgColor:(e,t)=>{const n=On(e,t);return n?n.bgcolor:null},getPlayerColor:(e,t)=>{const n=On(e,t);return n?n.color:null},getPlayerName:(e,t)=>{const n=On(e,t);return n?n.name:null},getPlayerIndexById:Bn,getPlayerIdByIndex:function(e,t){return Mn[e].players.length>t?We.decodePlayer(Mn[e].players[t]).id:null},changePlayer:Ln,setPlayer:Nn,setPiece:function(e,t,n){Mn[e].puzzle.tiles[t]=We.encodePiece(n)},setPuzzleData:function(e,t){Mn[e].puzzle.data=t},getTableWidth:go,getTableHeight:po,getPuzzle:e=>Mn[e].puzzle,getRng:e=>Mn[e].rng.obj,getPuzzleWidth:e=>Mn[e].puzzle.info.width,getPuzzleHeight:e=>Mn[e].puzzle.info.height,getPiecesSortedByZIndex:function(e){return Mn[e].puzzle.tiles.map(We.decodePiece).sort(((e,t)=>e.z-t.z))},getFirstOwnedPiece:(e,t)=>{const n=Xn(e,t);return n<0?null:Mn[e].puzzle.tiles[n]},getPieceDrawOffset:e=>Mn[e].puzzle.info.tileDrawOffset,getPieceDrawSize:eo,getFinalPiecePos:Yn,getStartTs:e=>Mn[e].puzzle.data.started,getFinishTs:e=>Mn[e].puzzle.data.finished,handleInput:function(e,t,n,o,l){const a=Mn[e].puzzle,i=function(e,t){return t in Mn[e].evtInfos?Mn[e].evtInfos[t]:{_last_mouse:null,_last_mouse_down:null}}(e,t),s=[],r=()=>{s.push([Bt,a.data])},d=t=>{s.push([Ot,We.encodePiece(qn(e,t))])},c=e=>{for(const t of e)d(t)},u=()=>{const n=On(e,t);n&&s.push([Nt,We.encodePlayer(n)])},g=n[0];if(g===It){const l=n[1];Ln(e,t,{bgcolor:l,ts:o}),u()}else if(g===_t){const l=n[1];Ln(e,t,{color:l,ts:o}),u()}else if(g===Et){const l=`${n[1]}`.substr(0,16);Ln(e,t,{name:l,ts:o}),u()}else if(g===kt){const l=n[1],a=n[2],i=On(e,t);if(i){const n=i.x-l,s=i.y-a;Ln(e,t,{ts:o,x:n,y:s}),u()}}else if(g===At){const l={x:n[1],y:n[2]};Ln(e,t,{d:1,ts:o}),u(),i._last_mouse_down=l;const a=((e,t)=>{const n=Mn[e].puzzle.info,o=Mn[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=oo(e)+1;jn(e,{maxZ:n}),r();const o=co(e,a);ao(e,o,oo(e)),ro(e,o,t),c(o)}i._last_mouse=l}else if(g===Tt){const l=n[1],a=n[2],s={x:l,y:a};if(null===i._last_mouse_down)Ln(e,t,{x:l,y:a,ts:o}),u();else{const n=Xn(e,t);if(n>=0){Ln(e,t,{x:l,y:a,ts:o}),u();const r=co(e,n);let d=In.pointInBounds(s,Zn(e))&&In.pointInBounds(i._last_mouse_down,Zn(e));for(const t of r){const n=Kn(e,t);if(In.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 Ln(e,t,{ts:o}),u();i._last_mouse_down=s}i._last_mouse=s}else if(g===St){const s={x:n[1],y:n[2]},g=0;i._last_mouse_down=null;const p=Xn(e,t);if(p>=0){const n=co(e,p);ro(e,n,0),c(n);const i=Qn(e,p),s=Yn(e,p);if(In.pointDistance(s,i){for(const n of t)Wn(e,n,{owner:-1,z:1})})(e,n),c(n);let d=uo(e,t);$n(e)===Ce.FINAL?d+=n.length:$n(e)===Ce.ANY&&(d+=1),Ln(e,t,{d:g,ts:o,points:d}),u(),Fn(e)===Vn(e)&&(jn(e,{finished:o}),r()),l&&l(t)}else{const n=(e,t,n,o)=>{const l=Mn[e].puzzle.info;if(n<0)return!1;if(((e,t,n)=>{const o=Hn(e,t),l=Hn(e,n);return!(!o||o!==l)})(e,t,n))return!1;const a=Qn(e,t),i=In.pointAdd(Qn(e,n),{x:o[0]*l.tileSize,y:o[1]*l.tileSize});if(In.pointDistance(a,i){const o=Mn[e].puzzle.tiles,l=Hn(e,t),a=Hn(e,n);let i;const s=[];l&&s.push(l),a&&s.push(a),l?i=l:a?i=a:(jn(e,{maxGroup:no(e)+1}),r(),i=no(e));if(Wn(e,t,{group:i}),d(t),Wn(e,n,{group:i}),d(n),s.length>0)for(const r of o){const t=We.decodePiece(r);s.includes(t.group)&&(Wn(e,t.idx,{group:i}),d(t.idx))}})(e,t,n),l=co(e,t);const s=((e,t)=>{let n=0;for(const o of t){const t=Jn(e,o);t>n&&(n=t)}return n})(e,l);return ao(e,l,s),c(l),!0}return!1};let a=!1;for(const t of co(e,p)){const o=lo(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&&$n(e)===Ce.ANY){const n=uo(e,t)+1;Ln(e,t,{d:g,ts:o,points:n}),u()}else Ln(e,t,{d:g,ts:o}),u();a&&l&&l(t)}}else Ln(e,t,{d:g,ts:o}),u();i._last_mouse=s}else if(g===zt){const l=n[1],a=n[2];Ln(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else if(g===Pt){const l=n[1],a=n[2];Ln(e,t,{x:l,y:a,ts:o}),u(),i._last_mouse={x:l,y:a}}else Ln(e,t,{ts:o}),u();return function(e,t,n){Mn[e].evtInfos[t]=n}(e,t,i),s}};let mo=-10,yo=20,fo=2,wo=15;class vo{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()*yo,this.vy=-1*(fo+Math.random()*wo),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;fo=t/2,wo=t-fo;const n=1/4*this.canvas.width/(t/2);mo=-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 vo(this.rng))}update(){100*Math.random()<5&&this.readyBombs.push(new vo(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(xn.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,To=!0})),t}(l,xn.createCanvas()),v={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};Yt.onConnectionStateChange((e=>{a.setConnectionState(e)}));const b=async e=>{v.requesting=!0;const t=await Yt.requestReplayData(e,v.dataOffset,v.dataSize);return v.dataOffset+=v.dataSize,v.requesting=!1,t};let x=()=>0;const C=async()=>{if("play"===o){const o=await Yt.connect(n,e,t),l=We.decodeGame(o);ho.setGame(l.id,l),x=()=>B()}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=We.decodeGame(t.game);ho.setGame(n.id,n),v.requesting=!1,v.log=t.log,v.lastRealTs=B(),v.gameStartTs=parseInt(v.log[0][4],10),v.lastGameTs=v.gameStartTs,x=()=>v.lastGameTs}}To=!0};await C();const k=ho.getPieceDrawOffset(e),A=ho.getPieceDrawSize(e),S=ho.getPuzzleWidth(e),T=ho.getPuzzleHeight(e),z=ho.getTableWidth(e),P=ho.getTableHeight(e),I={x:(z-S)/2,y:(P-T)/2},_={w:S,h:T},E={w:A,h:A},D=await Dn.loadPuzzleBitmaps(ho.getPuzzle(e)),M=new xo(w,ho.getRng(e));M.init();const O=w.getContext("2d");w.classList.add("loaded");const N=vn();N.move(-(z-w.width)/2,-(P-w.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 g=(e,t)=>{const o=n.viewportToWorld({x:e,y:t});return[o.x,o.y]},p=e=>g(e.offsetX,e.offsetY),h=()=>g(e.width/2,e.height/2),m=(e,t)=>{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=p(e),0===e.button&&f([At,...y])})),e.addEventListener("mouseup",(e=>{y=p(e),0===e.button&&f([St,...y])})),e.addEventListener("mousemove",(e=>{y=p(e),f([Tt,...y])})),e.addEventListener("wheel",(e=>{if(y=p(e),n.canZoom(e.deltaY<0?"in":"out")){const t=e.deltaY<0?zt:Pt;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([Dt]),"F"!==e.key&&"f"!==e.key||(Ao=!Ao,To=!0),"G"!==e.key&&"g"!==e.key||(So=!So,To=!0),"M"!==e.key&&"m"!==e.key||f([Mt]))}));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([kt,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([zt,...e])}}else if(c&&n.canZoom("out")){const e=y||h();f([Pt,...e])}},setHotkeys:e=>{l=e}}}(w,window,N),G=ho.getImageUrl(e),V=()=>{const t=ho.getStartTs(e),n=ho.getFinishTs(e),o=x();a.setFinished(!!n),a.setDuration((n||o)-t)};V(),a.setPiecesDone(ho.getFinishedPiecesCount(e)),a.setPiecesTotal(ho.getPieceCount(e));const $=x();a.setActivePlayers(ho.getActivePlayers(e,$)),a.setIdlePlayers(ho.getIdlePlayers(e,$));const R=!!ho.getFinishTs(e);let F=R;const L=()=>F&&!R,j=()=>{const e=localStorage.getItem("sound_enabled");return null!==e&&"1"===e},W=()=>ho.getPlayerBgColor(e,t)||localStorage.getItem("bg_color")||"#222222",q=()=>ho.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"];w.style.cursor=`url('${t}') ${p} ${m}, ${n}`},K=e=>{H=xn.colorizedCanvas(r,c,e).toDataURL(),Y=xn.colorizedCanvas(d,u,e).toDataURL(),Z(Q)};K(q());const J=()=>{a.setReplaySpeed&&a.setReplaySpeed(v.speeds[v.speedIdx]),a.setReplayPaused&&a.setReplayPaused(v.paused)};if("play"===o?setInterval(V,1e3):"replay"===o&&J(),"play"===o)Yt.onServerChange((n=>{n[0],n[1],n[2];const o=n[3];for(const[l,a]of o)switch(l){case Nt:{const n=We.decodePlayer(a);n.id!==t&&(ho.setPlayer(e,n.id,n),To=!0)}break;case Ot:{const t=We.decodePiece(a);ho.setPiece(e,t.idx,t),To=!0}break;case Bt:ho.setPuzzleData(e,a),To=!0}F=!!ho.getFinishTs(e)}));else if("replay"===o){const t=setInterval((()=>{const n=B();if(v.requesting)return void(v.lastRealTs=n);if(v.logPointer+1>=v.log.length)return v.lastRealTs=n,void(async e=>{const t=await b(e);v.log=v.log.slice(v.logPointer),v.logPointer=0,v.log.push(...t.log),t.log.length=v.log.length){v.final&&clearInterval(t);break}const o=v.log[n],a=v.gameStartTs+o[o.length-1];if(a>l)break;const i=o.slice();if(i[0]===bt){const t=i[1];ho.addPlayer(e,t,a),To=!0}else if(i[0]===xt){const t=ho.getPlayerIdByIndex(e,i[1]);if(!t)throw"[ 2021-05-17 player not found (update player) ]";ho.addPlayer(e,t,a),To=!0}else if(i[0]===Ct){const t=ho.getPlayerIdByIndex(e,i[1]);if(!t)throw"[ 2021-05-17 player not found (handle input) ]";const n=i[2];ho.handleInput(e,t,n,a),To=!0}v.logPointer=n}v.lastRealTs=n,v.lastGameTs=l,V()}),50)}let X=null;return(e=>{const t=e.fps||60,n=e.slow||1,o=e.update,l=e.render,a=window.requestAnimationFrame,i=1/t,s=n*i;let r,d=0,c=window.performance.now();const u=()=>{for(r=window.performance.now(),d+=Math.min(1,(r-c)/1e3);d>s;)d-=s,o(i);l(d/n),c=r,a(u)};a(u)})({update:()=>{U.createKeyEvents();for(const n of U.consumeAll())if("play"===o){const o=n[0];if(o===kt){const e=n[1],t=n[2],o=N.worldDimToViewport({w:e,h:t});To=!0,N.move(o.w,o.h)}else if(o===Tt){if(X&&!ho.getFirstOwnedPiece(e,t)){const e={x:n[1],y:n[2]},t=N.worldToViewport(e),o=Math.round(t.x-X.x),l=Math.round(t.y-X.y);To=!0,N.move(o,l),X=t}}else if(o===_t)K(n[1]);else if(o===At){const e={x:n[1],y:n[2]};X=N.worldToViewport(e),Z(!0)}else if(o===St)X=null,Z(!1);else if(o===zt){const e={x:n[1],y:n[2]};To=!0,N.zoom("in",N.worldToViewport(e))}else if(o===Pt){const e={x:n[1],y:n[2]};To=!0,N.zoom("out",N.worldToViewport(e))}else o===Dt?a.togglePreview():o===Mt&&a.toggleSoundsEnabled();const l=x();ho.handleInput(e,t,n,l,(e=>{j()&&s.play()})).length>0&&(To=!0),Yt.sendClientEvent(n)}else if("replay"===o){const e=n[0];if(e===kt){const e=n[1],t=n[2];To=!0,N.move(e,t)}else if(e===Tt){if(X){const e={x:n[1],y:n[2]},t=N.worldToViewport(e),o=Math.round(t.x-X.x),l=Math.round(t.y-X.y);To=!0,N.move(o,l),X=t}}else if(e===At){const e={x:n[1],y:n[2]};X=N.worldToViewport(e)}else if(e===St)X=null;else if(e===zt){const e={x:n[1],y:n[2]};To=!0,N.zoom("in",N.worldToViewport(e))}else if(e===Pt){const e={x:n[1],y:n[2]};To=!0,N.zoom("out",N.worldToViewport(e))}else e===Dt&&a.togglePreview()}F=!!ho.getFinishTs(e),L()&&(M.update(),To=!0)},render:async()=>{if(!To)return;const n=x();let l,i,s;window.DEBUG&&Sn(0),O.fillStyle=W(),O.fillRect(0,0,w.width,w.height),window.DEBUG&&Tn("clear done"),l=N.worldToViewportRaw(I),i=N.worldDimToViewportRaw(_),O.fillStyle="rgba(255, 255, 255, .3)",O.fillRect(l.x,l.y,i.w,i.h),window.DEBUG&&Tn("board done");const r=ho.getPiecesSortedByZIndex(e);window.DEBUG&&Tn("get tiles done"),i=N.worldDimToViewportRaw(E);for(const e of r)(-1===e.owner?Ao:So)&&(s=D[e.idx],l=N.worldToViewportRaw({x:k+e.pos.x,y:k+e.pos.y}),O.drawImage(s,0,0,s.width,s.height,l.x,l.y,i.w,i.h));window.DEBUG&&Tn("tiles done");const d=[];for(const a of ho.getActivePlayers(e,n))c=a,("replay"===o||c.id!==t)&&(s=await f(a),l=N.worldToViewport(a),O.drawImage(s,l.x-p,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&&Tn("players done"),a.setActivePlayers(ho.getActivePlayers(e,n)),a.setIdlePlayers(ho.getIdlePlayers(e,n)),a.setPiecesDone(ho.getFinishedPiecesCount(e)),window.DEBUG&&Tn("HUD done"),L()&&M.render(),To=!1}}),{setHotkeys:e=>{U.setHotkeys(e)},onBgChange:e=>{localStorage.setItem("bg_color",e),U.addEvent([It,e])},onColorChange:e=>{localStorage.setItem("player_color",e),U.addEvent([_t,e])},onNameChange:e=>{localStorage.setItem("player_name",e),U.addEvent([Et,e])},onSoundsEnabledChange:e=>{localStorage.setItem("sound_enabled",e?"1":"0")},replayOnSpeedUp:()=>{v.speedIdx+1{v.speedIdx>=1&&(v.speedIdx--,J())},replayOnPauseToggle:()=>{v.paused=!v.paused,J()},previewImageUrl:G,player:{background:W(),color:q(),name:ho.getPlayerName(e,t)||localStorage.getItem("player_name")||"anon",soundsEnabled:j()},disconnect:Yt.disconnect,connect:C}}var Po=e({name:"game",components:{PuzzleStatus:st,Scores:nt,SettingsOverlay:dt,PreviewOverlay:ht,ConnectionOverlay:Qt,HelpOverlay:tn},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=>{},disconnect:()=>{},connect:()=>{}}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await zo(`${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.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 Io={id:"game"},_o={class:"menu"},Eo={class:"tabs"},Do=s("🧩 Puzzles");Po.render=function(e,l,s,r,d,c){const u=a("settings-overlay"),p=a("preview-overlay"),h=a("help-overlay"),m=a("connection-overlay"),y=a("puzzle-status"),f=a("router-link"),w=a("scores");return i(),t("div",Io,[g(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]]),g(n(p,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),g(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",_o,[n("div",Eo,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Do])),_: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(w,{activePlayers:e.activePlayers,idlePlayers:e.idlePlayers},null,8,["activePlayers","idlePlayers"])])};var Mo=e({name:"replay",components:{PuzzleStatus:st,Scores:nt,SettingsOverlay:dt,PreviewOverlay:ht,HelpOverlay:tn},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:()=>{},disconnect:()=>{}},replay:{speed:1,paused:!1}}),async mounted(){this.$route.params.id&&(this.$watch((()=>this.g.player.background),(e=>{this.g.onBgChange(e)})),this.$watch((()=>this.g.player.color),(e=>{this.g.onColorChange(e)})),this.$watch((()=>this.g.player.name),(e=>{this.g.onNameChange(e)})),this.$watch((()=>this.g.player.soundsEnabled),(e=>{this.g.onSoundsEnabledChange(e)})),this.g=await zo(`${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.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 Bo={id:"replay"},Oo={class:"menu"},No={class:"tabs"},Uo=s("🧩 Puzzles");Mo.render=function(e,l,s,d,c,u){const p=a("settings-overlay"),h=a("preview-overlay"),m=a("help-overlay"),y=a("puzzle-status"),f=a("router-link"),w=a("scores");return i(),t("div",Bo,[g(n(p,{onBgclick:l[1]||(l[1]=t=>e.toggle("settings",!0)),modelValue:e.g.player,"onUpdate:modelValue":l[2]||(l[2]=t=>e.g.player=t)},null,8,["modelValue"]),[[C,"settings"===e.overlay]]),g(n(h,{onBgclick:l[3]||(l[3]=t=>e.toggle("preview",!1)),img:e.g.previewImageUrl},null,8,["img"]),[[C,"preview"===e.overlay]]),g(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",Oo,[n("div",No,[n(f,{class:"opener",to:{name:"index"},target:"_blank"},{default:o((()=>[Uo])),_: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(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:A(),routes:[{name:"index",path:"/",component:L},{name:"new-game",path:"/new-game",component:qe},{name:"game",path:"/g/:id",component:Po},{name:"replay",path:"/replay/:id",component:Mo}]});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=We.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 25c35b3..13893b5 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 0b37ab5..a56b184 100644 --- a/build/server/main.js +++ b/build/server/main.js @@ -1315,15 +1315,28 @@ async function getExifOrientation(imagePath) { }); }); } +const getAllTags = (db) => { + const query = ` +select c.id, c.slug, c.title, count(*) as total from categories c +inner join image_x_category ixc on c.id = ixc.category_id +group by c.id order by total desc;`; + return db._getMany(query).map(row => ({ + id: parseInt(row.id, 10) || 0, + slug: row.slug, + title: row.title, + total: parseInt(row.total, 10) || 0, + })); +}; const getTags = (db, imageId) => { const query = ` select * from categories c inner join image_x_category ixc on c.id = ixc.category_id where ixc.image_id = ?`; return db._getMany(query, [imageId]).map(row => ({ - id: parseInt(row.number, 10) || 0, + id: parseInt(row.id, 10) || 0, slug: row.slug, title: row.title, + total: 0, })); }; const imageFromDb = (db, imageId) => { @@ -1432,6 +1445,7 @@ var Images = { allImagesFromDisk, imageFromDb, allImagesFromDb, + getAllTags, resizeImage, getDimensions, }; @@ -2008,7 +2022,7 @@ app.get('/api/newgame-data', (req, res) => { const tagSlugs = q.tags ? q.tags.split(',') : []; res.send({ images: Images.allImagesFromDb(db, tagSlugs, q.sort), - tags: db.getMany('categories', {}, [{ title: 1 }]), + tags: Images.getAllTags(db), }); }); app.get('/api/index-data', (req, res) => { diff --git a/src/common/Types.ts b/src/common/Types.ts index 6f16971..15adeb3 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -60,6 +60,7 @@ export interface Tag { id: number slug: string title: string + total: number } interface GameRng { diff --git a/src/frontend/views/NewGame.vue b/src/frontend/views/NewGame.vue index a960737..fe30443 100644 --- a/src/frontend/views/NewGame.vue +++ b/src/frontend/views/NewGame.vue @@ -15,7 +15,12 @@ in jigsawpuzzles.io
@@ -132,6 +133,15 @@ export default defineComponent({ 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; @@ -141,10 +151,9 @@ export default defineComponent({ "settings" "buttons"; } -} -.new-game-dialog .area-image { - grid-area: image; - margin: 20px; + .new-game-dialog .area-image { + margin-right: 0; + } } .new-game-dialog .area-settings { grid-area: settings; @@ -162,10 +171,22 @@ export default defineComponent({ 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; From 902f8e51e9c25025f4c28cb56608f0ccfc41b48c Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 5 Jun 2021 11:08:06 +0200 Subject: [PATCH 33/78] fix styles --- build/public/assets/index.21102c83.css | 1 - build/public/assets/{index.d26a5baf.js => index.7efa4c6c.js} | 0 build/public/assets/index.8f0efd0f.css | 1 + build/public/index.html | 4 ++-- src/frontend/style.css | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 build/public/assets/index.21102c83.css rename build/public/assets/{index.d26a5baf.js => index.7efa4c6c.js} (100%) create mode 100644 build/public/assets/index.8f0efd0f.css diff --git a/build/public/assets/index.21102c83.css b/build/public/assets/index.21102c83.css deleted file mode 100644 index 9339c8c..0000000 --- a/build/public/assets/index.21102c83.css +++ /dev/null @@ -1 +0,0 @@ -: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:absolute;top:0;left:0;right:0;bottom:0;z-index:10;background:var(--bg-color)}.overlay.transparent{background:0 0}.overlay-content{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);background:var(--bg-color);padding:5px;border:solid 1px #000;box-shadow:0 0 10px 0 rgba(0,0,0,.7);z-index:1}.connection-lost .overlay-content{padding:20px;text-align:center}.preview{position:absolute;top:20px;left:20px;bottom:20px;right:20px}.preview .img{height:100%;width:100%;position:absolute;background-repeat:no-repeat;background-position:center;background-size:contain}.menu .opener{display:inline-block;margin-right:10px;color:var(--link-color)}.menu .opener:last-child{margin-right:0}.menu .opener:hover{color:var(--link-hover-color);cursor:pointer}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 diff --git a/build/public/assets/index.d26a5baf.js b/build/public/assets/index.7efa4c6c.js similarity index 100% rename from build/public/assets/index.d26a5baf.js rename to build/public/assets/index.7efa4c6c.js diff --git a/build/public/assets/index.8f0efd0f.css b/build/public/assets/index.8f0efd0f.css new file mode 100644 index 0000000..8d26cf8 --- /dev/null +++ b/build/public/assets/index.8f0efd0f.css @@ -0,0 +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 diff --git a/build/public/index.html b/build/public/index.html index 8e8e007..2fe3679 100644 --- a/build/public/index.html +++ b/build/public/index.html @@ -4,9 +4,9 @@ 🧩 jigsaw.hyottoko.club - + - +
diff --git a/src/frontend/style.css b/src/frontend/style.css index a20565a..65e0119 100644 --- a/src/frontend/style.css +++ b/src/frontend/style.css @@ -127,7 +127,7 @@ input:focus { } .overlay { - position: absolute; + position: fixed; top: 0; left: 0; right: 0; From 22f5ce0065497afa43f7079d3d4fdab26a105b2c Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 5 Jun 2021 13:04:22 +0200 Subject: [PATCH 34/78] prepare skipping of non action phases in replay --- src/frontend/game.ts | 127 +++++++++++++++++++++++-------------------- 1 file changed, 69 insertions(+), 58 deletions(-) diff --git a/src/frontend/game.ts b/src/frontend/game.ts index bbde901..7a14e42 100644 --- a/src/frontend/game.ts +++ b/src/frontend/game.ts @@ -58,7 +58,6 @@ interface Hud { } interface Replay { final: boolean - requesting: boolean log: Array // current log entries logPointer: number // pointer to current item in the log array speeds: Array @@ -67,6 +66,7 @@ interface Replay { lastRealTs: number lastGameTs: number gameStartTs: number + skipNonActionPhases: boolean // dataOffset: number dataSize: number @@ -293,7 +293,6 @@ export async function main( // TODO: refactor const REPLAY: Replay = { final: false, - requesting: true, log: [], logPointer: 0, speeds: [0.5, 1, 2, 5, 10, 20, 50, 100, 250, 500], @@ -302,6 +301,7 @@ export async function main( lastRealTs: 0, lastGameTs: 0, gameStartTs: 0, + skipNonActionPhases: false, dataOffset: 0, dataSize: 10000, } @@ -313,29 +313,23 @@ export async function main( const queryNextReplayBatch = async ( gameId: string ): Promise => { - REPLAY.requesting = true + const offset = REPLAY.dataOffset + REPLAY.dataOffset += REPLAY.dataSize const replay: ReplayData = await Communication.requestReplayData( gameId, - REPLAY.dataOffset, + offset, REPLAY.dataSize ) - REPLAY.dataOffset += REPLAY.dataSize - REPLAY.requesting = false - return replay - } - const getNextReplayBatch = async ( - gameId: string - ) => { - const replay: ReplayData = await queryNextReplayBatch(gameId) // 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 @@ -346,6 +340,10 @@ 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 ]' @@ -353,18 +351,11 @@ export async function main( const gameObject: GameType = Util.decodeGame(replay.game) Game.setGame(gameObject.id, gameObject) - REPLAY.requesting = false - REPLAY.log = replay.log REPLAY.lastRealTs = Time.timestamp() - REPLAY.gameStartTs = parseInt(REPLAY.log[0][4], 10) + REPLAY.gameStartTs = parseInt(replay.log[0][4], 10) REPLAY.lastGameTs = REPLAY.gameStartTs - REPLAY.final = false - REPLAY.logPointer = 0 - REPLAY.speeds = [0.5, 1, 2, 5, 10, 20, 50, 100, 250, 500] - REPLAY.speedIdx = 1 REPLAY.paused = false - REPLAY.dataOffset = 0 - REPLAY.dataSize = 10000 + REPLAY.skipNonActionPhases = false TIME = () => REPLAY.lastGameTs } else { @@ -503,10 +494,14 @@ export async function main( } const intervals: NodeJS.Timeout[] = [] + let to: NodeJS.Timeout const clearIntervals = () => { intervals.forEach(inter => { clearInterval(inter) }) + if (to) { + clearTimeout(to) + } } let gameLoopInstance: GameLoopInstance @@ -525,6 +520,9 @@ 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] @@ -554,65 +552,72 @@ export async function main( finished = !! Game.getFinishTs(gameId) }) } else if (MODE === MODE_REPLAY) { - // no external communication for replay mode, - // only the REPLAY.log is relevant - intervals.push(setInterval(() => { - const realTs = Time.timestamp() - if (REPLAY.requesting) { - REPLAY.lastRealTs = realTs - return + const handleLogEntry = (logEntry: any[], ts: Timestamp) => { + const entry = logEntry + if (entry[0] === Protocol.LOG_ADD_PLAYER) { + const playerId = entry[1] + Game.addPlayer(gameId, playerId, ts) + return true } + if (entry[0] === Protocol.LOG_UPDATE_PLAYER) { + const playerId = Game.getPlayerIdByIndex(gameId, entry[1]) + if (!playerId) { + throw '[ 2021-05-17 player not found (update player) ]' + } + Game.addPlayer(gameId, playerId, ts) + return true + } + if (entry[0] === Protocol.LOG_HANDLE_INPUT) { + const playerId = Game.getPlayerIdByIndex(gameId, entry[1]) + if (!playerId) { + throw '[ 2021-05-17 player not found (handle input) ]' + } + const input = entry[2] + Game.handleInput(gameId, playerId, input, ts) + return true + } + return false + } + const next = async () => { if (REPLAY.logPointer + 1 >= REPLAY.log.length) { - REPLAY.lastRealTs = realTs - getNextReplayBatch(gameId) - return + await queryNextReplayBatch(gameId) } + const realTs = Time.timestamp() if (REPLAY.paused) { REPLAY.lastRealTs = realTs + to = setTimeout(next, 50) return } const timePassedReal = realTs - REPLAY.lastRealTs const timePassedGame = timePassedReal * REPLAY.speeds[REPLAY.speedIdx] - const maxGameTs = REPLAY.lastGameTs + timePassedGame + let maxGameTs = REPLAY.lastGameTs + timePassedGame do { if (REPLAY.paused) { break } const nextIdx = REPLAY.logPointer + 1 if (nextIdx >= REPLAY.log.length) { - if (REPLAY.final) { - clearIntervals() - } break } - const logEntry = REPLAY.log[nextIdx] - const nextTs: Timestamp = REPLAY.gameStartTs + logEntry[logEntry.length - 1] + const currLogEntry = REPLAY.log[REPLAY.logPointer] + const currTs: Timestamp = REPLAY.gameStartTs + currLogEntry[currLogEntry.length - 1] + const nextLogEntry = REPLAY.log[nextIdx] + 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 + // lets skip to the next log entry + // log.info('skipping non-action, from', maxGameTs, skipInterval) + maxGameTs += skipInterval + } break } - const entryWithTs = logEntry.slice() - if (entryWithTs[0] === Protocol.LOG_ADD_PLAYER) { - const playerId = entryWithTs[1] - Game.addPlayer(gameId, playerId, nextTs) - RERENDER = true - } else if (entryWithTs[0] === Protocol.LOG_UPDATE_PLAYER) { - const playerId = Game.getPlayerIdByIndex(gameId, entryWithTs[1]) - if (!playerId) { - throw '[ 2021-05-17 player not found (update player) ]' - } - Game.addPlayer(gameId, playerId, nextTs) - RERENDER = true - } else if (entryWithTs[0] === Protocol.LOG_HANDLE_INPUT) { - const playerId = Game.getPlayerIdByIndex(gameId, entryWithTs[1]) - if (!playerId) { - throw '[ 2021-05-17 player not found (handle input) ]' - } - const input = entryWithTs[2] - Game.handleInput(gameId, playerId, input, nextTs) + if (handleLogEntry(nextLogEntry, nextTs)) { RERENDER = true } REPLAY.logPointer = nextIdx @@ -620,7 +625,13 @@ export async function main( REPLAY.lastRealTs = realTs REPLAY.lastGameTs = maxGameTs updateTimerElements() - }, 50)) + + if (!REPLAY.final) { + to = setTimeout(next, 50) + } + } + + next() } let _last_mouse_down: Point|null = null From 514b3c6b2282b88205de7896971db27ab7820f54 Mon Sep 17 00:00:00 2001 From: Zutatensuppe Date: Sat, 5 Jun 2021 17:13:17 +0200 Subject: [PATCH 35/78] 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 36/78] 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 37/78] 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 38/78] 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 39/78] 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 40/78] 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 41/78] 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 42/78] 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 43/78] 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 44/78] 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 45/78] 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 46/78] 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 47/78] 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 48/78] 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 49/78] 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 55/78] 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 56/78] 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 57/78] 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 59/78] 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 60/78] 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 61/78] 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 62/78] 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 63/78] 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 64/78] 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 65/78] 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 66/78] 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 67/78] 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 71/78] 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 72/78] 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 73/78] 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 74/78] 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 75/78] 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 76/78] 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 77/78] 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 78/78] 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