diff --git a/common/GameCommon.js b/common/GameCommon.js index ddcfb62..3cd6b29 100644 --- a/common/GameCommon.js +++ b/common/GameCommon.js @@ -6,6 +6,8 @@ import Util from './Util.js' const SCORE_MODE_FINAL = 0 const SCORE_MODE_ANY = 1 +const IDLE_TIMEOUT_SEC = 30 + // Map const GAMES = {} @@ -76,18 +78,14 @@ function playerExists(gameId, playerId) { return idx !== -1 } -function getRelevantPlayers(gameId, ts) { - const minTs = ts - 30 * Time.SEC - return getAllPlayers(gameId).filter(player => { - return player.ts >= minTs || player.points > 0 - }) +function getActivePlayers(gameId, ts) { + const minTs = ts - IDLE_TIMEOUT_SEC * Time.SEC + return getAllPlayers(gameId).filter(p => p.ts >= minTs) } -function getActivePlayers(gameId, ts) { - const minTs = ts - 30 * Time.SEC - return getAllPlayers(gameId).filter(player => { - return player.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(gameId, playerId, ts) { @@ -722,6 +720,16 @@ function handleInput(gameId, playerId, input, ts) { } } evtInfo._last_mouse = pos + } else if (type === Protocol.INPUT_EV_ZOOM_IN) { + const pos = { x: input[1], y: input[2] } + changePlayer(gameId, playerId, pos) + _playerChange() + evtInfo._last_mouse = pos + } else if (type === Protocol.INPUT_EV_ZOOM_OUT) { + const pos = { x: input[1], y: input[2] } + changePlayer(gameId, playerId, pos) + _playerChange() + evtInfo._last_mouse = pos } else { changePlayer(gameId, playerId, { ts }) _playerChange() @@ -736,8 +744,8 @@ export default { setGame, exists, playerExists, - getRelevantPlayers, getActivePlayers, + getIdlePlayers, addPlayer, getFinishedTileCount, getTileCount, diff --git a/public/Camera.js b/public/Camera.js index f44c438..d6277d1 100644 --- a/public/Camera.js +++ b/public/Camera.js @@ -5,44 +5,48 @@ export default class Camera { this.x = 0 this.y = 0 - this.zoom = 1 + this.curZoom = 1 this.minZoom = .1 this.maxZoom = 6 this.zoomStep = .05 } move(x, y) { - this.x += x / this.zoom - this.y += y / this.zoom + this.x += x / this.curZoom + this.y += y / this.curZoom + } + + canZoom(inout) { + return this.curZoom != this.calculateNewZoom(inout) + } + + calculateNewZoom(inout) { + const factor = inout === 'in' ? 1 : -1 + const newzoom = this.curZoom + this.zoomStep * this.curZoom * factor + const capped = Math.min(Math.max(newzoom, this.minZoom), this.maxZoom) + return capped } setZoom(newzoom, viewportCoordCenter) { - const zoom = Math.min(Math.max(newzoom, this.minZoom), this.maxZoom) - if (zoom == this.zoom) { + if (this.curZoom == newzoom) { return false } - const zoomFactor = 1 - (this.zoom / zoom) + const zoomFactor = 1 - (this.curZoom / newzoom) this.move( -viewportCoordCenter.x * zoomFactor, -viewportCoordCenter.y * zoomFactor, ) - this.zoom = zoom + this.curZoom = newzoom return true } - zoomOut(viewportCoordCenter) { - return this.setZoom( - this.zoom - this.zoomStep * this.zoom, - viewportCoordCenter - ) - } - - zoomIn(viewportCoordCenter) { - return this.setZoom( - this.zoom + this.zoomStep * this.zoom, - viewportCoordCenter - ) + /** + * Zooms towards/away from the provided coordinate, if possible. + * If at max or min zoom respectively, no zooming is performed. + */ + zoom(inout, viewportCoordCenter) { + return this.setZoom(this.calculateNewZoom(inout), viewportCoordCenter) } /** @@ -65,8 +69,8 @@ export default class Camera { */ viewportToWorldRaw(viewportCoord) { return { - x: (viewportCoord.x / this.zoom) - this.x, - y: (viewportCoord.y / this.zoom) - this.y, + x: (viewportCoord.x / this.curZoom) - this.x, + y: (viewportCoord.y / this.curZoom) - this.y, } } @@ -90,8 +94,8 @@ export default class Camera { */ worldToViewportRaw(worldCoord) { return { - x: (worldCoord.x + this.x) * this.zoom, - y: (worldCoord.y + this.y) * this.zoom, + x: (worldCoord.x + this.x) * this.curZoom, + y: (worldCoord.y + this.y) * this.curZoom, } } @@ -116,8 +120,8 @@ export default class Camera { */ worldDimToViewportRaw(worldDim) { return { - w: worldDim.w * this.zoom, - h: worldDim.h * this.zoom, + w: worldDim.w * this.curZoom, + h: worldDim.h * this.curZoom, } } } diff --git a/public/game.js b/public/game.js index 68f7d87..87a89e4 100644 --- a/public/game.js +++ b/public/game.js @@ -39,15 +39,6 @@ function addCanvasToDom(TARGET_EL, canvas) { return canvas } -function initme() { - let ID = localStorage.getItem('ID') - if (!ID) { - ID = Util.uniqId() - localStorage.setItem('ID', ID) - } - return ID -} - function EventAdapter (canvas, window, viewport) { let events = [] @@ -107,10 +98,12 @@ function EventAdapter (canvas, window, viewport) { }) canvas.addEventListener('wheel', (ev) => { - const evt = ev.deltaY < 0 - ? Protocol.INPUT_EV_ZOOM_IN - : Protocol.INPUT_EV_ZOOM_OUT - addEvent([evt, ...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)]) + } }) window.addEventListener('keydown', (ev) => key(true, ev)) @@ -157,9 +150,13 @@ function EventAdapter (canvas, window, viewport) { if (ZOOM_IN && ZOOM_OUT) { // cancel each other out } else if (ZOOM_IN) { - addEvent([Protocol.INPUT_EV_ZOOM_IN, ...canvasCenter()]) + if (viewport.canZoom('in')) { + addEvent([Protocol.INPUT_EV_ZOOM_IN, ...canvasCenter()]) + } } else if (ZOOM_OUT) { - addEvent([Protocol.INPUT_EV_ZOOM_OUT, ...canvasCenter()]) + if (viewport.canZoom('out')) { + addEvent([Protocol.INPUT_EV_ZOOM_OUT, ...canvasCenter()]) + } } } @@ -175,16 +172,11 @@ function EventAdapter (canvas, window, viewport) { } } -export async function main(gameId, wsAddress, MODE, TARGET_EL, HUD) { - if (typeof gameId === 'undefined') throw '[ gameId not set ]' - if (typeof wsAddress === 'undefined') throw '[ wsAddress not set ]' - if (typeof MODE === 'undefined') throw '[ MODE not set ]' - +export async function main(gameId, clientId, wsAddress, MODE, TARGET_EL, HUD) { if (typeof DEBUG === 'undefined') window.DEBUG = false - const CLIENT_ID = initme() const shouldDrawPlayerText = (player) => { - return MODE === MODE_REPLAY || player.id !== CLIENT_ID + return MODE === MODE_REPLAY || player.id !== clientId } const cursorGrab = await Graphics.loadImageToBitmap('/grab.png') @@ -227,12 +219,12 @@ export async function main(gameId, wsAddress, MODE, TARGET_EL, HUD) { let TIME if (MODE === MODE_PLAY) { - const game = await Communication.connect(wsAddress, gameId, CLIENT_ID) + const game = await Communication.connect(wsAddress, gameId, clientId) const gameObject = Util.decodeGame(game) Game.setGame(gameObject.id, gameObject) TIME = () => Time.timestamp() } else if (MODE === MODE_REPLAY) { - const {game, log} = await Communication.connectReplay(wsAddress, gameId, CLIENT_ID) + const {game, log} = await Communication.connectReplay(wsAddress, gameId, clientId) const gameObject = Util.decodeGame(game) Game.setGame(gameObject.id, gameObject) REPLAY.log = log @@ -285,16 +277,6 @@ export async function main(gameId, wsAddress, MODE, TARGET_EL, HUD) { const previewImageUrl = Game.getImageUrl(gameId) - const activePlayers = (gameId, ts) => { - const minTs = ts - 30 * Time.SEC - const players = Game.getRelevantPlayers(gameId, ts) - return players.filter(player => player.ts >= minTs) - } - const idlePlayers = (gameId, ts) => { - const minTs = ts - 30 * Time.SEC - const players = Game.getRelevantPlayers(gameId, ts) - return players.filter(player => player.ts < minTs) - } const updateTimerElements = () => { const startTs = Game.getStartTs(gameId) const finishTs = Game.getFinishTs(gameId) @@ -308,42 +290,29 @@ export async function main(gameId, wsAddress, MODE, TARGET_EL, HUD) { HUD.setPiecesDone(Game.getFinishedTileCount(gameId)) HUD.setPiecesTotal(Game.getTileCount(gameId)) const ts = TIME() - HUD.setActivePlayers(activePlayers(gameId, ts)) - HUD.setIdlePlayers(idlePlayers(gameId, ts)) + HUD.setActivePlayers(Game.getActivePlayers(gameId, ts)) + HUD.setIdlePlayers(Game.getIdlePlayers(gameId, ts)) const longFinished = !! Game.getFinishTs(gameId) let finished = longFinished const justFinished = () => finished && !longFinished const playerBgColor = () => { - return (Game.getPlayerBgColor(gameId, CLIENT_ID) + return (Game.getPlayerBgColor(gameId, clientId) || localStorage.getItem('bg_color') || '#222222') } const playerColor = () => { - return (Game.getPlayerColor(gameId, CLIENT_ID) + return (Game.getPlayerColor(gameId, clientId) || localStorage.getItem('player_color') || '#ffffff') } const playerName = () => { - return (Game.getPlayerName(gameId, CLIENT_ID) + return (Game.getPlayerName(gameId, clientId) || localStorage.getItem('player_name') || 'anon') } - const onBgChange = (value) => { - localStorage.setItem('bg_color', value) - evts.addEvent([Protocol.INPUT_EV_BG_COLOR, value]) - } - const onColorChange = (value) => { - localStorage.setItem('player_color', value) - evts.addEvent([Protocol.INPUT_EV_PLAYER_COLOR, value]) - } - const onNameChange = (value) => { - localStorage.setItem('player_name', value) - evts.addEvent([Protocol.INPUT_EV_PLAYER_NAME, value]) - } - const doSetSpeedStatus = () => { HUD.setReplaySpeed(REPLAY.speeds[REPLAY.speedIdx]) HUD.setReplayPaused(REPLAY.paused) @@ -382,7 +351,7 @@ export async function main(gameId, wsAddress, MODE, TARGET_EL, HUD) { switch (changeType) { case Protocol.CHANGE_PLAYER: { const p = Util.decodePlayer(changeData) - if (p.id !== CLIENT_ID) { + if (p.id !== clientId) { Game.setPlayer(gameId, p.id, p) RERENDER = true } @@ -468,7 +437,7 @@ export async function main(gameId, wsAddress, MODE, TARGET_EL, HUD) { RERENDER = true viewport.move(diffX, diffY) } else if (type === Protocol.INPUT_EV_MOUSE_MOVE) { - if (_last_mouse_down && !Game.getFirstOwnedTile(gameId, CLIENT_ID)) { + if (_last_mouse_down && !Game.getFirstOwnedTile(gameId, clientId)) { // move the cam const pos = { x: evt[1], y: evt[2] } const mouse = viewport.worldToViewport(pos) @@ -486,16 +455,12 @@ export async function main(gameId, wsAddress, MODE, TARGET_EL, HUD) { _last_mouse_down = null } else if (type === Protocol.INPUT_EV_ZOOM_IN) { const pos = { x: evt[1], y: evt[2] } - if (viewport.zoomIn(viewport.worldToViewport(pos))) { - RERENDER = true - Game.changePlayer(gameId, CLIENT_ID, pos) - } + RERENDER = true + viewport.zoom('in', viewport.worldToViewport(pos)) } else if (type === Protocol.INPUT_EV_ZOOM_OUT) { const pos = { x: evt[1], y: evt[2] } - if (viewport.zoomOut(viewport.worldToViewport(pos))) { - RERENDER = true - Game.changePlayer(gameId, CLIENT_ID, pos) - } + RERENDER = true + viewport.zoom('out', viewport.worldToViewport(pos)) } else if (type === Protocol.INPUT_EV_TOGGLE_PREVIEW) { HUD.togglePreview() } @@ -503,7 +468,7 @@ export async function main(gameId, wsAddress, MODE, TARGET_EL, HUD) { // LOCAL + SERVER CHANGES // ------------------------------------------------------------- const ts = TIME() - const changes = Game.handleInput(gameId, CLIENT_ID, evt, ts) + const changes = Game.handleInput(gameId, clientId, evt, ts) if (changes.length > 0) { RERENDER = true } @@ -536,14 +501,12 @@ export async function main(gameId, wsAddress, MODE, TARGET_EL, HUD) { _last_mouse_down = null } else if (type === Protocol.INPUT_EV_ZOOM_IN) { const pos = { x: evt[1], y: evt[2] } - if (viewport.zoomIn(viewport.worldToViewport(pos))) { - RERENDER = true - } + RERENDER = true + viewport.zoom('in', viewport.worldToViewport(pos)) } else if (type === Protocol.INPUT_EV_ZOOM_OUT) { const pos = { x: evt[1], y: evt[2] } - if (viewport.zoomOut(viewport.worldToViewport(pos))) { - RERENDER = true - } + RERENDER = true + viewport.zoom('out', viewport.worldToViewport(pos)) } else if (type === Protocol.INPUT_EV_TOGGLE_PREVIEW) { HUD.togglePreview() } @@ -639,8 +602,8 @@ export async function main(gameId, wsAddress, MODE, TARGET_EL, HUD) { // propagate HUD changes // --------------------------------------------------------------- - HUD.setActivePlayers(activePlayers(gameId, ts)) - HUD.setIdlePlayers(idlePlayers(gameId, ts)) + HUD.setActivePlayers(Game.getActivePlayers(gameId, ts)) + HUD.setIdlePlayers(Game.getIdlePlayers(gameId, ts)) HUD.setPiecesDone(Game.getFinishedTileCount(gameId)) if (DEBUG) Debug.checkpoint('HUD done') // --------------------------------------------------------------- @@ -661,9 +624,18 @@ export async function main(gameId, wsAddress, MODE, TARGET_EL, HUD) { setHotkeys: (state) => { evts.setHotkeys(state) }, - onBgChange, - onColorChange, - onNameChange, + onBgChange: (value) => { + localStorage.setItem('bg_color', value) + evts.addEvent([Protocol.INPUT_EV_BG_COLOR, value]) + }, + onColorChange: (value) => { + localStorage.setItem('player_color', value) + evts.addEvent([Protocol.INPUT_EV_PLAYER_COLOR, value]) + }, + onNameChange: (value) => { + localStorage.setItem('player_name', value) + evts.addEvent([Protocol.INPUT_EV_PLAYER_NAME, value]) + }, replayOnSpeedUp, replayOnSpeedDown, replayOnPauseToggle, diff --git a/public/index.html b/public/index.html index 8647404..de66d55 100644 --- a/public/index.html +++ b/public/index.html @@ -18,6 +18,15 @@ const res = await fetch(`/api/conf`) const conf = await res.json() + function initme() { + let ID = localStorage.getItem('ID') + if (!ID) { + ID = Util.uniqId() + localStorage.setItem('ID', ID) + } + return ID + } + const router = VueRouter.createRouter({ history: VueRouter.createWebHashHistory(), routes: [ @@ -37,6 +46,7 @@ const app = Vue.createApp(App) app.config.globalProperties.$config = conf + app.config.globalProperties.$clientId = initme() app.use(router) app.mount('#app') })() diff --git a/public/views/Game.vue.js b/public/views/Game.vue.js index 386b0d2..5aeaca5 100644 --- a/public/views/Game.vue.js +++ b/public/views/Game.vue.js @@ -82,6 +82,7 @@ export default { }) this.g = await main( this.$route.params.id, + this.$clientId, this.$config.WS_ADDRESS, MODE_PLAY, this.$el, diff --git a/public/views/Replay.vue.js b/public/views/Replay.vue.js index 94e77e0..c62b498 100644 --- a/public/views/Replay.vue.js +++ b/public/views/Replay.vue.js @@ -97,6 +97,7 @@ export default { }) this.g = await main( this.$route.params.id, + this.$clientId, this.$config.WS_ADDRESS, MODE_REPLAY, this.$el, diff --git a/server/Game.js b/server/Game.js index bc23561..642cf6e 100644 --- a/server/Game.js +++ b/server/Game.js @@ -58,7 +58,6 @@ export default { addPlayer, handleInput, getAllGames: GameCommon.getAllGames, - getRelevantPlayers: GameCommon.getRelevantPlayers, getActivePlayers: GameCommon.getActivePlayers, getFinishedTileCount: GameCommon.getFinishedTileCount, getImageUrl: GameCommon.getImageUrl,